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!
31 Comments
@LoukilAymen
And Twitter doesn't correctly displays your post title
@zachleat
Fix is building—thanks! (although Twitter’s cache might be sticky for a bit)
@bregtdl
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.
@peterhironaka
I just found out about details not too long ago 🤟🏽 so good
@zachleat
Thanks Peter!
@mfairchild365
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?
@zachleat
Ah, very fair point. I’m going to remove that example for now until I get a better story there—thank you!
@SteveALee
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.
@mfairchild365
Good point. Here is some evidence of that a11ysupport.io/tests/tech__ht… (heading semantics are stripped in JAWS and a couple other combinations)
@SteveALee
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.
@SteveALee
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.
@tomayac
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`.
@chelo_xl
Why not <details is=enhanced>?
@tomayac
Safari doesn't support it.
@andresrico86
Ty brother
@yatil
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.
@zachleat
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
@tomayac
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 ! 🙏
@nhoizey
Are you also in charge of content, and fan of X-Men? 😅
@zachleat
No and yes 😇 Loved the old cartoons growing up
@nhoizey
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?
@zachleat
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!
@nhoizey
😂
@gregwhitworth
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?
@zachleat
Whew, good question. I’m super focused on Eleventy open source right now and I’m wary to split time there
@gregwhitworth
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!