Add Responsive-Friendly Enhancements to <details> with <details-utils>
I use <details>. I use <details> a lot. It is one of my favorite HTML elements.
Over time I’ve collected a bunch of add-on utilities to enhance <details> with new features and functionality. They’ve been super useful in a bunch of long-standing production implementations at Netlify:
- 11ty.dev Eleventy Docs sidebar menu
- netlify.com masthead navigation
- The shared masthead navigation (across various tech-stacks) on Netlify Community (Vue/Eleventy), Remotely Interesting Podcast (Eleventy), Netlify Explorers (Next.js), Netlify Swag (Gatsby.js), and Netlify Answers (Discourse).
- jamstack.org masthead navigation
- Your Year on Netlify wizard steps
- (and probably more?)
I’ve decided to finally package those <details> helpers up and formally release them as a web component!
<details-utils>
<details-utils>
	<details>…</details>
	<!-- you can have one or more <details> elements in here -->
</details-utils>At time of writing, this web component adds five new responsive-friendly enhancements to one or more <details> elements nestled inside:
- Force open/closed
- Click outside to close
- Close on esc
- Animate open/closed
- Toggle root element class
Force open/closed
In this example, the <details> is forced open when viewport is wider than 48em.
<details-utils force-open="(min-width: 48em)" force-restore>
	<details open>…</details>
</details-utils>I’ve gotten a lot of mileage out of the above example, specifically to drive navigation that is always visible at a certain breakpoint (think a collapsible menu at small viewport versus sidebar, e.g. 11ty.dev/docs/).
Alternatively, force-close is also available. The optional force-restore attribute will restore previous state when the force-open or force-close media queries do not match.
The media query is optional, and using it as a bare attribute allows control of the state pre and post JavaScript.
<!-- closed without JS, open with JS -->
<details-utils force-open>
	<details>…</details>
</details-utils>
<!-- open without JS, closed with JS -->
<details-utils force-close>
	<details open>…</details>
</details-utils>Click outside to close
If you click anywhere on the document (outside of the <details> content), the <details> will be closed. This is useful when you want to absolutely position the <details> content (maybe to make a little custom dropdown 😱)
<details-utils close-click-outside>
	<details>…</details>
</details-utils>You can scope this with a media query as well:
<details-utils close-click-outside="(min-width: 48em)">
	<details>…</details>
</details-utils>Add your own bonus close button inside of the content (to complement <summary>):
/* Hide button without JS */
details-utils:not(:defined) [data-du-close-click] {
	display: none;
}<details-utils close-click-outside>
	<details id="my-details">
		<summary>…</summary>
		<button type="button" aria-controls="my-details" data-du-close-click>Close</button>
	</details>
</details-utils>Close on esc
Closes the <details> when the esc key is hit on the keyboard. Media query is optional.
<details-utils close-esc="(max-width: 767px)">
	<details>…</details>
</details-utils>Animate open/close
<details-utils animate>
	<details>…</details>
</details-utils>Animates the height of the content when opening and closing the <details>. Ignored automatically if (prefers-reduced-motion) is detected.
Just a full disclosure, the configuration around this one is pretty limited (re: easing and timing). Also this doesn’t support media query scoping yet (not for any technical reason, just haven’t run into this use case yet). Open to contributions here!
Toggle class on root element
<details-utils toggle-document-class="my-class-name">
	<details>…</details>
</details-utils>Adds a class to your <html> element when the <details> is open and removes it when the <details> is closed.
Enjoy!
Wiring up and combining each of these enhancements to <details> really can go a long way in building a lot of complex user interface elements in a pretty straightforward way. In my humble opinion, the super long list of things I’ve built using this is proof of that. I hope you can get some useful mileage out of them too!






































































































32 Comments
Aymen Loukil
And Twitter doesn't correctly displays your post title
Zach Leatherman
Fix is building—thanks! (although Twitter’s cache might be sticky for a bit)
Bregt
Cool. I haven't used this element until recently, but notice issues with a11y regarding headings in <summary> etc. It put me off, but then saw some examples on Github and now yours. I should reconsider.
Peter Hironaka
I just found out about details not too long ago 🤟🏽 so good
Zach Leatherman
Thanks Peter!
Michael 🧐 Fairchild
This is awesome! Thank you! The Toggle Document Class example is rendered as a modal dialog but is missing required modal dialog semantics for #a11y, and content behind the open modal is still findable/interactive with keyboard/sr, which fails several WCAG SC. Thoughts?
Zach Leatherman
Ah, very fair point. I’m going to remove that example for now until I get a better story there—thank you!
Steve Lee
FYI, doesn't effect your code but we recently found out the the JAWS screen reader strips out all tags from the Summary, mapping it to text. That's actually one response to some annoying spec problems. Also avoids some confusing possibilities allowed.
Michael 🧐 Fairchild
Good point. Here is some evidence of that a11ysupport.io/tests/tech__ht… (heading semantics are stripped in JAWS and a couple other combinations)
Steve Lee
The spec allows links but then you end up with two actions; expand/collapse and navigate, which is confusing visually and semantically unavailable on the a11y APIS :( There's an issue with FS.
Steve Lee
That said, it's one of the few "controls" that can be nicely styled with CSS :) Just as well as the browser defaults are rather naff imho.
Thomas Steiner
Oh, nice! The “Tweak” menu of SVGcode (svgco.de) is essentially this, but it hides the outer `summary` when the viewport is large enough. Resize the window to test. Your force-open behavior also reminds me of @briankardell’s `spicy-section`.
CheloXL
Why not <details is=enhanced>?
Thomas Steiner
Safari doesn't support it.
Andrew Rico
Ty brother
Eric Eggert
I have no idea how web components work… But could that not just be data attributes (or classes)? *sigh* I really need to catch up.
Zach Leatherman
The huge benefit of web components (imo, and especially from a jQuery mindset) is that you don’t need to do any queryselectoring to find and init the elements (the platform gives you that for free). So yes, they could be data attrs but then I’d have to find and init them manually
Thomas Steiner
That's right, but it's extra code. It would be wonderful if it were supported directly, but @webkit has decided to WONTFIX the bug bugs.webkit.org/show_bug.cgi?i… unfortunately.
David Larlet
@nhoizey @accessiblestef oui je suis ça de près 👍 Le sautillement ça doit être ouverture combinée au scroll ?
Nicolas Hoizey
@dav oui, c’est ça, le problèmene se poserait pas sans scroll.@accessiblestef
David Larlet
@nhoizey merci pour le retour 🙇
Nicolas Hoizey
@dav de rien, merci pour ce site ! 🙏
Nicolas Hoizey
Are you also in charge of content, and fan of X-Men? 😅
Zach Leatherman
No and yes 😇 Loved the old cartoons growing up
Nicolas Hoizey
Just saw 2 movies, I guess I'll have to see more when Marvel starts mixing them with the Avengers in the coming Multiverse… 😅 I guess you can tell the right person there's a typo?
Zach Leatherman
ahaha thank you for being very clear—I somehow did not even connect that was a typo 😅 I’ll let the team know, thank you!
Nicolas Hoizey
😂
Greg Whitworth
How interested would Netlify be to get engaged in Open UI given your creation of web components and driving the direction/utilization in prod of these components for feedback?
Zach Leatherman
Whew, good question. I’m super focused on Eleventy open source right now and I’m wary to split time there
Greg Whitworth
definitely didn't mean you specifically, I know @soMelanieSaid is there but unsure who owns all of what. So if this component maps to <selectmenu> it could be tested or even converted to oui-selectmenu
Jon
@DavidDarnes @zachleat I thought I'd seen this somewhere - should have known to check Zach's stuff!
caleb
www.zachleat.com/web/details-... is another great example <3