Modern HTML as a foundation for progressive enhancement

by Gaël Poupard published on

Reading HTMHell, you might be aware that progressive enhancement is a thing. To sum things up, it's a way to make sure anyone gets a viable version of your page whatever is their context — slow bandwitdh, oldish browser, etc. — but also making the said page more resilient (e.g. to JavaScript errors).

Progressive enhancement starts from the ground up:

  1. content,
  2. marked up with HTML,
  3. styled with CSS,
  4. enhanced with JavaScript
  5. and improve accessibility with a bit of ARIA.

Each of those steps should work as-is and enhance the lower steps without breaking them. In other words, you would need to write your markup independently from the CSS or JS you will apply later on.

Great.

That being said, it's kinda obvious that the better you know each step in the stack the more robust your page should be. JavaScript is everywhere so we may assume you know about it. ARIA is getting some shedlight for a few years now, so even without knowing ARIA we may suppose you use components libraries or something doing this just fine. CSS is constantly improving, I suppose you're following and discovering new CSS stuff every day or so.

But what about HTML? Is it part of your technical watch? How frequent are new HTML stuff popping out in your information sources? Not so often, I guess.

But hey. HTML is a living standard so specifications do change, browsers always improve support for HTML —as part of Interop 2022 for example— so there are new kids in town, either specified or implemented in browsers, and some of them are meant to be native controls replacing long-standing <div>-based JavaScript components.

What if we could improve the HTML stack for such components, making the markup step more resilient?

Disclaimer: you need to think of HTML as a foundation, not a replacement for your existing JavaScript component. If your pattern needs ARIA, do not try to implement a low-elevel component. In other word, always try to provide the best possible user experience and don't care too much about developer experience. You may be interested in Adrian Roselli's under-engineered components blog posts serie.

Let's go for a ride!

<details> and <summary> as a ARIA disclosure pattern foundation

Think of those "Show more" button thinggies where activating it make the content below appear. Making such component accessible currently require you to implement the ARIA disclosure design pattern, basically using a button with aria-expanded attribute to communicate state.

<details> and <summary> are a really good fit for this. As their names are pretty obvious, this markup pattern will display a summary and hide details—but make details available when activating the summary— and the browsers implementation and support are really good.

This feels quite right, isn't it? So why bother with the disclosure pattern at all and not use details and summary all the way? Well, interactions are not exactly what's expected by your users and support in browser and assistive technologies is not straightforward yet— as Scott O'Hara pointed out.

But… may we use it as our foundation for an ARIA disclosure pattern? Let's figure it out in a dedicated CodePen!

See the Pen Enhanced <details> with ARIA disclosure by ffoodd (@ffoodd) on CodePen.

Not much magic here: to improve and harmonize AT support, we make it a disclosure by adding to <summary> a button role and aria-expanded to convey state and make sure the disclosed content is hidden with the eponymous attribute. The open attribute on <details> handles this natively but we can't rely on it as long as we override the default triangle marker —which we will undoubtedly do: we must fully implement the disclosure pattern to prevent inconsistent rendering in browsers and ATs.

Moreover, did you know Chromium-based browsers allow collapsed details content discoverability using browser search? That's something coming to HTML with the until-found value for the hidden attribute, but that's not supposed to be the case of disclosed content when using the ARIA pattern.

As you may have understood, using <details> and <summary> enhanced with the ARIA disclosure pattern makes little to no difference for most users… but whenever your JS or CSS fails, your semantic markup would take over and stay interactive—with all its current limitations in ATs.

Also please be careful using those elements as a foundation: they aren't a good fit for accordions, a flyout menu, a burger menu, a tab set or a dialog… Stay semantic!

What if I told you there's more progressively enhanceable HTML elements like those? Wanna have a look?

<output> as a live region

Ever needed to populate a live region to communicate content changes to screen reader users—often being visually hidden with CSS? That's quite common now, and you find live region handling in some frameworks: Angular has a LiveAnnouncer in its Accessibility CDK, WordPress has it wp.a11y.speak() internal scriptARIA live regions rely on three specific roles: status, log and alert, each having a slightly different behaviour.

But did you know there's an HTML element that's a native live region, with an implicit status role? Meet <output>! Its browser implementation is complete and support is excellent. However some specific browsers and ATs pairing are flawed, as Scott O'Hara (again) pointed out.

Let's make <output> more robust by making it an ARIA live region, on CodePen!

See the Pen Enhanced <output> with status role by ffoodd (@ffoodd) on CodePen.

As you may remember, expliciting HTML elements roles was quite needed a few years ago for landmarks mostly to improve support in browsers and ATs —and still recommended in some accessibility guidelines like the French RGAA. We got used to write <main role="main">, <header role="banner"> and so on. What's also good with <output role="status"> is that it allows you to style your live regions by selecting the output element: lower specificity, and some kind of soft reset too —CSS containment, maybe?

A more specific live region: toast!

There's also a case for visually displayed live regions: toasts! I won't go way further on the topic, because it's been explored in a wide variety of posts: Scott O'Hara already mentionned The output element, a technical follow-up of his A toast to an accessible toast… addressing UX concerns), and Adrian Roselli in Defining toast messages.

Warning: if your toast or live region contains interactive elements, it should probably use a dialog or alertdialog role, and preferably not an <output> element at all. But… there might be another HTML element for that!

A modal <dialog>

I won't go that deep with the <dialog> element, but let's explore what it does natively compared to the ARIA dialog design pattern:

  • it has an implicit dialog role,
  • when opened with the .showModal() method:
    • it has an implicit aria-modal="true" property,
    • it's closable with the Escape key,
  • it traps focus in the dialog —but allows to get out of the browser frame…
  • it restores focus to the opening element when closing the dialog,
  • it makes the document inert automatically.

And there's more:

  • the ::backdrop pseudo-element allows you to style your dialog overlay really easily,
  • the :modal pseudo-class applies when opening dialog with the .showModal() method,
  • if your dialog contains a form, using method="dialog" form attribute makes your submit button to close the dialog.

Dialog element on its own isn't consistent enough in browsers and ATs (yet), as Scott O'Harra (again) pointed out, but you got the idea: what about enhancing it by applying the ARIA dialog pattern to it, and relying on its standard behaviour to save a few lines of JavaScript? That's more or less what Adam Argyle explored in his dialog component pattern on web.dev with some nice little tricks to learn —however keep in mind web.dev patterns aren't battle-tested with ATs nor against WCAG. Building a dialog component is not straightforward, and Kitty Giraudel goes in details about what's needed to make an accessible dialog from scratch. Since those posts, the dialog support has improved quite a lot and is even part of Interop 2022, so expect it to improve again this year's last month.

This is probably the least obvious case, since speaking of progressive enhancement the <dialog> element might be a bad choice: it simply cannot work without JavaScript. But if your JavaScript fails for any reason, your event handlers might still work.

Conclusion

HTML is awesome. It's obviously not enough to provide a fully accessible experience to users, as Dave Rupert highlighted two years ago in HTML: the inaccessible parts — even if some of those examples are not accurate anymore, fixed in either browser or ATs.

But not being perfect is probably still better than being neutral, isn't it?

I think that's the kind of thing HTML unveils: at some point, details and summary shall be consistently implemented and exposed to ATs, making ARIA enhancements pointless. Scott's work, from advanced testing to blog posts and filing bugs, are the way forward. Fully implemented patterns in libraries like Kitty Giraudel's a11y-dialog are what alllows maturity and clarify expectations. And as usage increases, browsers and ATs will eventually consider prioritization, in Interop or ARIA-AT.

Let's use, test and help browsers and ATs support those newer HTML elements!

About Gaël Poupard

HTML & CSS nerd living in Nantes (France), I enjoy sun and puns. I created a11y.css and chaarts, am part of the Bootstrap core team and spend most of my time working on accessibility and web quality assurance with Opquast.

Website/Blog: ffoodd.fr
Twitter: @ffoodd_fr
Mastodon: ffoodd@mamot.fr
Github: ffoodd