Tagged “disclosure”
Full disclosure
Whether I’m thinking about inclusive hiding, hamburger menus or web components one UI pattern I keep revisiting is the disclosure widget. Perhaps it’s because you can use this small pattern to bring together so many other wider aspects of good web development. So for future reference, here’s a braindump of my knowledge and resources on the subject.
A disclosure widget is for collapsing and expanding something. You might alternately describe that as hiding and showing something. The reason we collapse content is to save space. The thinking goes that users have a finite amount of screen estate (and attention) so we might want to reduce the space taken up by secondary content, or finer details, or repeated content so as to push the page’s key messages to the fore and save the user some scrolling. With a disclosure widget we collapse detailed content into a smaller snippet that acts as a button the user can activate to expand the full details (and collapse them again).
Adrian Roselli’s article Disclosure Widgets is a great primer on the available native and custom ARIA options, how to implement them and where each might be appropriate. Adrian’s article helpfully offers that a disclosure widget (the custom ARIA flavour) can be used as a base in order to achieve some other common UI requirements so long as you’re aware there are extra considerations and handle those carefully. Examples include:
- link and disclosure widget navigation
- table with expando rows
- accordion
- hamburger navigation
- highly custom
select
alternatives whenlistbox
is innapropriate because it needs to include items that do not have theoption
role - a toggle-tip
Something Adrian addresses (and I’ve previously written about) is the question around for which collapse/expand use cases we can safely use the native details
element. There’s a lot to mention but since I’d prefer to present a simple heuristic let’s go meta here and use a details
:
Use details
for basic narrative content and panels but otherwise use a DIY disclosure
It’s either a bad idea or at the very least “challenging” to use a native `details` for:
- a hamburger menu
- an accordion
In terms of styling terms it’s tricky to use a `details` for:
- a custom appearance
- animation
The above styling issues are perhaps not insurmountable. It depends on what level of customisation you need.
Note to self: add more detail and links to this section when I get the chance.
I’ve also noticed that Adrian has a handy pen combining code for numerous disclosure widget variations.
Heydon Pickering’s Collapsible sections on Inclusive Components is excellent, and includes consideration of progressive enhancement and an excellent web component version. It’s also oriented toward multiple adjacent sections (an accordion although it doesn’t use that term) and includes fantastic advice regarding:
- appropriate markup including screen reader considerations
- how best to programmatically switch state (such as open/closed) within a web component
- how to make that state accessible via an HTML attribute on the web component (e.g.
<toggle-section open=true>
) - how that attribute is then accessible outside the component, for example to a button and script that collapses and expands all sections simultaneously
There’s my DIY Disclosure widget demo on Codepen. I first created it to use as an example in a talk on Hiding elements on the web, but since then its implementation has taken a few twists and turns. In its latest incarnation I’ve taken some inspiration from the way Manuel Matuzovic’s navigation tutorial uses a template
in the markup to prepare the “hamburger toggle” button.
I’ve also been reflecting on how the hidden
attribute’s boolean nature is ideal for a toggle button in theory – it’s semantic and therefore programattically conveys state – but how hiding with CSS can be more flexible, chiefly because hidden
(like CSS’s display
) is not animatible. If you hide with CSS, you could opt to use visibility: hidden
(perhaps augmented with position
so to avoid taking up space while hidden) which similarly hides from everyone in terms of accessibilty.
As it happens, the first web component I created was a disclosure widget. It could definitely be improved by some tweaks and additions along the lines of Heydon Pickering’s web component mentioned above. I’ll try to do that soon.
Troubleshooting
For some disclosure widget use cases (such as a custom link menu often called a Dropdown) there are a few events that typically should collapse the expanded widget. One is the escape key. Another is when the user moves focus outside the widget. One possible scenario is that the user might activate the trigger button, assess the expanded options and subsequently decide none are suitable and move elsewhere. The act of clicking/tapping elsewhere should collapse the widget. However there’s a challenge. In order for the widget to be able to fire unfocus
so that an event listener can act upon that, it would have to be focused in the first place. And in Safari – unlike other browsers – buttons do not automatically receive focus when activated. (I think Firefox used to be the same but was updated.) The workaround is to set focus manually via focus()
in your click event listener for the trigger button.
Building the main navigation for a website (on web.dev)
learn about semantic HTML, accessibility, and how using ARIA attributes can sometimes do more harm than good.
Alongside all the sound accessibility and hiding-related advice, I also found Manuel’s approach to progressive enhancement interesting. Rather than i) include a hamburger button
directly in the DOM and set its initial state to hidden
; or ii) create the button
element with JavaScript, he instead nests the button
in a template element then clones that element with JavaScript. He later tweeted his rationale for this approach:
If JS doesn't work, the markup inside the template won't be rendered on screen and it's more convenient to prepare the markup upfront instead of using document.createElement().
The ability to prepare complex, JS-dependent component markup upfront in declarative HTML rather than recreating it in JavaScript is a compelling argument for his approach. Especially so if you don’t work in JS framework-based systems therefore your components are not written in JavaScript.
Does the HTML details element solve progressively-enhanced disclosures?
The HTML details
element continues to gain fans and get developers’ juices flowing. Scott Jehl recently tweeted:
I love the details/summary HTML elements. So versatile. My favorite part is being able to show a collapsed state from the start without worrying about potential operability issues if JavaScript fails to run (since its behavior doesn't need it).
Scott goes on to describe how creating disclosure widgets (controls that hide and show stuff) with resilience in mind is so much more difficult when not using <details>
since it can require complex progressive enhancement techniques. At the very least these involve making content available by default in case JavaScript fails, then hiding it when the disclosure widget script loads successfully, ideally without a jarring flash of content in between.
Like Scott says, the <details>
element is different because you can have the content collapsed (hidden) by default without worrying about JavaScript and workarounds since the hidden content can be toggled open natively. That‘s a real superpower… and also makes you wonder: how many different places and different ways might we use this super-element?
GitHub’s use of details
Back in 2019, GitHub caused a flutter by going all-in on <details>
to make various interesting UI elements interactive without JS. Muan has co-created a number of components for Github where <details>
is used to, for example, open menus. They also shared notes from a talk on this subject. And Chris Coyier for one was impressed and intrigued.
Zach Leatherman’s details-utils
I’ve previously noted Zach Leatherman’s details-utils – a great example of using a web component to enhance an existing HTML element, in this case <details>
. The enhancements include:
- animated open/close
- a quantum aspect ideal for responsive design – closed by default on narrow screens, open by default on wide
- and more
And Zach has already used it on the navigation menus on jamstack.org and netlify.com, amongst other use cases.
Notes of caution
- The details element and in-page search by Manuel Matuzovic
- A details element as a burger menu is not accessible on Cloud Four’s blog
- The details and summary elements, again by Scott O’Hara
- Disclosure widgets by Adrian Roselli
- Details and summary are not…
- Details content showing up in find (Ctrl+F)
Alternative approaches
Using a custom disclosure widget put together with JavaScript and ARIA is not the end of the world. In fact I recently tried my hand at a disclosure widget web component and early impressions are that the combination of fast, async ES modules plus native DOM discovery (which you get with web components) might alleviate the “flicker of content” issue I mentioned at the start.
Summing up
I’d been cautious about using details
for more than cases matching its intended usage but had started thinking the time was right to take it further – possibly using Zach’s web component. However based on the findings shared in the above Notes of caution section I’ve decided to stay safe to keep the user experience predictable and accessible. The behaviour when the user does an in-page search, and the current browser inconsistencies in announcing the summary
as an expand/collapse button tell me that a custom JS and ARIA disclosure widget is better for the custom UI cases.
Accessible interactions (on Adactio)
Jeremy Keith takes us through his thought process regarding the choice of link or button
when planning accessible interactive disclosure elements.
A button
is generally a solid choice as it’s built for general interactivity and carries the expectation that when activated, something somewhere happens. However in some cases a link might be appropriate, for example when the trigger and target content are relatively far apart in the DOM and we feel the need move the user to the target / give it focus.
For a typical disclosure pattern where some content is shown/hidden by an adjacent trigger, a button
suits perfectly. The DOM elements are right next to each other and flow into each other so there’s no need to move or focus anything.
However in the case of a log-in link in a navigation menu which—when enhanced by JavaScript—opens a log-in form inside a modal dialogue, a link might be better. In this case you might use an anchor with a fragment identifier (<a href="#login-modal">Log in</a>
) pointing to a login-form far away at the bottom of the page. This simple baseline will work if JavaScript is unavailable or fails, however when JavaScript is available we can intercept the link’s default behaviour and enhance things. Furthermore because the expectation with links is that you’ll go somewhere and modal dialogues are kinda like faux pages, the link feels appropriate.
While not explicit in the article, another thing I take from this is that by structuring your no-JavaScript experience well, this will help you make appropriate decisions when considering the with-JavaScript experience. There’s a kind of virtuous circle there.
Building an accessible show/hide disclosure component with vanilla JS (Go Make Things)
A disclosure component is the formal name for the pattern where you click a button to reveal or hide content. This includes things like a “show more/show less” interaction for some descriptive text below a YouTube video, or a hamburger menu that reveals and hides when you click it.
Chris’s article provides an accessible means of showing and hiding content at the press of a button when the native HTML details
element isn’t suitable.
Details and summary for no-JavaScript disclosure widgets
The fairly-recently added <details>
element is a great, native HTML way to toggle content visibility.
See all tags.