More options for styling <details>

Published: Nov 6, 2024

From Chrome 131 you have more options to style the structure of <details> and <summary> elements. You can now use these elements when building disclosure or accordion widgets.

In particular, the changes introduced in Chrome 131 enable the use of the display property on these elements, and add a ::details-content pseudo-element to style the part that expands and collapses.

Browser Support

  • Chrome: 131.
  • Edge: 131.
  • Firefox: not supported.
  • Safari: not supported.

Setting display on the <details> element

Historically it wasn't possible to change the display type of the <details> element. This restriction has now been relaxed, allowing you to, for example, use grid or flex layouts on the <details> element.

In the following example the exclusive accordion consists of several <details> elements placed side-by-side. When expanding one of the <details> elements, its content is placed next to the <summary>.

Demo

Recording

Recording of https://codepen.io/web-dot-dev/pen/VwoBQjY in Chrome 131

This is achieved by using a flex layout on the <details> element, using the following CSS:

details {
  display: flex;
  flex-direction: row;
}

Also allowed are other display values such as grid.

A note on using display: inline

A display value that can have an unexpected outcome is inline. Not because it doesn't work but because of HTML parser limitations.

When placing a <details> element inside a paragraph it forces the HTML parser to first close the open paragraph, as defined in section 13.2.6.4.7 of the HTML Standard:

A start tag whose tag name is one of: "address", "article", "aside", "blockquote", "center", "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "main", "menu", "nav", "ol", "p", "search", "section", "summary", "ul"

If the stack of open elements has a p element in button scope, then close a p element. Insert an HTML element for the token.

As a result, the <details> flows in the block direction, regardless of you having set display: inline.

For example, the following markup

<p>Hello <details>…</details> world</p>

Becomes this after parsing:

<p>Hello </p><details>…</details> world<p></p>

You can see for yourself in this demo by inspecting the parsed markup using Chrome DevTools.

Note that this only applies to nesting <details> inside a <p>. Using display: inline on a <details> inside a <div> works fine.

The ::details-content pseudo

In browsers, the <details> element is implemented using Shadow DOM. It contains one <slot> for the summary (with a default summary child) and a <slot> for all remaining content, meaning all children of the <details> element except the <summary> element.

<details>
  ↳ #shadow-root (user-agent)
      <slot id="details-summary">
        <summary>Details</summary>
        <!-- The summary goes here -->
      </slot>
      <slot id="details-content">
        <!-- All content goes here -->
      </slot>
</details>

In addition to using more display types on <details>, the content slot can now be targeted using the ::details-content pseudo-element. You can use this pseudo to style the container that wraps the content of the <details> element.

details::details-content {
  border: 5px dashed hotpink;
}

To only apply the set style when the <details> element is in the open state, prepend the [open] selector to it.

[open]::details-content {
  border: 5px dashed hotpink;
}

It is recommended to only apply styling to the ::details-content pseudo when the <details> element is in the [open] state.

Demo

Recording

Recording of https://codepen.io/web-dot-dev/pen/oNKMEYv in Chrome 131

The display type of ::details-content is set to block in the UA style sheet, whereas before it used to be display: contents. This change might work against you in some cases, such as disclosed content relying on height: 100%. If this is an issue for you, you can work around this by setting the display type back to contents, like so: details[open]::details-content { display: contents; }.

Animating the ::details-content pseudo

You can animate the content of the <details> element as it expands. In the following example, the width animates from 0px to 300px.

::details-content {
  transition: width 0.5s ease, content-visibility 0.5s ease allow-discrete;
  width: 0;
}

[open]::details-content {
  width: 300px;
}

Besides transitioning the width, the content-visibility property also needs to transition. This is because its value changes between the unopened and opened state, as defined in the User-Agent style sheet. Because that property is a discretely animatable property, you need the allow-discrete keyword to make it work.

Added to the exclusive accordion demo shared before, the result becomes this:

Demo

Recording

Recording of https://codepen.io/web-dot-dev/pen/XWvBZNo in Chrome 131

The height can also be animated. To animate to height: auto, you need to use interpolate-size or calc-size(). Additionally, to prevent the content from bleeding out of the ::details-content pseudo, apply overflow: clip to it.

::details-content {
    transition: height 0.5s ease, content-visibility 0.5s ease allow-discrete;
    height: 0;
    overflow: clip;
}

/* Browser supports interpolate-size */
@supports (interpolate-size: allow-keywords) {
    :root {
        interpolate-size: allow-keywords;
    }

    [open]::details-content {
        height: auto;
    }
}

/* Fallback for browsers with no interpolate-size support */
@supports not (interpolate-size: allow-keywords) {
    [open]::details-content {
        height: 150px;
        overflow-y: scroll; /* In case the contents should be taller than 150px */
    }
}

You can see the code in action in the following demo, inspired by Material UI's accordion. The content of each <details> element nicely animates.

Demo

Recording

Recording of https://codepen.io/web-dot-dev/pen/ExqpQZM in Chrome 131

In browsers with no support for ::details-content the component still works fine. The only thing visitors don't get to see is the animation.

Feature detection

To feature detect support for the ::details-content pseudo in CSS, use the following snippet.

@supports selector(::details-content) {
  
}

You can also use this detection as a telltale check to figure out if the browser your visitor is using supports the extra display values or not.

Accessibility considerations

The introduction of the ::details-content pseudo-element and the ability to change the display type does not have an impact on the accessibility of the <details> element.

Like before, at least in Chromium based browsers and as per HTML Standard, the <details> element is searchable and automatically expands when the browser tries to scroll to its hidden contents in response to find-in-page, ScrollToTextFragment, and element fragment navigation. This does not change.

However, before using exclusive accordions, consider if it's helpful or harmful to users. While using an exclusive accordion reduces the amount of visual space content occupies, users may have to open many items to consume all the information. This may frustrate users who want to look at multiple items at the same time.

What about styling the marker?

Currently styling of the list marker is not interoperable as there are two different approaches, one taken by Gecko and (current) Chromium, and another taken by WebKit (which was previously shared with Chromium).

Once the feature is interoperable, our aim is to give you better control over how to style the marker.

More demos

To close off, here are some more demos for you to check out. They all use ::details-content.

UIKit Accordion

Demo

Recording

Recording of https://codepen.io/web-dot-dev/pen/rNXrJyQ in Chrome 131

This demo is built after the UIKit Accordion. The code is practically the same as the Material UI accordion that was shared before.

Partially opened disclosure widget

Demo

Recording

Recording of https://codepen.io/web-dot-dev/pen/PoMBQmW in Chrome 131

This demo features a partially opened disclosure widget whose content is already visible on the screen. To achieve this, the content-visibility is always set to visible. The height gets animated using calc-size() because there is a calculation involved.

::details-content {
  content-visibility: visible; /* Make it always visible */
    
  transition: height 0.5s ease;
  height: 150px;
  overflow: clip;
}

[open]::details-content {
  height: calc-size(auto, size + 0.5rem); /* calc-size because we need to add a length */
}

For styling convenience the content is wrapped in a wrapper div. The wrapper div gets the layout styles such as padding applied and the ::details-content pseudo is animated.