Home >Web Front-end >CSS Tutorial >Styling HTML with modern CSS
Written by Rob O'Leary✏️
The 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.
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:
The disclosure widget has a label that is always shown and is provided by the
You can also provide multiple elements after the
<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>
There are a few interoperability issues that should be considered when styling the elements. Let's cover the basics before we get into some common use cases.
The
The disclosure widget has two pseudo-elements to style its constituent parts:
In the following sections, I will demonstrate some of the newer, lesser-known ways to customize a disclosure widget.
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.
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 .
The disclosure widget may grow horizontally if the “additional information” content is wider than the
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; }
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
/* open state */ details[open]::details-content { block-size: auto; }
The default style is fairly simple:
Each
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
<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.
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
As mentioned earlier,
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:
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; }
This should work across all browsers. Here’s the CodePen example.
By default, the marker is to the side of the text content of
If we set list-style-position to outside, the marker sits outside of the
<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:
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
<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
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.
You may want to style the "additional information" section of the disclosure widget without leaking styles to the
/* 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
/* open state */ details[open]::details-content { block-size: auto; }
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 */ } }
Notice the difference? There is only one margin at the start of the section. The
and
<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>
Because the in a
<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
Recently, there was a big proposal to help make
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.
The
The Achilles’ heel of
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.
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 with modern CSS. For more information, please follow other related articles on the PHP Chinese website!