Skip to content

Accordion

hc-accordion is a pure CSS skin over the native <details> / <summary> element. Keyboard handling, the open attribute, and the toggle event are all provided by the browser. The <details name="..."> attribute expresses the single-open (“exclusive”) variant declaratively, so no JS is needed to enforce the “only one item open at a time” rule.

FeatureRequired version
<details> / <summary>every browser
<details name="..."> exclusive groupingChrome 120+, Firefox 130+, Safari 17.2+

Give every <details> in the group the same name value and the browser enforces single-open semantics.

What is Hypermedia Components?

A semantic CSS + htmx UI kit for hypermedia applications.

How do I install it?

pnpm add @hypermedia-components/core

Does it require JavaScript?

Most components are CSS only. This page does not import a behavior.

Omit name and each item operates independently — multiple may be open at once.

<div class="hc-accordion">
<details class="hc-accordion__item"></details>
<details class="hc-accordion__item"></details>
</div>

The panel animates its height open and closed — pure CSS, no JavaScript — using ::details-content with interpolate-size: allow-keywords (so the height can transition to and from auto). The chevron rotation and the height both respect prefers-reduced-motion: reduce.

<details> fires the native toggle event when its open state changes. htmx’s [target.open] filter restricts the trigger to the “becoming open” case, and once makes the request happen exactly the first time.

<details class="hc-accordion__item"
data-hx-get="/account/billing"
data-hx-trigger="toggle once[target.open]"
data-hx-target="find .hc-accordion__content"
data-hx-swap="innerHTML">
<summary class="hc-accordion__trigger">
Billing details
<svg class="hc-accordion__icon" viewBox="0 0 16 16" aria-hidden="true"></svg>
</summary>
<div class="hc-accordion__content">
<hc-spinner></hc-spinner>
</div>
</details>
  • <summary> is keyboard-focusable and toggles the disclosure on Enter and Space — both handled by the browser.
  • Use a meaningful heading inside the summary if the accordion is structurally important. The summary itself reports as a button to assistive tech; the contained text is the accessible name.
  • We replace the default disclosure marker with an inline SVG chevron so it inherits color and font-size. Setting aria-hidden="true" on the icon prevents it being announced.
  • The chevron animation respects prefers-reduced-motion: reduce — transition duration drops to 0 ms when the user has indicated they prefer reduced motion.
  • Do not nest interactive controls inside <summary> (a known cross-browser accessibility footgun). Put any extra controls in the content panel instead.

Component tokens (in component.tokens.json):

Token pathPurpose
accordion.item.border-colorDivider between items and the top edge of the first one.
accordion.trigger.padding-x / padding-y / gapSummary layout.
accordion.trigger.font-weight / fg / hover-fgSummary text.
accordion.icon.size / transition-durationChevron dimensions and rotation timing.
accordion.content.padding-block-end / fgBody padding and color.
accordion.content.transition-durationOpen / close height animation timing (where supported).
Show the generated CSS variables
--hc-accordion-item-border-color
--hc-accordion-trigger-padding-x | -padding-y | -gap
--hc-accordion-trigger-font-weight | -fg | -hover-fg
--hc-accordion-icon-size | -transition-duration
--hc-accordion-content-padding-block-end | -fg | -transition-duration
--hc-color-focus-ring (inherited from data-color)
  • Tabs — when only one section should be visible at a time and switching them is the primary action.
  • Card — when sections are always visible.