BEST_PRACTICES /
Toggle content

← Back
Approach Requires CSS Requires JavaScript Requires new JavaScript
<details>
<details class="Details-element"> Yes
.js-details-container Yes Yes
[hidden] Yes

Out of the 4 approaches listed, [hidden] is only one that requires you to add new JS on top of the initial markup. When you want to hide or show all elements with each interaction, use .js-details-container because it is a linear path. When you want to show A but hide B with each interaction, use [hidden] with custom script so the intention would be clear.

<details>

The <details> element is accessible by default. Use it whenever possible.

<details>
  <summary>Reveal content</summary>
  <div>Content to be revealed</div>
</details>

To remove the that comes with the element, add .details-reset on <details>.

<details class="Details-element">

To toggle content display in <summary> as the element expands/collapses, use .Details-element. Common use case:

<details class="Details-element">
  <summary>
    <span class="Details-content--closed">Show</span>
    <span class="Details-content--open">Hide</span>
    content
  </summary>
  <div>Content to be revealed</div>
</details>

[hidden]

For more complex interactions, use <div hidden> to hide a content by default and div.hidden = false to unhide it.

When expanding and collapsing content, toggle aria-expanded on the control element to indicate the expand/collapse state. When appropriate, use aria-live to inform screen reader users of the newly expanded content.

Good

<button aria-expanded="false" type="button">Toggle currency</button>
<div aria-live="polite">
  <div class="js-currency-toggle">Bronze Plan $9/month</div>
  <div class="js-currency-toggle" hidden>Bronze Plan 270 NT$/month</div>
  <div class="js-currency-toggle">Silver Plan $15/month</div>
  <div class="js-currency-toggle" hidden>Silver Plan 460 NT$/month</div>
</div>
button.addEventListener('click', () => {
  button.setAttribute('aria-expanded', button.getAttribute('aria-expanded') === 'false')
  for (const el of document.querySelectorAll('.js-currency-toggle')) {
    el.hidden = !el.hidden
  }
})

Bad

This pattern is no ideal because it has 3 points of failure. The default visibility relies on CSS, CSS classes are toggled by JavaScript, and JavaScript classes are set in the markup.

<button type="button">Toggle currency</button>
<div class="usd">Bronze Plan $9/month</div>
<div class="local-currency">Bronze Plan 270 NT$/month</div>
<div class="usd">Silver Plan $15/month</div>
<div class="local-currency">Silver Plan 460 NT$/month</div>
.local-currency { display: none; }
.is-showing-local-currency .usd { display: none; }
.is-showing-local-currency .local-currency { display: block; }
button.addEventListener('click', () => {
  body.classList.toggle('is-showing-local-currency')
})