Home >Web Front-end >CSS Tutorial >Styling HTML

and with modern CSS

Styling HTML
and with modern CSS

DDD
DDDOriginal
2025-01-03 05:54:40837browse

Written by Rob O'Leary✏️

The

and HTML elements, collectively referred to as a disclosure widget, are not easy to style. People often make their own version with a custom component because of the limitations. However, as CSS has evolved, these elements have gotten easier to customize. In this article, I will cover how you can customize the appearance and behavior of a disclosure widget.

How do
and work together?

is an HTML element that creates a disclosure widget in which additional information is hidden. A disclosure widget is typically presented as a triangular marker accompanied by some text.

When the user clicks on the widget or focuses on it and presses the space bar, it opens and reveals additional information. The triangle marker points down to indicate that it is in an open state:

Styling HTML <details> and <summary> with modern CSS

Styling HTML <details> and <summary> with modern CSS

The disclosure widget has a label that is always shown and is provided by the

element. This is the first child. If it is omitted, a default label is provided by the browser. Usually, it will say "details":

Styling HTML <details> and <summary> with modern CSS

You can also provide multiple elements after the

element to represent the additional information:

<details>
  <summary>Do you want to know more?</summary>
  <h3>Additional info</h3>
  <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
</details>

Styling
and

There are a few interoperability issues that should be considered when styling the

and elements. Let's cover the basics before we get into some common use cases.

The

element is similar to a [
  • ](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li) element because its default style includes display: list-item. Therefore, it supports the [list-style](https://developer.mozilla.org/docs/Web/CSS/list-style) shorthand property and its longhand properties. The browser support for the list-style properties is quite good, but Safari is still lagging.

    The disclosure widget has two pseudo-elements to style its constituent parts:

    1. The ::marker pseudo-element: Represents the triangular marker that sits at the beginning of . The styling story for this is a bit complicated. We are limited to a small set of CSS properties. Browser support is good for ::marker, but Safari doesn’t currently support the complete set of properties. I will discuss this in more detail in the “Styling the summary marker” section of this article
    2. The ::details-content pseudo-element: Represents the “additional information” of
      . This is a recent addition, so browser support is currently limited to Chrome

    Styling HTML <details> and <summary> with modern CSS

    In the following sections, I will demonstrate some of the newer, lesser-known ways to customize a disclosure widget.

    Animating the open and close actions

    When you open a disclosure widget, it snaps open instantly. Blink, and you will miss it!

    It is preferable to transition from one state to another in a more gradual way to show the user the impact of their action. Can we add a transition animation to the opening and closing actions of a disclosure widget? In short, yes!

    To animate this, we want the height of the hidden content to transition from zero to its final height. The default value of the height property is auto, which leaves it to the browser to calculate the height based on the content. Animating to a value of auto was not possible in CSS until the addition of the [interpolate-size](https://nerdy.dev/interpolate-size) property. While browser support is a bit limited for the newer CSS features we need to use — chiefly interpolate-size and ::details-content — this is a great example of a progressive enhancement. It will currently work in Chrome!

    Here's a CodePen example of the animation.

    How does the disclosure animation work?

    First, we add interpolate-size so we can transition to a height of auto:

    <details>
      <summary>Do you want to know more?</summary>
      <h3>Additional info</h3>
      <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
    </details>
    

    Next, we want to describe the closed style. We want the “additional info” content to have a height of zero and ensure that no content is visible, i.e., we want to prevent overflow.

    We use the ::details-content pseudo-element to target the hidden content. I use the block-size property rather than height because it's a good habit to use logical properties. We need to include content-visibility in the transition because the browser sets content-visibility: hidden on the content when it is in a closed state — the closing animation will not work without including it:

    <details>
      <summary>Do you want to know more?</summary>
      <h3>Additional info</h3>
      <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
    </details>
    

    The animation still won’t work as expected because the content-visibility property is a discrete animated property. This means that there is no interpolation; the browser will flip between the two values so that the transitioned content is shown for the entire animation duration. We don't want this.

    If we include transition-behavior: allow-discrete;, the value flips at the very end of the animation, so we get our gradual transition.

    Also, we get content overflow by setting the block-size to 0 when the disclosure widget is in an intermediate state. We show most of the content as it opens. To prevent this from happening, we add overflow: hidden.

    Lastly, we add the style for the open state. We want the final state to have a size of auto:

    details {
        interpolate-size: allow-keywords;
    }
    

    Those are the broad strokes. If you would prefer a more detailed video explanation, check out Kevin Powell's walkthrough for how to animate

    and .

    Are there any other considerations when animating a disclosure widget?

    The disclosure widget may grow horizontally if the “additional information” content is wider than the

    content. That may cause an unwanted layout shift. In that case, you may want to set a width on
    .

    Like any animation, you should consider users who are sensitive to motion. You can use the prefers-reduced-motion media query to cater to that scenario:

    /* closed state */
    details::details-content {
      block-size: 0;
    
      transition: content-visibility, block-size;
      transition-duration: 750ms;
    
      transition-behavior: allow-discrete;
      overflow: hidden;
    }
    

    Implementing an exclusive
    group (exclusive accordion)

    A common UI pattern is an accordion component. It consists of a stack of disclosure widgets that can be expanded to reveal their content. To implement this pattern, you just need multiple consecutive

    elements. You can style them to visually indicate that they belong together:

    /* open state */
    details[open]::details-content {
      block-size: auto;
    }
    

    The default style is fairly simple:

    Styling HTML <details> and <summary> with modern CSS

    Each

    occupies its own line. They are positioned close together (no margin or padding) and are perceived as a group because of their proximity. If you want to emphasize that they are grouped together, you could add a border and give them the same background styles as shown in the example below:

    A variation of this pattern is to make the accordion exclusive so that only one of the disclosure widgets can be opened at a time. As soon as one is opened, the browser will close the other. You can create exclusive groups through the name attribute of

    . Having the same name forms a semantic group:

    <details>
      <summary>Do you want to know more?</summary>
      <h3>Additional info</h3>
      <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
    </details>
    

    Before using exclusive accordions, consider if it is helpful to users. If users are likely to want to consume more of the information, this will require them to open items often, which can be frustrating.

    This feature is currently supported in all modern browsers so you can use it right away.

    Styling the summary marker

    A disclosure widget is typically presented with a small triangular marker beside it. In this section, we'll cover the process of styling this marker.

    The marker is associated with the

    element. The addition of the [::marker](https://developer.mozilla.org/docs/Web/CSS/::marker) pseudo-element means that we can style the marker box directly. However, we are limited to a small set of CSS properties:

    • All of the font properties
    • color
    • white-space
    • text-combine-upright, [unicode-bidi](https://developer.mozilla.org/en-US/docs/Web/CSS/unicode-bidi), and direction properties
    • content
    • All animation and transition properties

    As mentioned earlier,

    is similar to a [
  • ](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li); it supports the list-style shorthand property and its longhand properties. While this might sound a bit hodge-podge, it will be easier to understand the styling options with some examples.

    Before jumping into examples, a quick word on browser support. At the time of writing, Safari is the only major browser that doesn’t fully support styling the marker:

    • Safari support is currently limited to styling the color and font-size properties of the ::marker pseudo-element. Safari supports the non-standard pseudo-element ::-webkit-details-marker
    • Safari doesn’t support styling the list-style properties at all. See CanIUse for reference

    Changing the color and size of a marker

    Say we wanted to change the color of the triangular marker to red and make it 50% larger. We can do the following:

    details {
        interpolate-size: allow-keywords;
    }
    

    Styling HTML <details> and <summary> with modern CSS

    This should work across all browsers. Here’s the CodePen example.

    Adjusting the spacing of the marker

    By default, the marker is to the side of the text content of

    and they are in the same bounding box. The list-style-position is set to inside. When it is an open state, the “additional information” is directly underneath the marker. Perhaps you want to change the spacing and alignment of this:

    Styling HTML <details> and <summary> with modern CSS

    If we set list-style-position to outside, the marker sits outside of the

    bounding box. This enables us to adjust the space between the summary text and the marker:

    <details>
      <summary>Do you want to know more?</summary>
      <h3>Additional info</h3>
      <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
    </details>
    

    You can see this in the second instance in the screenshot above.

    Here is a CodePen of this example:

    Changing the marker text/image

    If you want to change the content of the marker, you can use the content property of the ::marker pseudo-element. Based on your preferences, you can set it to text. For my example, I used the zipper mouth emoji for the closed state and the open mouth emoji for the open state:

    details {
        interpolate-size: allow-keywords;
    }
    

    To use an image for the marker, you can use the content property of the ::marker pseudo-element, or the list-style-image property of

    :

    /* closed state */
    details::details-content {
      block-size: 0;
    
      transition: content-visibility, block-size;
      transition-duration: 750ms;
    
      transition-behavior: allow-discrete;
      overflow: hidden;
    }
    

    In the following example, we are using two arrow icons from Material Symbols for the marker. The right-facing arrow is for the closed state, and the down-facing arrow is for the open state:

    These examples will work as expected in Chrome and Firefox, but Safari will ignore the styles. You can approach this as a progressive enhancement and call it a day. But if you want the same appearance across all browsers, you can hide the marker and then add your own image as a stand-in. This gives you more freedom:

    /* open state */
    details[open]::details-content {
      block-size: auto;
    }
    

    You can visually indicate the state using a new marker icon, such as an inline image or via pseudo-elements. The

    already (mostly) indicates the expand/collapse state. So if you use an inline graphic, it should be treated as decorative. An empty alt attribute does this:

    <details>
      <summary>Do you want to know more?</summary>
      <h3>Additional info</h3>
      <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
    </details>
    

    You can choose to position the marker at the end of

    , too, if you wish:

    details {
        interpolate-size: allow-keywords;
    }
    

    However, it is important to note that hiding the marker causes accessibility issues with screen readers. Firefox, VoiceOver, JAWS, and NVDA all have an issue with consistently announcing the toggled state of the disclosure widget if the marker is removed. Unfortunately, the style is tied to the state. It is preferable to avoid doing this.

    Styling the "additional information" section of

    You may want to style the "additional information" section of the disclosure widget without leaking styles to the

    . Because you can have a variable number of elements inside a
    , it would be nice to have a catch-all rule:

    /* closed state */
    details::details-content {
      block-size: 0;
    
      transition: content-visibility, block-size;
      transition-duration: 750ms;
    
      transition-behavior: allow-discrete;
      overflow: hidden;
    }
    

    My go-to is to exclude the

    element using the :not() function. Just keep in mind that this targets each element rather than the content as a single section!

    /* open state */
    details[open]::details-content {
      block-size: auto;
    }
    

    Styling HTML <details> and <summary> with modern CSS

    Alternatively, you can use the ::details-content pseudo-element, which targets the entire section. This is why you want to use this for animating the opening and closing state transitions:

    >@media (prefers-reduced-motion) {
      /* styles to apply if a user's device settings are set to reduced motion */
    
       details::details-content {
          transition-duration: 0.8s; /* slower speed */
       }
    }
    

    Styling HTML <details> and <summary> with modern CSS

    Notice the difference? There is only one margin at the start of the section. The

    and

    do not have margins. The downside of using the ::details-content pseudo-element is that browser support is currently limited to Chrome.

    Common mistakes when styling disclosure widgets

    • Historically, it wasn't possible to change the display type of the
      element. This restriction has been relaxed in Chrome
    • Be careful changing the display type of . The default is display: list-item;; if you change it to display: block;, it may result in the marker being hidden in some browsers. This was an issue in Firefox:
    <details>
        <summary>Payment Options</summary>
        <p>...</p>
    </details>
    <details>
        <summary>Personalise your PIN</summary>
        <p>...</p>
    </details>
    <details>
        <summary>How can I add an additional cardholder to my Platinum Mastercard</summary>
        <p>...</p>
    </details>
    
    • You cannot nest
    • Because the

      element has a default ARIA role of button, it strips all roles from child elements. Therefore, if you want to have a heading like a

      in a , assistive technologies such as screen readers won’t recognize it as a heading. Try to avoid this pattern:

      <details>
        <summary>Do you want to know more?</summary>
        <h3>Additional info</h3>
        <p>The average human head weighs around 10 to 11 pounds (approximately 4.5 to 5 kg).</p>
      </details>
      

    • Hiding the marker causes accessibility issues with some screen readers. Firefox, VoiceOver, JAWS, and NVDA all have an issue with consistently announcing the toggled state of the disclosure widget if the marker is removed

    Are there more changes to come?

    Recently, there was a big proposal to help make

    more customizable and interoperable between browsers. Phase 1 includes some of what I covered in this article:

    1. Remove CSS display property restrictions so you can use other display types like flex and grid
    2. Specify the structure of the shadow tree more clearly. This should help with interoperability with Flexbox and CSS Grid
    3. Add a ::details-content pseudo-element to address the second slot so that a container for the "additional information" in the
      element can be styled

    The exciting news is items 1 and 3 in the list above have shipped in Chrome 131 (as of November 2024). The next phase should be tackling improving the styling of the marker. Additionally, there is a set of related changes that will help improve the ability to animate these elements.

    Conclusion

    The

    HTML element has gotten much easier to customize in CSS. You can now make exclusive groups with full browser support, animate the transition of opening/closing states as a progressive enhancement, and perform simple styling of the marker.

    The Achilles’ heel of

    is the styling of the marker. The good news is that there is an active proposal that addresses this and some other pain points. This should remove all of the stumbling blocks when using
    . In the near future, you won’t need to write your own disclosure widget or use a third-party web component! ?


    Is your frontend hogging your users' CPU?

    As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.

    Styling HTML <details> and <summary> with modern CSS

    LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

    Modernize how you debug web and mobile apps — start monitoring for free.

    The above is the detailed content of Styling HTML

    and with modern CSS. For more information, please follow other related articles on the PHP Chinese website!

    Statement:
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn