Navigation

v0.0.4
Structure

Accordion

Native details rows that open to their own height.

On this page

An accordion is a list of rows that open in place. Each row is a native <details> with a <summary>, so the browser keeps the toggle, the keys and find-in-page, which opens a closed row when it holds a match. The stylesheet draws the rows on hairlines and lets each panel open to its own height.

Accordion

<div class="accordion">  <details class="accordion-item" name="faq" open>    <summary class="accordion-trigger">Does it need JavaScript?</summary>    <div class="accordion-content">No. Every component is a native element with one stylesheet; the browser keeps its behavior.</div>  </details>  <details class="accordion-item" name="faq">    <summary class="accordion-trigger">Can I change the radius?</summary>    <div class="accordion-content">Yes. Override the radius tokens after importing Master UI, or set a component's own property such as --btn-radius.</div>  </details>  <details class="accordion-item" name="faq">    <summary class="accordion-trigger">Is dark mode a second stylesheet?</summary>    <div class="accordion-content">No. Every color token resolves with light-dark(), following the page's color scheme.</div>  </details></div>

#One at a time, or many

Rows that share a name attribute form a group: opening one closes the others, as above. Leave name out to let any number stay open.

Independent rows

<div class="accordion">  <details class="accordion-item" open>    <summary class="accordion-trigger">Build</summary>    <div class="accordion-content">Installed 412 packages in 9 seconds and compiled 38 routes.</div>  </details>  <details class="accordion-item" open>    <summary class="accordion-trigger">Deploy</summary>    <div class="accordion-content">Uploaded 3.1 MB to 14 regions.</div>  </details>  <details class="accordion-item">    <summary class="accordion-trigger">Checks</summary>    <div class="accordion-content">All 6 checks passed.</div>  </details></div>

#On a surface

The accordion has no material of its own. Put it in a card or a sheet for a boxed list.

In a card

<section class="card">  <header class="card-header">    <h3 class="card-title">Shipping</h3>    <p class="card-description">Rates and delivery times.</p>  </header>  <div class="accordion">    <details class="accordion-item" name="shipping">      <summary class="accordion-trigger">Domestic</summary>      <div class="accordion-content">Two to four working days. Free above 50 USD.</div>    </details>    <details class="accordion-item" name="shipping">      <summary class="accordion-trigger">International</summary>      <div class="accordion-content">Five to ten working days, with tracking.</div>    </details>  </div></section>

#Motion

The panel is the ::details-content pseudo-element. interpolate-size: allow-keywords lets its block-size travel from 0 to auto, and content-visibility transitions with allow-discrete so the text stays painted until the panel has closed. The chevron turns over with it. In a browser without these the panel opens at once; with reduced motion it fades in place.

#Classes

ClassDescription
accordionThe list of rows
accordion-itemA row, on a details element; a hairline parts it from the next
accordion-triggerThe row's title, on the summary, with a chevron at its end
accordion-contentThe panel: running text under the title

#Source

@master/ui/components/accordion.css
/* * Accordion: native <details> rows on hairlines. The browser keeps the toggle, the keys and find-in-page; * sharing a name attribute keeps one row open at a time. The panel opens to its own height by transitioning * ::details-content with interpolate-size, and opens at once where that is not supported. */@layer components {  .accordion {    display: grid;    min-width: 0;  }  .accordion-item {    min-width: 0;    /* Lets block-size travel from 0 to auto. */    interpolate-size: allow-keywords;    &:not(:last-child) {      box-shadow: inset 0 -1px 0 var(--color-line-hair);    }    &::details-content {      block-size: 0;      overflow-y: clip;      opacity: 0;      transition-property: block-size, opacity, content-visibility;      transition-duration: var(--duration-base);      transition-timing-function: var(--easing-glide);      transition-behavior: allow-discrete;    }    &[open]::details-content {      block-size: auto;      opacity: 1;    }    @media (prefers-reduced-motion: reduce) {      &::details-content {        transition-property: opacity, content-visibility;      }    }    /* The chevron turns over when its row is open. A closed row nested in an open one keeps its own. */    &[open] .accordion-trigger::after {      rotate: x 180deg;    }    &[open] .accordion-item:not([open]) .accordion-trigger::after {      rotate: none;    }    @media (forced-colors: active) {      &:not(:last-child) {        border-bottom: 1px solid CanvasText;      }    }  }  /* The <summary>: the row's title with a chevron at its end. */  .accordion-trigger {    display: flex;    align-items: center;    justify-content: space-between;    gap: 1rem;    min-height: 3rem;    padding: .75rem 0;    border-radius: calc((var(--radius-chip) - 2px) * var(--corner-scale));    corner-shape: var(--corner-shape);    color: var(--color-text-strong);    font: var(--font-weight-control) .875rem/1.4 var(--font-family-sans);    font-feature-settings: inherit;    list-style: none;    user-select: none;    -webkit-user-select: none;    -webkit-tap-highlight-color: transparent;    cursor: pointer;    touch-action: manipulation;    &::-webkit-details-marker {      display: none;    }    &::after {      content: '';      flex: none;      width: 1rem;      height: 1rem;      background: var(--color-text-muted);      mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4.5 6.5 3.5 3.5 3.5-3.5'/%3E%3C/svg%3E") center / contain no-repeat;      transition: rotate var(--duration-base) var(--easing-glide), background-color var(--duration-quick) var(--easing-glide);    }    @media (hover: hover) {      &:hover::after {        background: var(--color-text-strong);      }    }    /* Pressed: the chevron takes the strong ink, as under the pointer. */    &:active::after {      background: var(--color-text-strong);    }    &:focus-visible {      @apply --focus-ring;    }    @media (prefers-reduced-motion: reduce) {      &::after {        transition: none;      }    }    @media (forced-colors: active) {      &::after {        background: CanvasText;      }    }  }  /* The panel: running text under the title, on the same edge. */  .accordion-content {    padding: 0 0 1rem;    padding-inline-end: 2rem;    color: var(--color-text-body);    font-size: .875rem;    line-height: 1.6;    text-wrap: pretty;    overflow-wrap: anywhere;  }}