Skip to content

Accordion

Leverages the HTML details and summary elements. Uses the native HTML arrow, check out how to add your own custom arrow.

Basics

Accordion

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

html
<details class="card">
  <summary id="summary-id" aria-controls="content-id">Accordion title</summary>
  <div
    id="content-id"
    class="content"
    role="region"
    aria-labelledby="summary-id"
  >
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales,
      nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis
      neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id
      sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse
      potenti. Cras ut ante in libero tempus sodales sed quis dolor.
    </p>
  </div>
</details>

Variants

Text

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Elevated

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Outlined

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Tonal

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

html
<!-- Text (default) -->
<details class="card">
  <!--  -->
</details>

<!-- or -->

<details class="card text">
  <!--  -->
</details>

<!-- Elevated -->
<details class="card elevated">
  <!--  -->
</details>

<!-- Outlined -->
<details class="card outlined">
  <!--  -->
</details>

<!-- Tonal -->
<details class="card tonal">
  <!--  -->
</details>

Accordion group

  • Apply the .card class to a parent element.
  • You are still able to set variant styles, but instead of doing it on the individual elements - apply the variant class on the .card element instead.
Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

html
<div class="card outlined">
  <details>
    <!--  -->
  </details>
  <details>
    <!--  -->
  </details>
  <details>
    <!--  -->
  </details>
</div>

Only show one accordion at a time

To have the newly opened accordion close the prior, apply a shared [name] attribute to all <details> elements.

Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

html
<div class="card outlined">
  <details name="example-group">
    <!--  -->
  </details>
  <details name="example-group">
    <!--  -->
  </details>
  <details name="example-group">
    <!--  -->
  </details>
</div>

Actions

Accordion with actions

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare.

html
<details open class="outlined">
  <summary id="summary1" aria-controls="content1">
    Accordion with actions
  </summary>
  <div id="content1" class="content" role="region" aria-labelledby="summary1">
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales,
      nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis
      neque ante id justo. Nam tempor euismod nisi ac ornare.
    </p>
  </div>
  <div class="actions">
    <button class="button">Cancel</button>
    <button class="button">Agree</button>
  </div>
</details>

Custom arrow

If the native details arrow doesn't suit your needs you can easily modify the accordion.css file to add your own preferred icon.

Custom arrow

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

html
<details class="outlined">
  <summary id="summary1" aria-controls="content1">
    Custom arrow
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
    >
      <path
        fill="currentColor"
        d="M4.293 8.293a1 1 0 0 1 1.414 0L12 14.586l6.293-6.293a1 1 0 1 1 1.414 1.414l-7 7a1 1 0 0 1-1.414 0l-7-7a1 1 0 0 1 0-1.414"
      />
    </svg>
  </summary>
  <div id="content1" class="content" role="region" aria-labelledby="summary1">
    <!--  -->
  </div>
</details>
css
details {
  summary {
    align-items: center;
    display: flex;
    justify-content: space-between;
    list-style: none;

    &::marker,
    &::-webkit-details-marker {
      display: none;
    }

    svg {
      transition: rotate 0.3s var(--ease-1);
    }
  }

  &[open] > summary svg {
    rotate: 180deg;
  }

  /* ... */
}

Accessibility

The WAI-ARIA guidelines for accordions recommend:

  • summary element
    • adding id and aria-controls
    • adding aria-expanded (if using JS)
  • content wrapper
    • adding id, role and aria-labelledby

Anatomy

  1. <details>: a wrapper for the accordion
  2. <summary>: a wrapper for the accordion header
  3. & > .content (optional): a wrapper for the accordion content
  4. & > .actions (optional): a wrapper that groups a set of buttons
Accordion title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sodales, nulla sit amet porttitor rhoncus, lacus ex vestibulum libero, ac mollis neque ante id justo. Nam tempor euismod nisi ac ornare. Pellentesque id sapien lacinia, venenatis est aliquam, dignissim elit. Suspendisse potenti. Cras ut ante in libero tempus sodales sed quis dolor.

html
<details>
  <summary id="summary-1" aria-controls="content-1">
    <!--  -->
  </summary>
  <div id="content-1" class="content" role="region" aria-labelledby="summary-1">
    <!--  -->
  </div>
  <div class="actions">
    <!--  -->
  </div>
</details>

API

These are the classes and attributes a card can be styled with. As usual, feel free to add your own!

TypeModifiersDefaultDescription
Anatomy& > summary, & > .content, & > .actions-Optional wrappers for child content.
Variants.text, .elevated, .tonal, .outlined,.textThe variant to use.

Browser compatibility

Installation

INFO

Accordion has card as a dependency.

css
:where(details) {
  --_accordion-transition-time: 0.2s;
  --_bg-color: transparent;
  --_margin-inline: var(--size-1);
  --_shadow: none;

  background-color: var(--_bg-color);
  box-shadow: var(--_shadow);
  border-radius: var(--surface-border-radius, 4px);
  margin-inline: var(--_margin-inline);
  transition: all var(--_accordion-transition-time) ease-out;

  /* Accordion animation */
  /* https://nerdy.dev/open-and-close-transitions-for-the-details-element */
  @media (prefers-reduced-motion: no-preference) {
    interpolate-size: allow-keywords;
  }

  &::details-content {
    block-size: 0;
    opacity: 0;
    overflow-y: clip;
    transition:
      content-visibility var(--_accordion-transition-time) allow-discrete,
      opacity var(--_accordion-transition-time),
      block-size var(--_accordion-transition-time);
  }

  &[open]::details-content {
    block-size: auto;
    opacity: 1;
  }
  /***/

  summary {
    background-color: inherit;
    cursor: pointer;
    font-weight: 700;
    padding-block: var(--size-3);
    user-select: none;
  }

  /* Custom arrow */
  /* summary {
    align-items: center;
    background-color: inherit;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    list-style: none;
    padding-block: var(--size-3);
    user-select: none;

    &::marker,
    &::-webkit-details-marker {
      display: none;
    }

    svg {
      transition: rotate 0.2s ease-out;
    }
  }

  &[open] > summary svg {
    rotate: 180deg;
  } */

  .content {
    margin-block-start: 0;
    padding-block: var(--size-2) var(--size-3);
  }

  .actions {
    display: flex;
    gap: var(--size-1);
    justify-content: end;
    margin-block-start: var(--size-3);
    padding-block-end: var(--size-1);
    padding-inline: var(--size-3) var(--size-1);
  }

  /* Variants */
  &.card,
  &.text {
    --_bg-color: transparent;
    --_margin-inline: var(--size-1);
  }

  &.elevated {
    --_bg-color: var(--surface-elevated);
    --_margin-inline: 0;
    --_shadow: var(--shadow-2);
  }

  &.outlined {
    --_bg-color: var(--surface-default);
    --_margin-inline: 0;
    border: 1px solid var(--border-color);
  }

  &.tonal {
    --_bg-color: var(--surface-tonal);
    --_margin-inline: 0;
  }

  &:where(.elevated, .outlined, .tonal) > * {
    padding-inline: var(--size-3);
  }
}

/* Accordion group  */
:where(.card:has(details)) {
  --_gutter-color: var(--border-color);

  & > .content {
    margin-block: 0;
    padding-inline: 0;
  }

  &.card,
  &.text {
    &:not(.tonal, .outlined, .elevated) {
      summary {
        padding-inline: 0;
      }
    }
  }

  details {
    --_margin-inline: 0;
    border: 0;
    box-shadow: none;

    & > * {
      padding-inline: var(--size-3);
    }

    /* Border between accordion items */
    & + & {
      summary {
        border-radius: 0;
        border-block-start: 1px solid var(--_gutter-color);
      }
    }

    /* First item */
    &:first-of-type {
      border-start-start-radius: var(--surface-border-radius, 0.25rem);
      border-start-end-radius: var(--surface-border-radius, 0.25rem);
      summary {
        border-start-start-radius: var(--surface-border-radius, 0.25rem);
        border-start-end-radius: var(--surface-border-radius, 0.25rem);
      }
    }

    /* Last item */
    &:last-of-type {
      border-end-start-radius: var(--surface-border-radius, 0.25rem);
      border-end-end-radius: var(--surface-border-radius, 0.25rem);
      summary {
        border-end-start-radius: var(--surface-border-radius, 0.25rem);
        border-end-end-radius: var(--surface-border-radius, 0.25rem);
      }
    }
  }
}
css
:where(.card) {
  --_bg-color: transparent;
  --_border-color: transparent;
  --_border-width: 0;
  --_shadow: none;

  background-color: var(--_bg-color);
  border-color: var(--_border-color);
  border-radius: var(--surface-border-radius, 0.25rem);
  border-style: solid;
  border-width: var(--_border-width);
  box-shadow: var(--_shadow);
  overflow: hidden;
  padding-inline: 0;

  /* Variants */
  &.text {
    --_bg-color: transparent;
    --_border-color: transparent;
    --_border-width: 0;
    --_shadow: none;
    padding-inline: 0;
  }

  &.tonal {
    --_bg-color: var(--surface-tonal);
    --_border-width: 1px;
  }

  &.elevated {
    --_bg-color: var(--surface-elevated);
    --_border-color: transparent;
    --_border-width: 0;
    --_shadow: var(--shadow-3);

    /* Adjust shadow in dark mode */
    @container style(--color-scheme: dark) {
      --_shadow: var(--shadow-4);
    }
  }

  &.outlined {
    --_bg-color: var(--surface-default);
    --_border-color: var(--border-color);
    --_border-width: 1px;
  }

  :where(hgroup, .content) {
    padding-inline: var(--size-3);
  }

  hgroup {
    margin-block: var(--size-3);
    & > * {
      margin: 0;
    }

    /* Top paragraph */
    & > p:first-of-type:first-child {
      line-height: 1.3;
    }

    /* Bottom paragraph */
    & > p:last-of-type:last-child:not(:first-child) {
      font-size: var(--font-size-1, 1rem);
    }
  }

  .content {
    margin-block: var(--size-3) var(--size-4, 1.25rem);
  }

  .actions {
    display: flex;
    gap: var(--size-1);
    margin-block: var(--size-4, 1.25rem) 0;
    padding-block-end: var(--size-1);
    padding-inline: var(--size-1) var(--size-3);
  }
}