Accordion

A native accordion built with <details> and <summary>. The shared name attribute makes the group exclusive, so opening one panel closes the others.

Accordion component illustration

0 KB JavaScript

~30 CSS lines

details exclusive group

keyboard native access

Preview

What is this project?

A demonstration of everything modern CSS and HTML can do without a single line of JavaScript. Carousel, modals, tabs, tooltips, validated forms… all of it.

How does this accordion work?

The <details> element has been native for a long time, but the name="..." attribute shared across multiple <details> is recent. It creates an accordion where only one entry can be open at a time.

Which browsers support this?

All Chromium-based browsers, recent Firefox, and recent Safari support these features. For older browsers, the markup remains usable, just without the exclusive behaviour.

Why no JavaScript?

Because the web platform has become powerful enough. Less JS = fewer bugs, better performance, more free accessibility, and a site that works even when JS fails to load.

The code

The markup

<div class="accordion">
  <details name="accordion-1" open>
    <summary>
      <!-- Section 1 -->
      <span class="chev" aria-hidden="true">+</span>
    </summary>
    <!-- Content 1 -->
  </details>  
  <details name="accordion-1">
    <summary>
      <!-- Section 2 -->
      <span class="chev" aria-hidden="true">+</span>
    </summary>
    <!-- Content 2 -->
  </details>  
  ...
</div>

CSS mechanisms

/* ----------------------------------------------------------------------------
 * Details
 * ---------------------------------------------------------------------------- */

/**
 * If you need to support browsers without `::details-content`
 * (pre-late 2024), add `overflow: hidden` to `.accordion > details`
 * to prevent visual overflow during CSS transitions.
 * On current browsers, clipping is handled by `overflow: clip`
 * directly on `::details-content`.
 */

.accordion > details {
  /* add your styles here */
}

/**
 * styles of the open `<details>`
 */

.accordion > details[open] {
  /* add your styles here */
}

/* ----------------------------------------------------------------------------
 * Summary
 * ---------------------------------------------------------------------------- */

/**
 * Summary row: removes default disclosure marker, lays out title
 * and chevron with space-between.
 */

.accordion > details > summary {
  list-style: none;
  cursor: pointer;
  /* add your styles here */
}

/**
 * Hover state on summary row.
 */

.accordion > details > summary:hover { 
  /* add your styles here */
}

/**
 * Hide the legacy WebKit disclosure triangle.
 */

.accordion > details > summary::-webkit-details-marker { 
  display: none; 
}

/* ----------------------------------------------------------------------------
 * Chevron
 * ---------------------------------------------------------------------------- */

/**
 * Chevron / "+" indicator that rotates 45° when open (turning
 * "+" into "×").
 */

.accordion > details > summary > .chev {
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
  /* add your styles here */
}

/**
 * Open state: rotate chevron to form an "×".
 */

.accordion > details[open] > summary > .chev {
  transform: rotate(45deg);
  /* add your styles here */
}

/* ----------------------------------------------------------------------------
 * Open/Close Animation
 * ---------------------------------------------------------------------------- */

/**
 * Animated open/close using the new `::details-content` pseudo
 * and `interpolate-size: allow-keywords` which unlocks transitions 
 * between `0` and `auto` block sizes.
 *
 * `content-visibility` is transitioned discretely so the browser
 * keeps the collapsed/rendered state in sync with the open/close
 * transition.
 */

@supports selector(details::details-content) and (interpolate-size: allow-keywords) {

  /**
   * Enables transitions to and from `auto` sizes globally.
   * Scoped inside `@supports` so it only applies in browsers
   * that understand `interpolate-size`.
   */

  .accordion {
    interpolate-size: allow-keywords;
  }

  .accordion > details::details-content {
    block-size: 0;
    overflow: clip;
    transition: block-size 320ms cubic-bezier(0.22, 1, 0.36, 1), content-visibility 320ms allow-discrete;
  }

  .accordion > details[open]::details-content {
    block-size: auto;
  }
}

Browser support

Benefits

Current limitations