Home Flight manual By tags Ramblings Colophon RSS

A <details> drop-down without JavaScript

Table of contents

The very recent1 <details> tag (and its <summary> companion) lets us create collapsible content in plain HTML. In plain HTML! Though you're likely to want to add some CSS styling to make it look remotely palatable, JavaScript may very well be kept at bay entirely. Let's explore a bit and see what can be done with it.

Obligatory TL;DR

File
Font style
Alignment

A menu bar in ~20 lines of CSS, fully navigable by keyboard, Web crawlers and screen readers, without a hint of JavaScript in sight.

I'll section this article in three parts, where we'll go over the templating aspects, the styling quirks and possibilities, and finally a practical example, in that order.

The DOM markup logic

In the relevant MDN docs, the <details> element is introduced as such:

The <details> HTML element creates a disclosure widget in which information is visible only when the widget is toggled into an open state. A summary or label must be provided using the <summary> element.

Consider the following HTML snippet:

<details>
   This is the collapsible content.  It can include text,
   images—anything you want.
</details>

I do my best to render it below as "default" as it gets, except for some background colour and centring, to keep the article comfortable:

This is the collapsible content. It can include text, images—anything you want.

Yeah, that's pretty bare—but, crucially, functional. You may click on the label to collapse and expand the piece of content it's encapsulating.

The <summary> in the <details>

Something's iffy, however: just like the devil, wasn't the <summary> prescribed to belong as well in the <details>? The MDN quote above mentions that a label must be provided using the <summary> element, yet we didn't include one in the previous snippet.

To me today, that label reads "Details". While being fairly standard across Web browsers in an English-language context (as far as I could tell), that value still is left to the discretion of the user agent (i.e., the client's Web browser).

I went and tracked down the specification documents at the WHATWG to ascertain either way, and sure enough, it is fairly ambiguous, in stating both that the Content Model of <details> shall include:

One <summary> element followed by Flow Content.

Yet also (and conversely), that:

The first <summary> element child of the element, if any, represents the summary or legend of the details. If there is no child summary element, the user agent should provide its own legend (e.g. "Details").

In any case, there isn't a single example there of a <details> element without its <summary> "firstborn" (> summary:first-child), and for what it's worth, the W3C's Markup Validation Service is aligned to the WHATWG specification in disallowing the omission of the <summary> child element in <details>.

I conclude that we'd do well to always include a <summary> element as the first child of our <details>, to ensure consistent behaviour across user agents—we'll likely wish to have some control over the label anyway.

In summary, you may customise the label of a <details> element by providing it with a <summary> child:

<details name="linked">
   <summary>Woof!</summary>
   <span style="font-size: 3em;">🐶</span>
</details>

Woof! 🐶

The [open] attribute and the [name] linking

I lingered far too long on the <summary> element, so I'll keep this one straight to the point. There's only a couple more things to know about the DOM structure:

<details name="linked" open> <!-- note this <details> being [open] -->
   <summary>Quack!</summary>
   <span style="font-size: 3em;">🦆</span>
</details>
<details name="linked">
   <summary>Moo!</summary>
   <span style="font-size: 3em;">🐮</span>
</details>
<details name="linked">
   <summary>Meow!</summary>
   <span style="font-size: 3em;">🐱</span>
</details>
Its [open] attribute makes the 🦆 start out expanded

Quack! 🦆
Moo! 🐮
Meow! 🐱

That's it! There isn't much more to the <details> from the perspective of its mark-up. Let's get on now with the matter that is becoming most pressing, I'm sure: its styling possibilities.

The styling guidebook

First thing first: you don't need to keep that odd triangle marker that the user agent places before the <summary> text. You can style it away or replace it with something more fitting to your design, by targeting its ::marker pseudo-element, in either of its two possible states, via the :open pseudo-class on the parent <details>:

<style type="text/css">
   details      summary::marker { content: '😶‍🌫️ ' }
   details:open summary::marker { content: '👻 ' }
</style>
<details>
   <summary>Peekaboo?</summary>
   I see you!
</details>

Peekaboo? I see you!

In practice, support for the :open pseudo-class on the <details> and for the ::marker pseudo-element on <summary> is not quite universal yet: Safari is the notable bad apple on that front—ha!

In any case, bending that marker to your specific will would often entail disabling it entirely and working rather with your own ::before and ::after additions—just as you would with <li> elements in lists:

<style type="text/css">
   details summary { cursor: pointer }
   details summary::marker { content: '' }
   details::before {
      content: 'New';
      font-size: .8em;
   }
   details:not(:open)::before { color: green }
   details:open::before { content: 'Read' }
   details:open summary {
      text-decoration: line-through;
      color: grey;
   }
   summary + section { padding-left: 1.5em; border-left: 2px solid lightgrey; }
</style>
<details>
   <summary>Subject: 🎉💰 URGENT: You've Won $1,000,000 Today! 💰🎉</summary>
   <section>
      <h3>Congratulations, lucky friend!</h3>
      Click HERE to claim your prize NOW! 💷💶💵💴
   </section>
</details>

Subject: 🎉💰 URGENT: You've Won $1,000,000 Today! 💰🎉

Congratulations, lucky friend!

Click HERE to claim your prize NOW! 💷💶💵💴

In any case, this is still workable, without even forgoing Safari users! Without further ado, onto the pièce de résistance we go.

A practical drop-down example

Let us, arrive at the crown jewel of this article: a drop-down menu bar, entirely in HTML and CSS:

<style type="text/css">
   #menu              { display: flex }
   #menu details      { position: relative }
   #menu button:hover,  #menu details summary:hover  { background-color: grey }
   #menu button:active, #menu details summary:active { background-color: lightgrey }
   #menu button,        #menu details summary {
      padding: .5rem 1rem;
      cursor:  pointer;
      /* Add tweaks to align the style of button and summary */
   }
   #menu details > :not(summary) {
      display:        flex;
      flex-direction: column;
      position:       absolute;
      min-width:      100%;
      z-index:        1;
   }
</style>

<div id="menu">
   <details name="menu">
      <summary>File</summary>
      <div>
         <button>Open</button>
         <button>Save</button>
         <button>Exit</button>
      </div>
   </details>
   <button>Undo</button>
   <button>Redo</button>
   <details name="menu">
      <summary>Font style</summary>
      <div>
         <button>Normal</button>
         <button style="font-weight: bold;">Bold</button>
         <button style="font-style: italic;">Italic</button>
      </div>
   </details>
   <details name="menu">
      <summary>Alignment</summary>
      <div>
         <button style="text-align: left;">Left</button>
         <button style="text-align: center;">Center</button>
         <button style="text-align: right;">Right</button>
      </div>
   </details>
</div>

Yep, it really is that simple. Some ~20 lines of CSS, and a stunningly sane HTML template. Here's a live rendering of the above—with adjustments to the colours to fit this blog's scheme:

File
Font style
Alignment

You could consider adding user-select: none to the <summary> elements to prevent accidental text selection when the user may be frantically clicking around, especially in these sorts demonstrations.

Of course this menu I suggest would rely on buttons that cannot work without JavaScript, but you'll note that I use essentially the same template on this very blog to collapse the navigation items on mobile devices, where space comes at a premium.

Using bare <a> links and CSS media queries, I can have a fully functional navigation menu that works responsively across all devices, without a single line of JavaScript. And now, so can you!

  1. Available across browsers since 2020.