Tag: Details

Two Issues Styling the Details Element and How to Solve Them

In the not-too-distant past, even basic accordion-like interactions required JavaScript event listeners or some CSS… trickery. And, depending on the solution used, editing the underlying HTML could get complicated.

Now, the <details> and <summary> elements (which combine to form what’s called a “disclosure widget”) have made creation and maintenance of these components relatively trivial.

At my job, we use them for things like frequently asked questions.

Pretty standard question/answer format

There are a couple of issues to consider

Because expand-and-collapse interactivity is already baked into the <details> and <summary> HTML tags, you can now make disclosure widgets without any JavaScript or CSS. But you still might want some. Left unstyled, <details> disclosure widgets present us with two issues.

Issue 1: The <summary> cursor

Though the <summary> section invites interaction, the element’s default cursor is a text selection icon rather than the pointing finger you may expect:

We get the text cursor but might prefer the pointer to indicate interaction instead.

Issue 2: Nested block elements in <summary>

Nesting a block-level element (e.g. a heading) inside a <summary> element pushes that content down below the arrow marker, rather than keeping it inline:

Block-level elements won’t share space with the summary marker.

The CSS Reset fix

To remedy these issues, we can add the following two styles to the reset section of our stylesheets:

details summary {    cursor: pointer; }  details summary > * {   display: inline; }

Read on for more on each issue and its respective solution.

Changing the <summary> cursor value

When users hover over an element on a page, we always want them to see a cursor “that reflects the expected user interaction on that element.”

We touched briefly on the fact that, although <summary> elements are interactive (like a link or form button), its default cursor is not the pointing finger we typically see for such elements. Instead, we get the text cursor, which we usually expect when entering or selecting text on a page.

To fix this, switch the cursor’s value to pointer:

details summary {    cursor: pointer; }

Some notable sites already include this property when they style <details> elements. The MDN Web Docs page on the element itself does exactly that. GitHub also uses disclosure widgets for certain items, like the actions to watch, star and fork a repo.

GitHub uses cursor: pointer on the <summary> element of its disclosure widget menus. 

I’m guessing the default cursor: text value was chosen to indicate that the summary text can (along with the rest of a disclosure widget’s content) be selected by the user. But, in most cases, I feel it’s more important to indicate that the <summary> element is interactive.

Summary text is still selectable, even after we’ve changed the cursor value from text to pointer. Note that changing the cursor only affects appearance, and not its functionality.

Displaying nested <summary> contents inline

Inside each <summary> section of the FAQ entries I shared earlier, I usually enclose the question in an appropriate heading tag (depending on the page outline):

<details>   <summary>     <h3>Will my child's 504 Plan be implemented?</h3>   </summary>   <p>Yes. Similar to the Spring, case managers will reach out to students.</p> </details>

Nesting a heading inside <summary> can be helpful for a few reasons:

  • Consistent visual styling. I like my FAQ questions to look like other headings on my pages.
  • Using headings keeps the page structure valid for users of Internet Explorer and pre-Chromium versions of Edge, which don’t support <details> elements. (In these browsers, such content is always visible, rather than interactive.)
  • Proper headings can help users of assistive technologies navigate within pages. (That said, headings within <summary> elements pose a unique case, as explained in detail below. Some screen readers interpret these headings as what they are, but others don’t.)

Headings vs. buttons

Keep in mind that the <summary> element is a bit of an odd duck. It operates like a button in many ways. In fact, it even has implicit role=button ARIA mapping. But, very much unlike buttons, headings are allowed to be nested directly inside <summary> elements.

This poses us — and browser and assistive technology developers — with a contradiction:

  • Headings are permitted in <summary> elements to provide in-page navigational assistance.
  • Buttons strip the semantics out of anything (like headings) nested within them.

Unfortunately, assistive technologies are inconsistent in how they’ve handled this situation. Some screen-reading technologies, like NVDA and Apple’s VoiceOver, do acknowledge headings inside <summary> elements. JAWS, on the other hand, does not.

What this means for us is that, when we place a heading inside a <summary>, we can style the heading’s appearance. But we cannot guarantee our heading will actually be interpreted as a heading!

In other words, it probably doesn’t hurt to put a heading there. It just may not always help.

Inline all the things

When using a heading tag (or another block element) directly inside our <summary>, we’ll probably want to change its display style to inline. Otherwise, we’ll get some undesired wrapping, like the expand/collapse arrow icon displayed above the heading, instead of beside it.

We can use the following CSS to apply a display value of inline to every heading — and to any other element nested directly inside the <summary>:

details summary > * {    display: inline; }

A couple notes on this technique. First, I recommend using inline, and not inline-block, as the line wrapping issue still occurs with inline-block when the heading text extends beyond one line.

Second, rather than changing the display value of the nested elements, you might be tempted to replace the <summary> element’s default display: list-item value with display: flex. At least I was! However, if we do this, the arrow marker will disappear. Whoops!

Bonus tip: Excluding Internet Explorer from your styles

I mentioned earlier that Internet Explorer and pre-Chromium (a.k.a. EdgeHTML) versions of Edge don’t support <details> elements. So, unless we’re using polyfills for these browsers, we may want to make sure our custom disclosure widget styles aren’t applied for them. Otherwise, we end up with a situation where all our inline styling garbles the element.

Inline <summary> headings could have odd or undesirable effects in Internet Explorer and EdgeHTML.

Plus, the <summary> element is no longer interactive when this happens, meaning the cursor’s default text style is more appropriate than pointer.

If we decide that we want our reset styles to target only the appropriate browsers, we can add a feature query that prevents IE and EdgeHTML from ever having our styles applied. Here’s how we do that using @supports to detect a feature only those browsers support:

@supports not (-ms-ime-align: auto) {    details summary {      cursor: pointer;   }    details summary > * {      display: inline;   }    /* Plus any other <details>/<summary> styles you want IE to ignore. }

IE actually doesn’t support feature queries at all, so it will ignore everything in the above block, which is fine! EdgeHTML does support feature queries, but it too will not apply anything within the block, as it is the only browser engine that supports -ms-ime-align.

The main caveat here is that there are also a few older versions of Chrome (namely 12-27) and Safari (macOS and iOS versions 6-8) that do support <details> but don’t support feature queries. Using a feature query means that these browsers, which account for about 0.06% of global usage (as of January 2021), will not apply our custom disclosure widget styles, either.

Using a @supports selector(details) block, instead of @supports not (-ms-ime-align: auto), would be an ideal solution. But selector queries have even less browser support than property-based feature queries.

Final thoughts

Once we’ve got our HTML structure set and our two CSS reset styles added, we can spruce up all our disclosure widgets however else we like. Even some simple border and background color styles can go a long way for aesthetics and usability. Just know that customizing the <summary> markers can get a little complicated!


The post Two Issues Styling the Details Element and How to Solve Them appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,

Exploring What the Details and Summary Elements Can Do

Gosh bless the

element. Toss some content inside it and you have an accessible expand-for-more interaction with just about zero work.

See the Pen Simple details. by Chris Coyier (@chriscoyier) on CodePen.

Toss a

in there to customize what the expander text says.

See the Pen Multiple Details/Summary by Chris Coyier (@chriscoyier) on CodePen.

Works great for FAQs.

There is really no limit to how you can style them. If you don’t like the default focus ring, you can remove that, but make sure to put some kind of styling back.

Here I’ve used a header element for each expandable section, which has a focus state that mimics other interactive elements on the page.

The only browser that doesn’t support this are the Microsoft ones (and Opera Mini which makes sense—it doesn’t really do interactive).

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
12 49 No 79 6

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
86 82 4 6.0-6.1

But even then, it’s just like all the sections are opened, so it’s not a huge deal:

Wanna style that default triangle? Strangely enough, the standard way to do that is through the list-style properties. But Blink-based browsers haven’t caught up to that yet, so they have a proprietary way to do it. They can be combined though. Here’s an example of replacing it with an image:

summary {   list-style-image: url(right-arrow.svg); }  summary::-webkit-details-marker {   background: url(right-arrow.svg);   color: transparent; }

See the Pen Custom Markers on Details/Summary by Chris Coyier (@chriscoyier) on CodePen.

Unfortunately, they don’t turn, and there is no way to animate the default triangle either. One idea might be to target the :focus state and swap backgrounds:

See the Pen Custom Markers on Details/Summary by Geoff Graham (@geoffgraham) on CodePen.

But that seems to be limited to WebKit and Blink and, even then, the arrow will return once the item is out of focus even if the item is still expanded.


The post Exploring What the Details and Summary Elements Can Do appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

How to Animate the Details Element Using WAAPI

Animating accordions in JavaScript has been one of the most asked animations on websites. Fun fact: jQuery’s slideDown() function was already available in the first version in 2011.

In this article, we will see how you can animate the native <details> element using the Web Animations API.

HTML setup

First, let’s see how we are gonna structure the markup needed for this animation.

The <details> element needs a <summary> element. The summary is the content visible when the accordion is closed.
All the other elements within the <details> are part of the inner content of the accordion. To make it easier for us to animate that content, we are wrapping it inside a <div>.

<details>   <summary>Summary of the accordion</summary>   <div class="content">     <p>       Lorem, ipsum dolor sit amet consectetur adipisicing elit.       Modi unde, ex rem voluptates autem aliquid veniam quis temporibus repudiandae illo, nostrum, pariatur quae!       At animi modi dignissimos corrupti placeat voluptatum!     </p>   </div> </details>

Accordion class

To make our code more reusable, we should make an Accordion class. By doing this we can call new Accordion() on every <details> element on the page.

class Accordion {   // The default constructor for each accordion   constructor() {}    // Function called when user clicks on the summary   onClick() {}    // Function called to close the content with an animation   shrink() {}    // Function called to open the element after click   open() {}    // Function called to expand the content with an animation   expand() {}    // Callback when the shrink or expand animations are done   onAnimationFinish() {} }

Constructor()

The constructor is the place we save all the data needed per accordion.

constructor(el) {   // Store the <details> element   this.el = el;   // Store the <summary> element   this.summary = el.querySelector('summary');   // Store the <div class="content"> element   this.content = el.querySelector('.content');    // Store the animation object (so we can cancel it, if needed)   this.animation = null;   // Store if the element is closing   this.isClosing = false;   // Store if the element is expanding   this.isExpanding = false;   // Detect user clicks on the summary element   this.summary.addEventListener('click', (e) => this.onClick(e)); }

onClick()

In the onClick() function, you’ll notice we are checking if the element is being animated (closing or expanding). We need to do that in case users click on the accordion while it’s being animated. In case of fast clicks, we don’t want the accordion to jump from being fully open to fully closed.

The <details> element has an attribute, [open], applied to it by the browser when we open the element. We can get the value of that attribute by checking the open property of our element using this.el.open.

onClick(e) {   // Stop default behaviour from the browser   e.preventDefault();   // Add an overflow on the <details> to avoid content overflowing   this.el.style.overflow = 'hidden';   // Check if the element is being closed or is already closed   if (this.isClosing || !this.el.open) {     this.open();   // Check if the element is being openned or is already open   } else if (this.isExpanding || this.el.open) {     this.shrink();   } }

shrink()

This shrink function is using the WAAPI .animate() function. You can read more about it in the MDN docs. WAAPI is very similar to CSS @keyframes. We need to define the start and end keyframes of the animation. In this case, we only need two keyframes, the first one being the current height the element, and the second one is the height of the <details> element once it is closed. The current height is stored in the startHeight variable. The closed height is stored in the endHeight variable and is equal to the height of the <summary>.

shrink() {   // Set the element as "being closed"   this.isClosing = true;    // Store the current height of the element   const startHeight = `$  {this.el.offsetHeight}px`;   // Calculate the height of the summary   const endHeight = `$  {this.summary.offsetHeight}px`;    // If there is already an animation running   if (this.animation) {     // Cancel the current animation     this.animation.cancel();   }    // Start a WAAPI animation   this.animation = this.el.animate({     // Set the keyframes from the startHeight to endHeight     height: [startHeight, endHeight]   }, {     // If the duration is too slow or fast, you can change it here     duration: 400,     // You can also change the ease of the animation     easing: 'ease-out'   });    // When the animation is complete, call onAnimationFinish()   this.animation.onfinish = () => this.onAnimationFinish(false);   // If the animation is cancelled, isClosing variable is set to false   this.animation.oncancel = () => this.isClosing = false; }

open()

The open function is called when we want to expand the accordion. This function does not control the animation of the accordion yet. First, we calculate the height of the <details> element and we apply this height with inline styles on it. Once it’s done, we can set the open attribute on it to make the content visible but hiding as we have an overflow: hidden and a fixed height on the element. We then wait for the next frame to call the expand function and animate the element.

open() {   // Apply a fixed height on the element   this.el.style.height = `$  {this.el.offsetHeight}px`;   // Force the [open] attribute on the details element   this.el.open = true;   // Wait for the next frame to call the expand function   window.requestAnimationFrame(() => this.expand()); }

expand()

The expand function is similar to the shrink function, but instead of animating from the current height to the close height, we animate from the element’s height to the end height. That end height is equal to the height of the summary plus the height of the inner content.

expand() {   // Set the element as "being expanding"   this.isExpanding = true;   // Get the current fixed height of the element   const startHeight = `$  {this.el.offsetHeight}px`;   // Calculate the open height of the element (summary height + content height)   const endHeight = `$  {this.summary.offsetHeight + this.content.offsetHeight}px`;    // If there is already an animation running   if (this.animation) {     // Cancel the current animation     this.animation.cancel();   }    // Start a WAAPI animation   this.animation = this.el.animate({     // Set the keyframes from the startHeight to endHeight     height: [startHeight, endHeight]   }, {     // If the duration is too slow of fast, you can change it here     duration: 400,     // You can also change the ease of the animation     easing: 'ease-out'   });   // When the animation is complete, call onAnimationFinish()   this.animation.onfinish = () => this.onAnimationFinish(true);   // If the animation is cancelled, isExpanding variable is set to false   this.animation.oncancel = () => this.isExpanding = false; }

onAnimationFinish()

This function is called at the end of both the shrinking or expanding animation. As you can see, there is a parameter, [open], that is set to true when the accordion is open, allowing us to set the [open] HTML attribute on the element, as it is no longer handled by the browser.

onAnimationFinish(open) {   // Set the open attribute based on the parameter   this.el.open = open;   // Clear the stored animation   this.animation = null;   // Reset isClosing & isExpanding   this.isClosing = false;   this.isExpanding = false;   // Remove the overflow hidden and the fixed height   this.el.style.height = this.el.style.overflow = ''; }

Setup the accordions

Phew, we are done with the biggest part of the code!

All that’s left is to use our Accordion class for every <details> element in the HTML. To do so, we are using a querySelectorAll on the <details> tag, and we create a new Accordion instance for each one.

document.querySelectorAll('details').forEach((el) => {   new Accordion(el); });

Notes

To make the calculations of the closed height and open height, we need to make sure that the <summary> and the content always have the same height.

For example, do not try to add a padding on the summary when it’s open because it could lead to jumps during the animation. Same goes for the inner content — it should have a fixed height and we should avoid having content that could change height during the opening animation.

Also, do not add a margin between the summary and the content as it will not be calculated for the heights keyframes. Instead, use a padding directly on the content to add some spacing.

The end

And voilà, we have a nice animated accordion in JavaScript without any library! 🌈


The post How to Animate the Details Element Using WAAPI appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Creating a Details Element That Opens But Never Closes

The <details> and <summary> elements in HTML are useful for making content toggles for bits of text. By default, you see the <summary> element with a toggle triangle (▶︎) next to it. Click that to expand the rest of the text inside the <details> element.

But let’s say you want to be able to click it open and that’s that. Interactivity over. I saw this used in one of those “Read more” article designs, where you click that “Read more” button and the article expands, but there is no going back.

I’ll preface this by saying that I’m not sure that this is a great idea in general. Removing controls just doesn’t feel great, nor does slapping too much important content within a <details> element. But, hey, the web is a big place and you never know what you might need. The fact that this can be done in a few lines of HTML/CSS is compelling and might reduce the need for heavier solutions.

The main trick here is to hide the summary when details is open.

details[open] summary {   display: none; }

That’s it really.

The post Creating a Details Element That Opens But Never Closes appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]