A stack of headings that each open to reveal their content, as separate cards or as a flat list divided by rules.
What is the design system?
How do I get the tokens?
Can I use it without React?
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
looksmbk-accordion--look-<value> | cardsflat | cards | Each item as a raised card, or a flat list separated by dividers. Figma calls this "Cards". |
sizesmbk-accordion--size-<value> | mdsm | md | md for a page; sm for a narrow column such as a settings panel, with smaller text, tighter padding and a smaller caret. |
Examples
Usage
Each item is a raised card; the first starts open, and opening another closes it.
What is the design system?
How do I get the tokens?
Can I use it without React?
Flat
The flat look draws a rule under each item instead of a card.
What is the design system?
How do I get the tokens?
Can I use it without React?
Small
The small size, cards and flat, for a narrow column such as a settings panel.
Notifications
Privacy
Notifications
Privacy
Disabled item
A disabled item cannot be opened.
What is the design system?
How do I get the tokens?
Several open
With multiple, each item opens and closes on its own, so a reader can compare answers.
What is the design system?
How do I get the tokens?
Can I use it without React?
Guidelines
Purpose
A list of headings, each of which opens to show its content and closes again, so that a long set of questions or sections fits in a short space and the reader chooses what to expand. Frequently asked questions are the classic case.
When to use
- Questions and answers.
- Optional detail under a short heading, in settings and forms. In a narrow column, such as a settings panel, use the small size: a smaller title and text, tighter padding and a smaller caret.
When not to use
- Content most people need. Show it.
- Navigation between views. Use tabs.
- One section only. That is a disclosure, which this can be, but a single card looks odd; use the flat look.
Usage rules
Do
- Keep titles to one line.
- Open the first item by default when the page would otherwise look empty.
- In markup written by hand for the flat look, add the divider after each item's disclosure element, not inside it, where it would be hidden while the item is closed, and hide it from assistive technology.
- Give a title that holds more than words, such as a tag beside a topic or a name with a count, a block-level row of its own, a flex or grid row.
Don't
- Nest accordions.
- Round an item into a capsule. Its corner is a control corner, however short the item is.
What is the design system?
How do I get the tokens?
Keep each title to one line, so the list scans at a glance.
What is the design system, who looks after it, and how do I ask for a new component?
How do I get the tokens?
A title that runs onto two lines makes the reader work before they know whether to open it.
What is the design system?
How do I get the tokens?
Can I use it without React?
Keep one level of items, so every answer is one press away.
Getting started
How do I get the tokens?
Can I use it without React?
What is the design system?
An accordion inside an accordion hides answers two presses deep and loses the reader.
Anatomy
- The block, a column of items.
- The item part, one section: a native disclosure element, drawn as a raised card or as a flat row.
- The divider part, flat look only: the divider component after each item.
- The summary part, the always-visible heading row, holding the title part and the caret part. It carries the item's padding, so the whole row is what a press opens.
- The content part, revealed when open, with the item's padding at its sides and foot.
Behavior
Pressing the summary opens or closes the item; the caret turns and takes the brand color while open. One item stays open at a time: opening one closes the one that was open. Set multiple to let several stay open, for answers a reader may want to compare side by side. In markup written by hand, give every item of one accordion the same name for one at a time, and none for several.
An item unfolds with the expand motion and folds back the same way: its height grows from the heading row while the content fades in from a slight blur, the foot of what shows fades rather than meeting the edge with a hard cut, and the caret turns with it. A press halfway turns it round from where it is. The React component moves it, the same in every browser. Markup written by hand is moved by the stylesheet where the browser can animate to a natural height, without the fading foot, and opens at once elsewhere. Under reduced motion the height changes at once and the fade still plays: an item opens and then its content fades in, and its content fades out and then it shuts.
On a touch screen a heading row keeps its height; one shorter than the comfortable touch target, the small size's, takes an invisible press area out to it.
Accessibility
What it does for you
- It is made of native disclosure elements, so Enter and Space toggle an item and the open state is announced, with no script of yours.
- Its focus ring is drawn around the whole item while the item's summary has keyboard focus. In the flat look the item also lifts to the raised surface.
- A disabled item cannot be opened or closed, by the mouse, the keyboard or a screen reader's press. It stays in the tab order, so a keyboard or screen reader user learns it is there, and it is announced as dimmed or unavailable.
- In the flat look, the divider after each item is hidden from assistive technology, since each item is already its own disclosure.
What you must do
- Wrap the title in a heading element when the accordion structures the page. The title is plain text inside the summary.
- Use the React component for a disabled item, or leave the item out. In markup written by hand only the mouse is kept off a disabled item, since a screen reader can still press its summary.
How to test
- Tab to an item: the focus ring shows around the whole item, in both themes and in a forced-color mode.
- Press Enter, then Space: each opens or closes the item. Tab reaches a disabled item, which is announced as unavailable and does nothing.
- With a screen reader, each item is read as a disclosure by its title, with its open or closed state.
- With text at 200% and the page 320 pixels wide, titles wrap and nothing is cut off.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the next item's summary, a disabled one included. |
| EnterorSpace | Opens the focused item, or closes it when it is open. A disabled item does nothing. |
Source
The files the accordion is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-accordion — Accordion. Figma "Accordion" 4128:20419 and "AccordionItem" 4128:20300.
* Items are native details/summary elements, so opening, closing and keyboard
* behavior are the browser's; items sharing a name close each other. The caret
* turns and takes the brand color when open. The item unfolds as it opens and
* folds back as it closes (see the motion rules at the end). */
.smbk-accordion {
display: flex;
flex-direction: column;
gap: var(--smbk-space-inset-md);
width: 100%;
}
/* Figma pads the item; here the padding is the summary's and the content's, so the whole heading row,
padding included, is what a press opens. */
.smbk-accordion__item {
box-sizing: border-box;
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
border-radius: var(--smbk-radius-control-sm);
background: var(--smbk-color-surface-default);
color: var(--smbk-color-text-default);
}
.smbk-accordion__summary {
display: flex;
align-items: center;
gap: var(--smbk-space-inset-md);
padding: var(--smbk-space-inset-2xl) var(--smbk-space-inset-xl);
list-style: none;
cursor: pointer;
font-family: var(--smbk-typography-body-lg-font-family);
font-size: var(--smbk-typography-body-lg-font-size);
font-weight: var(--smbk-typography-body-lg-font-weight);
line-height: var(--smbk-typography-body-lg-line-height);
letter-spacing: var(--smbk-typography-body-lg-letter-spacing);
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
@media (hover: hover) {
.smbk-accordion__summary:hover {
color: var(--smbk-color-text-brand);
}
}
.smbk-accordion__summary::-webkit-details-marker {
display: none;
}
/* Focus is drawn around the whole item, its corners with it, as Figma draws it: the summary takes focus,
the item shows the ring. */
.smbk-accordion__summary:focus-visible {
outline: none;
}
.smbk-accordion__item:has(> .smbk-accordion__summary:focus-visible) {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
/* A browser that cannot ask what an item holds rings the summary instead. */
@supports not selector(:has(*)) {
.smbk-accordion__summary:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: calc(-1 * var(--smbk-focus-ring-width));
}
}
/* The title takes the room the caret leaves. */
.smbk-accordion__title {
flex: 1 1 auto;
}
.smbk-accordion__caret {
flex-shrink: 0;
color: var(--smbk-color-text-subtle);
font-size: var(--smbk-size-icon-md);
}
/* An item the React component is closing (data-closing) is still open until its motion ends; its caret turns back
as the motion starts. */
.smbk-accordion__item[open]:not([data-closing]) > .smbk-accordion__summary .smbk-accordion__caret {
color: var(--smbk-color-text-brand);
transform: rotate(180deg);
}
.smbk-accordion__content {
padding: 0 var(--smbk-space-inset-xl) var(--smbk-space-inset-2xl);
color: var(--smbk-color-text-subtle);
font-family: var(--smbk-typography-body-md-font-family);
font-size: var(--smbk-typography-body-md-font-size);
font-weight: var(--smbk-typography-body-md-font-weight);
line-height: var(--smbk-typography-body-md-line-height);
letter-spacing: var(--smbk-typography-body-md-letter-spacing);
}
.smbk-accordion__item--disabled {
background: var(--smbk-color-surface-disabled);
border-color: transparent;
color: var(--smbk-color-text-disabled);
}
.smbk-accordion__item--disabled > .smbk-accordion__summary {
cursor: not-allowed;
pointer-events: none;
}
.smbk-accordion__item--disabled .smbk-accordion__caret {
color: var(--smbk-color-text-disabled);
}
/* ---- flat: no cards, the divider component after each item (the divider part), which
draws its own two lines: the shade over the highlight ---- */
.smbk-accordion--look-flat {
gap: 0;
}
.smbk-accordion--look-flat .smbk-accordion__item {
border: 0;
border-radius: 0;
background: transparent;
}
/* A flat item lifts to the raised surface while its heading has keyboard focus, as Figma draws it. */
.smbk-accordion--look-flat .smbk-accordion__item:has(> .smbk-accordion__summary:focus-visible) {
background: var(--smbk-color-surface-raised);
}
.smbk-accordion--look-flat .smbk-accordion__summary {
padding: var(--smbk-space-inset-2xl) 0;
}
.smbk-accordion--look-flat .smbk-accordion__content {
padding: 0 0 var(--smbk-space-inset-xl);
}
.smbk-accordion--look-flat .smbk-accordion__item--disabled {
background: transparent;
}
/* ---- small: for a narrow column such as a settings panel. A Label MD title over Body SM text, the
summary padded by the medium inset over and under and the large inset at the sides, a 16 caret.
The flat look keeps no side padding. ---- */
.smbk-accordion--size-sm .smbk-accordion__summary {
padding: var(--smbk-space-inset-md) var(--smbk-space-inset-lg);
font-family: var(--smbk-typography-label-md-font-family);
font-size: var(--smbk-typography-label-md-font-size);
font-weight: var(--smbk-typography-label-md-font-weight);
line-height: var(--smbk-typography-label-md-line-height);
letter-spacing: var(--smbk-typography-label-md-letter-spacing);
}
.smbk-accordion--size-sm .smbk-accordion__caret {
font-size: var(--smbk-size-icon-sm);
}
.smbk-accordion--size-sm .smbk-accordion__content {
padding: 0 var(--smbk-space-inset-lg) var(--smbk-space-inset-md);
font-family: var(--smbk-typography-body-sm-font-family);
font-size: var(--smbk-typography-body-sm-font-size);
font-weight: var(--smbk-typography-body-sm-font-weight);
line-height: var(--smbk-typography-body-sm-line-height);
letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}
.smbk-accordion--look-flat.smbk-accordion--size-sm .smbk-accordion__summary {
padding-inline: 0;
}
.smbk-accordion--look-flat.smbk-accordion--size-sm .smbk-accordion__content {
padding: 0 0 var(--smbk-space-inset-xl);
}
/* ---- motion: an item unfolds with the expand purpose and folds back the same way. Its height grows
from the heading row to its own while the content fades in from the purpose's slight blur, and
the caret turns with it. The React component moves it, the same in every browser, and marks the
block data-motion once it runs. While an item moves (data-moving) it is clipped, with room to
spare for a focus ring's reach, and the foot of what shows fades out over a spacing step, or
over what is still hidden when that is less, so the text never meets the edge with a hard cut.
Markup written by hand is moved by the stylesheet alone where the browser can animate to a
natural height, without the fading foot, and opens at once elsewhere. Under reduced motion the
height lands at once and the fade and the blur still play: an item opens and then fades in, and
fades out and then shuts. ---- */
/* How much of the content shows while its item moves, from its top: the React component moves it with the height. */
@property --smbk-accordion-reveal {
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}
.smbk-accordion__caret {
transition: transform var(--smbk-motion-expand-duration) var(--smbk-motion-expand-easing);
}
.smbk-accordion__item[data-moving] {
overflow: clip;
overflow-clip-margin: calc(var(--smbk-focus-ring-width) + var(--smbk-focus-ring-offset));
}
/* The mask reads only how opaque its paint is, so the text color stands in for "shown". */
.smbk-accordion__item[data-moving] > .smbk-accordion__content {
mask-image: linear-gradient(currentcolor calc(var(--smbk-accordion-reveal) - min(var(--smbk-space-16), 100% - var(--smbk-accordion-reveal))), transparent var(--smbk-accordion-reveal));
}
@supports (interpolate-size: allow-keywords) {
.smbk-accordion:not([data-motion]) > .smbk-accordion__item {
interpolate-size: allow-keywords;
}
.smbk-accordion:not([data-motion]) > .smbk-accordion__item::details-content {
block-size: 0;
opacity: 0;
filter: blur(var(--smbk-motion-expand-blur));
overflow: clip;
overflow-clip-margin: calc(var(--smbk-focus-ring-width) + var(--smbk-focus-ring-offset));
transition-property: block-size, opacity, filter, content-visibility;
transition-duration: var(--smbk-motion-expand-duration);
transition-timing-function: var(--smbk-motion-expand-easing);
transition-behavior: allow-discrete;
}
.smbk-accordion:not([data-motion]) > .smbk-accordion__item[open]::details-content {
block-size: auto;
opacity: 1;
filter: none;
}
/* Under reduced motion the height steps: at once as an item opens, and only once it has faded as it closes. */
@media (prefers-reduced-motion: reduce) {
.smbk-accordion:not([data-motion]) > .smbk-accordion__item::details-content {
transition-duration: 0s, var(--smbk-motion-expand-duration), var(--smbk-motion-expand-duration), var(--smbk-motion-expand-duration);
transition-delay: var(--smbk-motion-expand-duration), 0s, 0s, 0s;
}
.smbk-accordion:not([data-motion]) > .smbk-accordion__item[open]::details-content {
transition-delay: 0s;
}
}
}
@media (prefers-reduced-motion: reduce) {
.smbk-accordion__caret {
transition: none;
}
}
/* ---- touch: the heading row takes an invisible press area ----
On a touch screen an invisible area, centred on it, takes the press out to the comfortable target in each direction
it falls short of it; its look, its focus ring and its place stay as drawn. */
@media (pointer: coarse) {
.smbk-accordion__summary {
position: relative;
}
.smbk-accordion__summary::after {
content: '';
position: absolute;
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
/* Flat items touch across their divider: a heading right under a closed one reaches up only to its own edge, and the
one over it reaches down under it. */
.smbk-accordion--look-flat .smbk-accordion__item:not([open]) + .smbk-accordion__item > .smbk-accordion__summary::after,
.smbk-accordion--look-flat .smbk-accordion__item:not([open]) + .smbk-accordion__divider + .smbk-accordion__item > .smbk-accordion__summary::after {
inset-block-start: 0;
}
}
/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
user's own colors. Nothing here applies outside that mode. */
@media (forced-colors: active) {
.smbk-accordion__item--disabled,
.smbk-accordion__item--disabled > .smbk-accordion__summary,
.smbk-accordion__item--disabled .smbk-accordion__caret {
color: GrayText;
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.inset.md--smbk-space-inset-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.rim--smbk-color-border-rim | light: #ffffffe6dark: #ffffff0dchanges with theme | Color | |
| radius.control.sm--smbk-radius-control-sm | 4px | Radius | |
| color.surface.default--smbk-color-surface-default | light: #f3f5f8dark: #181a1cchanges with theme | Color | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| space.inset.2xl--smbk-space-inset-2xl | comfortable: 24pxcompact: 16pxchanges with density | Spacing | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| typography.body-lg--smbk-typography-body-lg-font-family--smbk-typography-body-lg-font-size--smbk-typography-body-lg-font-weight--smbk-typography-body-lg-line-height--smbk-typography-body-lg-letter-spacing--smbk-typography-body-lg-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 20pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.text.brand--smbk-color-text-brand | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| color.text.subtle--smbk-color-text-subtle | light: #4e555edark: #9ba5b1changes with theme | Color | |
| size.icon.md--smbk-size-icon-md | 20px | Size | |
| typography.body-md--smbk-typography-body-md-font-family--smbk-typography-body-md-font-size--smbk-typography-body-md-font-weight--smbk-typography-body-md-line-height--smbk-typography-body-md-letter-spacing--smbk-typography-body-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.surface.disabled--smbk-color-surface-disabled | light: #e7ebf0dark: #373d43changes with theme | Color | |
| color.text.disabled--smbk-color-text-disabled | light: #7c8692dark: #606a75changes with theme | Color | |
| color.surface.raised--smbk-color-surface-raised | light: #f9fafbdark: #232527changes with theme | Color | |
| space.inset.lg--smbk-space-inset-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| typography.label-md--smbk-typography-label-md-font-family--smbk-typography-label-md-font-size--smbk-typography-label-md-font-weight--smbk-typography-label-md-line-height--smbk-typography-label-md-letter-spacing--smbk-typography-label-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| size.icon.sm--smbk-size-icon-sm | 16px | Size | |
| typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| motion.expand.duration--smbk-motion-expand-duration | 320ms | Motion | |
| motion.expand.easing--smbk-motion-expand-easing | cubic-bezier(0.215, 0.61, 0.355, 1) | Motion | |
| space.16--smbk-space-16 | 16px | Spacing | |
| motion.expand.blur--smbk-motion-expand-blur | 2px | Motion | |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"name": "accordion",
"block": "smbk-accordion",
"summary": "A stack of headings that each open to reveal their content, as separate cards or as a flat list divided by rules.",
"adapt": "css",
"axes": {
"look": {
"values": [
"cards",
"flat"
],
"default": "cards",
"description": "Each item as a raised card, or a flat list separated by dividers. Figma calls this \"Cards\"."
},
"size": {
"values": [
"md",
"sm"
],
"default": "md",
"description": "md for a page; sm for a narrow column such as a settings panel, with smaller text, tighter padding and a smaller caret."
}
},
"flags": {},
"parts": [
"item",
"summary",
"title",
"caret",
"content",
"divider"
],
"figmaNodeId": "4128:20419"
}