Skip to content
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Getting startedWebReactSwiftUIJetpack ComposeFlutterReact NativePresetsYour own design systemUsing Tanrei with AI agentsBrandDesign tokensDesign systems and customizationTheme StudioColorTypographyIconsShapeElevationMotionInteraction statesBehaviourLayoutAccessibilityWordsColor tokensTypescale tokensShape tokensElevation tokensMotion tokensState tokensSpace and measure tokensComponentsButtonMain action buttonSegmented buttonButton groupButton with menuToolbarCopy buttonCheckboxRadio buttonSwitchChipMenuSliderSlider fieldCalendarDateTimePickerField groupRadio groupCheckbox groupSelectSelect fieldMenubarFile uploadFile picker AdvancedDrop zoneText input AdvancedText fieldChip fieldFieldCombobox AdvancedCombobox fieldNumber input AdvancedNumber fieldColor input AdvancedColor fieldOne-time-code input AdvancedOne-time-code fieldEmbedded text input AdvancedChat composerCardDividerModal layer AdvancedDialogListList rowBottom sheetSide sheetCarouselCode blockTablePopoverStepsAccordionDescription listFigureApp shellPage headerTree viewTimelineImage previewHandleResizable frameScroll areaBadgeProgress indicatorMeterNumber tickerLoading indicatorSnackbarTooltipKeyboard shortcutTagInline codeCalloutAvatarEmpty stateTag groupSkeletonFile itemSwatchMarquee textTabsTab viewApp barNavigation barNavigation drawerSearchLinkPaginationPrevious and next linksStepperBreadcrumbsTable of contentsLink cardFAB menuStatusHow Tanrei is builtContributingLicense and attribution
HomeComponentsAccordion

Accordion

Content that a row shows and hides in place. One item is a single disclosure; several are a set whose items open by themselves or one at a time.

content_copyView as Markdown
Category
Containment
Tokens
comp.accordion
Level
Base

Overview

Web
tanrei-accordion
schedule
SwiftUI
TanreiAccordion
radio_button_unchecked
Jetpack Compose
TanreiAccordion
radio_button_unchecked
Flutter
TanreiAccordion
radio_button_unchecked
React Native
Accordion
radio_button_unchecked

Generated from docs/status/status.json, specs/accordion.spec.json

A help page's questions: the outlined look, one item open

What is a design token? A named design decision: a color, a size, a duration. Components read tokens and never a literal value.

Which platforms are covered? Web Components, SwiftUI, Jetpack Compose, Flutter and React Native, from one set of tokens and specs.

How do I change the look? With a layer: a file that sets the dials and, where a product needs it, single tokens.

An accordion folds parts of a page under rows that show and hide them: the answers of a help page, the sections of a settings screen, the advanced part of a form. One item alone is a disclosure, and it is the same component. A group of destinations that opens inside a navigation drawer is the drawer’s own group; a choice among views of one place is tabs.

On the web it is <tanrei-accordion> around your own native <details> elements. The element has no shadow root, so each item opens and closes, takes focus, carries its expanded state and keeps its closed content in the page before any script runs. The other platforms have not implemented it yet.

Usage

<link rel="stylesheet" href="@tanrei/web/icons.css" />
<link rel="stylesheet" href="@tanrei/web/accordion.css" />

<tanrei-accordion variant="outlined">
  <details open>
    <summary>
      <span class="tanrei-icon" aria-hidden="true">palette</span>
      Colors
      <span data-supporting-text>Roles, palettes and the six schemes</span>
      <span data-trailing-text>48 roles</span>
    </summary>
    <p>Every color a component reads is a role.</p>
  </details>
  <details>
    <summary>Typefaces</summary>
    <p>One stack for text, one for code.</p>
  </details>
</tanrei-accordion>

<script type="module">
  import '@tanrei/web/components/accordion';
</script>
import { Accordion } from '@tanrei/react';
import '@tanrei/web/icons.css';
import '@tanrei/web/accordion.css';

<Accordion density="compact" exclusive>
  {sections.map((section) => (
    <details key={section.id} onToggle={(event) => track(section.id, event.currentTarget.open)}>
      <summary>
        {section.name}
        <span data-trailing-text>{section.count}</span>
      </summary>
      {section.content}
    </details>
  ))}
</Accordion>

@tanrei/web/accordion.css draws the whole look and every state before any script runs. The element adds the same stylesheet to the page by itself, so a page that loads only the script still gets a drawn accordion, a moment later. The chevron and an item’s icon are glyphs of the icon set: they need @tanrei/web/icons.css and the icon font.

What you write

Part On the web
An item A <details>. open is its own attribute and property.
The header Its <summary>, first in the item. It is one button.
The label The summary’s text.
A label that is a heading, or holds inline content One element in the text’s place: <summary><h3>Billing</h3></summary>, or <summary><span>Use <code>tokens.css</code></span></summary>.
The supporting text An element with data-supporting-text inside the summary.
The trailing text An element with data-trailing-text inside the summary.
The icon A <span class="tanrei-icon" aria-hidden="true"> first in the summary, holding the glyph’s name.
The content Everything after the summary: text, your own blocks, any component, another accordion.
A disabled item <summary aria-disabled="true" tabindex="-1">.
An item’s event The item’s own toggle. It does not bubble; event.newState is "open" or "closed".

The browser sends toggle for every change of open, including the ones your own code makes and the one it makes for a <details open> it has just parsed. Code that sets open itself knows its own changes by the state it set.

Markdown

A Markdown renderer passes <details> through as HTML. Wrap one or several in <tanrei-accordion> in the source, or at build time as the Table page shows for tables.

Specs

Compact, with a second line and a value at the end: folds in a panel

TextBody, labels and headingsInter, Noto Sans JP The font stack, in order.

CodeCode blocks and inline codeRoboto Mono The font stack, in order.

Inherited rules12 Twelve rules come from the layers below this one.

One open at a time, each item in an edge of its own

Colors Roles, palettes and the six schemes.

Notifications Email, push and the weekly digest.

Account Your name, your address and your password.

Anatomy

Part Description
1 accordion The column that holds the items. It draws nothing of its own in the plain look and adds no space around the items; in the outlined look with a line between the items it carries the one edge around them all. It has no role and no name.
2 item One header and the content it shows and hides (an item of the item kind). Open or closed by itself; in the outlined look with a gap between the items it carries an edge of its own.
3 container An item's header: the row across the item's width that opens and closes it. One button. It carries hover, focus and press and is the hit area; it has no fill at rest in the default system.
4 icon · optional A glyph of the system's icon set at the start of the header, by name. Decorative, and the same open and closed.
5 label The item's name and the header's accessible name. Plain text unless the product gives a heading; it wraps and is never cut.
6 supporting-text · optional A line under the label that says what the item holds, quieter than the label. It wraps.
7 trailing-text · optional A short value before the chevron, readable without opening the item: a count, the present value of what the item holds. One line.
8 chevron The icon set's expand_more at the end of the header, turned half a turn while the item is open: the only mark of open and closed. Hidden from assistive technology, which is given the state in words.
9 content What the item shows while open, under its header and inset to the header's first part: the product's content, from one sentence to fields, a table or another accordion. Not drawn, not focusable and not exposed while the item is closed, and still part of the document where the platform has one.
10 outline · optional The outlined look's edge: one hairline with rounded corners around the whole accordion, or around each item where the items are parted by a gap.
11 divider · optional A hairline across the accordion between two items, where the items are ruled by a line.
12 focus-ring Gap-then-ring indicator shown for keyboard focus of a header, drawn inside the header's row.

Properties

Property Type Default Description
variant plain | outlined "plain" How the accordion stands on its surface. plain: rows on the surface, with nothing around them; for content in the flow of a page or a panel. outlined: bounded by one hairline with rounded corners; for a set that should read as one block among others, and on a surface where rows alone would be lost. A design system may add values.
density regular | compact "regular" How much room a header takes, in a table's words. regular (a 56 header with a 16 label in the default system) is for a page a person reads: the questions of a help page, the sections of a long form. compact (a 48 header with a 14 label, a navigation drawer row's) is for a panel among controls: settings, an inspector, a card. A design system may add values.
separation auto | divider | gap "auto" How an item is told from the item next to it. divider: one hairline between them; in the outlined look the items share one edge. gap: empty space and no line; in the outlined look each item has an edge of its own. Auto takes comp.accordion.separation, the design system's answer (divider in the default system).
exclusive boolean false Whether opening an item closes the one that was open, so that at most one item of this accordion is open. False, the default, lets every item open and close by itself: a reader may compare two answers, and nothing moves that they did not touch. True is for items that exclude each other (the steps of one choice). An open item can always be closed again, leaving none open.

Items

The kinds of item the component repeats. A fixture lists its items in order; each platform names each kind in its own idiom.

item

One header and the content it shows and hides. The fixture's content is the text of its content.

Property Type Default Description
label string "" Its name, drawn in its header and used as the header's accessible name. Plain text, never a heading; the label slot takes its place where the product needs a heading of its own level or inline content. Required unless the label slot is filled.
supportingText string "" A line under the label that says what the item holds ("Email, push and the weekly digest"), read as the header's description. Empty draws the label alone.
trailingText string "" A short value before the chevron that can be read without opening the item: a count, the present value. One line; read after the supporting text as the header's description.
icon string "" The name of a glyph in the system's icon set, drawn at the start of the header. Decorative.
open boolean false Whether its content is shown under its header. Set by the product for an item that starts open; the web sets it itself when the user opens or closes the item, and a controlled platform leaves it to the caller.
disabled boolean false Makes its header unavailable: it keeps its place and its open, draws its disabled look, cannot be toggled and leaves the focus order. The content of an open disabled item stays shown and as usable as the product made it.
Slot Description
label The product's own label content in place of the label prop: text with inline content (a code span, a tag), or a heading of the level the page's outline needs. The accordion draws it in the label's style and changes neither its role nor its level. No control.
default The content: text, or the product's own blocks (paragraphs, fields, a table, any component, another accordion). The accordion insets it and gives it no style.
Event Description
toggle The item opened or closed: by the user from its header, by the accordion closing it for another (exclusive), or by the platform's search finding text in it; carries its new open state. Emitted exactly once per change, when the change begins.
Spec Web SwiftUI Jetpack Compose Flutter React Native
Item details TanreiAccordionItem TanreiAccordionItem TanreiAccordionItem AccordionItem
label · Property summary title label label label
supportingText · Property data-supporting-text supportingText supportingText supportingText supportingText
trailingText · Property data-trailing-text trailingText trailingText trailingText trailingText
icon · Property tanrei-icon icon icon icon icon
open · Property open isExpanded expanded expanded open
disabled · Property aria-disabled disabled enabled enabled disabled
toggle · Event toggle isExpanded onExpandedChange onExpansionChanged onToggle
Notes A native <details> whose first child is its <summary>. The summary's content is the label (the label slot): text, inline content, or a heading (<summary><h3>…</h3></summary>, which HTML allows). Inside the summary, an element with data-supporting-text holds the supporting text and one with data-trailing-text the trailing text; a .tanrei-icon element first in the summary is the icon. open is the element's own attribute and property, and toggle its own event (not bubbling; event.newState is open or closed), which the browser also sends when the product changes open — a product that sets open itself tells its own changes by the state it set. A disabled item is written <summary aria-disabled="true" tabindex="-1">: the stylesheet draws it and takes the pointer from it, tabindex takes it from the focus order, and the script refuses what is left. TanreiAccordionItem(_ title: String, supportingText: String = "", trailingText: String = "", icon: String? = nil, isExpanded: Binding<Bool>) { content }, as SwiftUI's DisclosureGroup binds its state and as TanreiNavigationDrawerGroup does; toggling writes the binding. A second initializer takes the label as a @ViewBuilder (the label slot; .accessibilityAddTraits(.isHeader) is the product's). disabled follows .disabled(_:). TanreiAccordionItem(label, expanded, onExpandedChange, supportingText = "", trailingText = "", icon = null, enabled = true, labelContent = null) { content }, the names of TanreiNavigationDrawerGroup; the header has Modifier.semantics { expand() / collapse() } and the expanded state. enabled is the inverse flag; labelContent is the label slot. TanreiAccordionItem(label:, supportingText: '', trailingText: '', icon:, expanded:, onExpansionChanged:, enabled: true, labelWidget:, child:), controlled, with the callback name of TanreiNavigationDrawerGroup; the header carries Semantics(button: true, expanded:). labelWidget is the label slot. <AccordionItem label="…" open={…} onToggle={…}>…</AccordionItem>, with the names of NavigationDrawerGroup; controlled when open is given, and defaultOpen otherwise. The label slot is the labelContent prop; the content is children. The header sets accessibilityRole="button" and accessibilityState={{ expanded, disabled }}.

States

Behavior

Requirements in EARS form. Every platform implementation is tested against these ids.

ID Requirement
ACC-1 THE accordion SHALL lay out its items in one column, in the order given, each as wide as the accordion, and the accordion as wide as the layout it is placed in gives it: in a page, in a card, in a dialog, in a panel and in another item's content alike, with its start edge on the start edge of what is around it. It SHALL bring no outer space of its own (PRS-15: the space between it and its neighbours is its container's), SHALL have no minimum width, no maximum width and no height of its own, and SHALL NOT scroll: it is as tall as its headers and its open content, and content that cannot shrink scrolls inside its own box. An accordion of one item is a single disclosure and SHALL differ in nothing from an item of a set.
ACC-2 THE accordion SHALL draw each item as a header and, WHILE the item is open, its content below the header (ACC-8). A header is one row across the item's whole width, at least as high as its density gives (comp.accordion.container.height.regular for regular and comp.accordion.container.height.compact for compact) and growing with its text, padded at its start and end by comp.accordion.container.padding-inline.regular for regular and comp.accordion.container.padding-inline.compact for compact and above its first line and below its last by comp.accordion.container.padding-block.regular for regular and comp.accordion.container.padding-block.compact for compact. Inside it SHALL lay out, from the start: the icon, where the item has one (ACC-5); the text, a column that takes the remaining width and holds the label (ACC-3) and under it the supporting text (ACC-4); the trailing text, where it has one (ACC-4); and the chevron (ACC-6); with comp.accordion.container.gap between each two of them. THE accordion SHALL centre the icon, the trailing text and the chevron on the label's first line and keep them there however tall the text grows, and any height a header has beyond its text SHALL be shared equally above and below it.
ACC-3 THE accordion SHALL draw an item's label in the text style of its density (comp.accordion.label.typescale.regular for regular and comp.accordion.label.typescale.compact for compact) and the label color of its variant (comp.accordion.plain.label.color for plain, comp.accordion.outlined.label.color for outlined); the label SHALL wrap and SHALL never be cut. WHERE the item's label slot holds content THE accordion SHALL draw that content in the label's place, style and color instead of the label, and SHALL change neither its role nor its level: a label given as text is plain text and never a heading, and a label the product gives as a heading stays a heading of the level the product gave it, drawn as every other label is. The label slot SHALL hold text, inline content or a heading and no control: IF it holds a link, a button or a field THE accordion SHALL still render it and in debug builds report that a header is one button and holds no other. THE accordion SHALL set the label's letter-spacing by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them.
ACC-4 WHILE an item's supportingText is not empty THE accordion SHALL draw it under the label, in the text style of its density (comp.accordion.supporting-text.typescale.regular for regular and comp.accordion.supporting-text.typescale.compact for compact) and the supporting-text color of its variant (comp.accordion.plain.supporting-text.color, comp.accordion.outlined.supporting-text.color); it SHALL wrap and SHALL never be cut, and the header SHALL grow to hold it. WHILE an item's trailingText is not empty THE accordion SHALL draw it on one line between the text and the chevron, in the text style of its density (comp.accordion.trailing-text.typescale.regular for regular and comp.accordion.trailing-text.typescale.compact for compact) and the trailing-text color of its variant (comp.accordion.plain.trailing-text.color, comp.accordion.outlined.trailing-text.color); it SHALL NOT wrap, and WHILE it would take more than half of the header's inner width it SHALL be cut at the end of its own direction with an ellipsis, so that the label always keeps at least half.
ACC-5 WHILE an item's icon is not empty THE accordion SHALL draw that glyph of the system's icon set at the start of the header, in a box of comp.accordion.icon.size.regular for regular and comp.accordion.icon.size.compact for compact, in the icon color of its variant (comp.accordion.plain.icon.color, comp.accordion.outlined.icon.color), with sys.icon.family.unselected and sys.icon.fill.unselected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, and SHALL keep it from assistive technology. WHILE icon is empty THE accordion SHALL draw no glyph and keep no room for one: the label starts at the start padding. The icon SHALL be the same glyph whether the item is open or closed.
ACC-6 THE accordion SHALL draw a chevron at the end of every header: the icon set's expand_more in a box of comp.accordion.chevron.size.regular for regular and comp.accordion.chevron.size.compact for compact, in the chevron color of its variant (comp.accordion.plain.chevron.color, comp.accordion.outlined.chevron.color), drawn as ACC-5 draws a glyph, turned half a turn WHILE the item is open, and kept from assistive technology, which is given the item's state in words (ACC-24). The chevron SHALL be the only thing about a header that shows whether its item is open: the header's container, label, texts and icon SHALL be the same open and closed. A disabled item SHALL keep its chevron.
ACC-7 THE accordion SHALL draw an item's open as the state of that item alone: WHILE open is true its content is shown under its header, and WHILE open is false its content is not drawn, takes no space, cannot take focus and is not exposed to assistive technology. WHEN the accordion first appears it SHALL draw every item as its open says, in its first frame, without the motion of ACC-13 and without emitting toggle. A closed item's content SHALL stay what it was: on a platform that keeps what it does not draw (the web, where the content stays in the document, a field in it keeps its value and is still sent with its form) THE accordion SHALL NOT remove, rebuild or reset it when the item closes or opens; on a platform that builds only what is drawn, the content is built again when the item opens, and state that must outlive a close is the product's to hold.
ACC-8 WHILE an item is open THE accordion SHALL draw the content of its default slot under its header, inset at its start and end by the header's inline padding (comp.accordion.container.padding-inline.regular for regular and comp.accordion.container.padding-inline.compact for compact), so that it starts under the first part of the header, with no space above it and comp.accordion.content.padding-block-end.regular for regular and comp.accordion.content.padding-block-end.compact for compact below it. The content is the product's: THE accordion SHALL give it no text style and no color, which it takes from where the accordion stands, SHALL add no style to anything inside a component placed in it (PRS-16), and SHALL neither cut it nor limit its height. The content SHALL be held whole by the item: a margin of its first or last part is space inside the content, under the header and above the space below the content, and SHALL NOT reach out of the item. WHILE the item is open it is one target (ACC-36), and its hovered, focused and pressed states are the whole item's: THE accordion SHALL draw the container color of that state under the content as under the header (ACC-17) and SHALL give the content the state's opacity, and SHALL change nothing else about it — not its box, its text style or its color.
ACC-9 WHILE variant is plain THE accordion SHALL draw no edge and no fill of its own: its headers are rows on the surface it stands on, in the colors of the plain look (comp.accordion.plain.container.color and the plain tokens ACC-3 to ACC-6 name). WHILE variant is outlined THE accordion SHALL draw an edge comp.accordion.outline.width wide in comp.accordion.outline.color with corners comp.accordion.outline.shape, inside the size of what it bounds and without moving anything in it — around the whole accordion WHILE its separation is divider, and around each item WHILE it is gap (ACC-10) — SHALL clip the headers' containers, their state layers and the content to that edge's corners, and SHALL draw its headers in the colors of the outlined look (comp.accordion.outlined.container.color and the outlined tokens ACC-3 to ACC-6 name). The variant SHALL change the edge and these colors and nothing else.
ACC-10 THE accordion SHALL tell an item from the item next to it as its separation names: WHILE separation is auto, as comp.accordion.separation names, which follows sys.behavior.separation unless a design system sets it apart; WHILE separation is divider or gap, as that value names, whatever the tokens say. WHILE it is divider THE accordion SHALL draw one line of comp.accordion.divider.thickness in comp.accordion.divider.color across its whole width between every two items that follow each other, under the first one's content WHILE that item is open and under its header WHILE it is closed, without adding to an item's height, and SHALL draw no line above the first item or below the last. WHILE it is gap it SHALL leave comp.accordion.item.gap of empty space between every two items that follow each other, SHALL draw no such line, and SHALL leave no such space above the first item or below the last. WHEN separation, or the token it follows, changes THE accordion SHALL draw the other form at once, without animation, keeping every item's open; WHEN separation names a value the accordion does not define it SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README.
ACC-11 WHEN the user activates the header of an item that is not disabled by pointer, touch, Enter or Space THE accordion SHALL emit that item's toggle exactly once with its new open state, and on the web SHALL set the item's open itself (a controlled platform leaves it to the caller). WHEN focus is on the header of a closed item and the user presses the arrow key toward the end of the line (Right in a left-to-right layout) THE accordion SHALL open the item in the same way, and on the header of an open item the arrow key toward the start SHALL close it; the arrow key that would do neither, and every other key, SHALL be left to the platform. THE accordion SHALL NOT move focus from one header to another by an arrow key: every header is a stop of its own in the focus order, as a navigation drawer's group rows are. Activating a header SHALL NOT move focus, and focus SHALL stay on the header while its item opens and closes.
ACC-12 WHILE exclusive is true THE accordion SHALL keep at most one of its own items open: WHEN an item opens, by the user's act, by the product setting its open or by ACC-16, THE accordion SHALL close the item that was open, emitting that item's toggle exactly once with false and on the web setting its open itself (a controlled platform leaves it to the caller), in the same motion as the one that opens (ACC-13). An open item SHALL still close by its own header, leaving none open. The items of an accordion placed inside an item's content SHALL NOT count: each accordion keeps its own. WHEN the accordion first appears, or exclusive becomes true, with more than one item open THE accordion SHALL draw the first of them in order open and the others closed, on the web setting their open itself, and in debug builds report that an exclusive accordion was given several open items. WHILE exclusive is false every item SHALL open and close by itself.
ACC-13 WHEN an item's open changes THE accordion SHALL reveal or hide its content from the header downward, the item growing or shrinking in height and everything after it moving with its lower edge, and SHALL turn the chevron, over comp.accordion.motion.disclosure-duration with comp.accordion.motion.disclosure-easing, at once where motion is reduced. The content SHALL be laid out once, at the width and height it has when shown, and the motion SHALL only change how much of it is seen: its lines SHALL NOT wrap again, its parts SHALL NOT be resized and nothing in it SHALL be measured again from frame to frame. The item's height SHALL go from its height at rest before the change to its height at rest after it with no jump at the start or at the end, whatever margins the content's first and last parts have. Content that is being hidden SHALL stay drawn until the motion ends and SHALL leave the focus order and assistive technology when the motion begins. WHEN open changes again before the motion ends THE accordion SHALL go on from where the motion is toward the new state, without a jump. toggle SHALL be emitted when the change begins, not when the motion ends.
ACC-14 WHEN items open and close in answer to the user's act on a header THE accordion SHALL keep that header where it is in the view, as far as the scrolling of what holds it allows, and move only what follows it: IF another item above it closes at the same time (ACC-12) THE accordion SHALL hold the activated header's place while the content above it goes, so that the row under the pointer or the finger does not move away from it. It SHALL NOT scroll for any other reason: an item that opens SHALL NOT be scrolled into view, and its content MAY reach below the view's edge.
ACC-15 WHEN an item closes while keyboard focus is inside its content THE accordion SHALL move focus to that item's header or, WHILE the item is disabled, leave focus to the platform's default; WHEN it closes while focus is elsewhere it SHALL NOT move focus.
ACC-16 WHERE the platform can search the text of what is shown and reports a match that lies in content it does not draw, or goes to a place inside such content (on the web, the browser's find-in-page and a link to a fragment of the page), WHEN the match or the place is inside a closed item THE accordion SHALL open that item, and every closed item of an accordion that holds it, at once and without the motion of ACC-13, SHALL emit each one's toggle exactly once with true, SHALL apply ACC-12 to each, and SHALL leave bringing the match into view to the platform; it SHALL do so for a disabled item too, whose header is unavailable and whose content is still part of the page. On a platform that has no such search or cannot report such a match, a closed item's content is not found, and THE accordion SHALL NOT open anything for it.
ACC-17 WHILE an item's header is hovered, keyboard-focused or pressed THE accordion SHALL render that header's container, label, supporting text, trailing text, icon, chevron and opacity from the component tokens of its variant in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. Hover and press over any part of the header's row SHALL answer on the whole container. WHILE the item is open THE accordion SHALL draw these states on the whole item, which is one target (ACC-36): the item's container — under the header and the content, to the item's edges — takes the state's container color and the header's parts its colors, whether the pointer is over the header or over the content where a press would close the item (ACC-36); over a descendant of the content that takes a press itself, the item SHALL show no hovered state of its own. The edge and the lines SHALL NOT change under a pointer, a press or focus.
ACC-18 WHEN an item's header receives keyboard focus THE accordion SHALL show the focus ring inside the header's container, following comp.accordion.container.shape, while the item is closed, and WHILE it is open around the whole item, which is one target (ACC-36) — inside the item's edge, around the header and the content together, following the item's corners (the outlined look's at the set's corners, none elsewhere) — so that neither a neighbouring item, an outlined edge nor a scrolling ancestor cuts it, without animation; focus gained by pointer or touch SHALL NOT show it.
ACC-19 WHERE a header is lower than the minimum target THE accordion SHALL make its container at least sys.target.touch tall for touch input and sys.target.pointer tall for fine pointers, because rows that touch cannot extend a hit area over each other; a platform that cannot tell the input type SHALL use sys.target.touch. The whole row, from the item's start edge to its end edge, its paddings, icon, texts and chevron included, SHALL be the header's hit area; WHILE the item is open, its content, down to the item's lower edge, SHALL be part of the item's hit area too: the open item is one target, as ACC-36 says.
ACC-20 WHILE an item's disabled is true THE accordion SHALL render its header's container, label, supporting text, trailing text, icon, chevron and opacity from the disabled component tokens of its variant, SHALL NOT emit its toggle for a press, a key or a touch, SHALL NOT make its header focusable and SHALL expose the header as disabled. The item SHALL keep its place and its open: a disabled item that is open still shows its content, which stays as usable as the product made it, and the product MAY still set its open. A header that draws no fill at rest SHALL draw none while disabled.
ACC-21 WHEN an item's header is pressed by pointer, touch, Enter or Space THE accordion SHALL show its pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
ACC-22 WHILE an item's header is pressed THE accordion SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer, clipped to the container, from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole header and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; label, supporting text, trailing text, icon, chevron and opacity still come from the pressed tokens. Under none every part stays with the state under the press. WHILE the item is open the press is the whole item's (ACC-36), by its header or its content: overlay paints the pressed container color on the whole item, ripple grows the layer over the whole item from the point of contact (from the item's center for a key), and the pressed opacity applies to the content as to the header. The ripple grows over comp.accordion.motion.feedback-duration with comp.accordion.motion.state-easing, fades over comp.accordion.motion.state-duration with comp.accordion.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
ACC-23 THE accordion SHALL ease its headers' container corners and the outlined look's corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show a header's keyboard focus as sys.behavior.focus-style names (ring or glow), around the header while its item is closed and around the whole item while it is open (ACC-18); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
ACC-24 THE accordion SHALL expose each item's header as one button named by its label (the text of the label slot's content where the product gave one), described by its supporting text and then its trailing text, with its expanded or collapsed state, the disabled state where it applies, and its icon and chevron hidden; and SHALL expose an open item's content after its header in reading order, as the content it is. On the web the header is the summary of a details element, which the browser exposes as a button with the expanded state; on Apple platforms a button whose value is expanded or collapsed, as a disclosure group reads; on Android the expanded state with the expand and collapse actions; in Flutter the expanded semantics flag; in React Native accessibilityState.expanded. THE accordion SHALL NOT make an item's content a landmark or a named region, and the accordion itself SHALL have no role and no name: a page with several items would otherwise list each of them among its regions. WHILE an item's label is empty and its label slot holds nothing THE accordion SHALL still render it, and in debug builds report that its header has no accessible name.
ACC-25 WHEN an item opens or closes THE accordion SHALL let the header's changed state be the only thing assistive technology is told: it SHALL announce nothing itself, SHALL have no live region, and SHALL NOT move keyboard or screen-reader focus into the content.
ACC-26 WHILE the text size is enlarged THE accordion SHALL grow its label, its supporting text and its trailing text with it, keep its paddings and gaps, wrap the label and the supporting text, and grow each header in height; it SHALL keep the icon's and the chevron's boxes at their token sizes, both still centred on the label's first line (ACC-2), SHALL cut nothing but a trailing text as ACC-4 says, SHALL set no fixed height, SHALL lose no content when the reader raises line height, paragraph spacing, letter spacing or word spacing, and SHALL need no horizontal scrolling at a width of 320 points with text at 200 %.
ACC-27 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE accordion SHALL draw the outlined look's edge and the lines of ACC-10 in the platform's text color; its labels, texts, icons and chevrons in that text color; a disabled header in the platform's color for disabled text; and a header's hover, press and focus in the platform's own colors for a button and for focus, with the focus ring still inside the header. Open and closed SHALL still be told apart by the chevron's turn, which no color carries.
ACC-28 WHERE the layout direction is right-to-left THE accordion SHALL mirror the order of a header's parts and apply its paddings by logical start and end: the icon at the right, the text after it, the trailing text and the chevron at the left; the content SHALL be inset by the same paddings. The arrow keys of ACC-11 SHALL follow the direction: Left opens and Right closes. The chevron's half turn SHALL be the same in both directions: a glyph that points down and then up has no mirrored form. A ripple SHALL still grow from the point of contact.
ACC-29 THE accordion SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the accordion is shown THE accordion SHALL draw itself for the new direction at once, without a motion of its own, keeping every item's open and its focus.
ACC-30 WHERE the layout direction is right-to-left THE accordion SHALL draw an item's icon mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size and color nor the order ACC-28 gives. THE accordion SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name. The chevron (expand_more, ACC-6) is not marked and is never mirrored.
ACC-31 THE accordion SHALL lay out an item's label, its supporting text and its trailing text each as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right name, a number with its unit and a version keep their own order in a right-to-left layout and reorder nothing beside them; THE accordion SHALL still place each and align its lines by the layout direction, not by the text's own, and SHALL cut a trailing text that does not fit (ACC-4) at the end of its own direction.
ACC-32 WHERE an item's content holds another accordion THE accordion SHALL treat it as content like any other: the inner accordion is laid out inside the item's inset (ACC-8), which is what shows its depth; it has its own variant, density, separation and exclusive; its headers answer the keys of ACC-11 for its own items only; and WHEN the outer item closes and opens again the inner items SHALL be as their open said. IF accordions nest more than three levels deep THE accordion SHALL still render them, and in debug builds report the depth.
ACC-33 WHILE the system's icon set cannot yet draw a glyph of the accordion (its font is still loading) THE accordion SHALL draw nothing in that glyph's place, never the glyph's name as text, SHALL keep the glyph's box, and SHALL draw the glyph once the set can; a header SHALL open and close its item all the same.
ACC-34 WHEN variant or density names a value that the active design system does not have THE accordion SHALL render with the default value (the default variant's or density's tokens), and in debug builds report the unknown value.
ACC-35 IF the accordion has no items THE accordion SHALL draw nothing, take no space and expose nothing. IF an item has no content THE accordion SHALL still render its header, which opens onto nothing but the space under the content (ACC-8), and in debug builds report that the item shows and hides nothing.
ACC-36 WHEN the content of an open item is pressed and released with the primary mouse button, a touch or a pen, without the pointer moving further than the platform's touch slop between the two, THE accordion SHALL close that item exactly as a press on its header does: one toggle with false, the motion of ACC-13 and the header kept where it is as ACC-14 says, with exclusive (ACC-12) closing nothing else. The item's whole frame is its hit area while it is open: the content from the item's start edge to its end edge and down to its lower edge, the space under the content (ACC-8) included, the way a whole card answers a press while the controls inside it keep their own. THE accordion SHALL NOT close the item IF the press lands on or inside something that answers a press itself — a link, a button, a field, a select, a label, a summary, an editable element, an element with a tab index, an element with a widget role, a custom element that takes focus, or an element to which the product gave the pointer cursor where the item's own is not (the pointer the item shows for itself does not count) — which keeps its own behaviour; IF the release ends a text selection, that is, the selection is not empty and lies in the item once the press is released; IF the pointer moved further than the slop (a drag, a scroll); IF a modifier key (Control, Command, Shift, Alt or Option) is held or a button other than the primary is used; IF the press is the second or a later press of a multiple click; WHILE the item is disabled (ACC-20); or IF the press lands inside an item of an accordion nested in the content: such a press belongs to the innermost item, so a press in a nested open item's content closes that item only, and a nested header toggles its own item only. For a pointer the open item is one target, as a whole clickable card is, and not a header and a content styled alike: WHILE a fine pointer is over the item where a press would toggle or close it — its header, or its content outside a descendant that takes a press itself — THE accordion SHALL show the hovered state on the whole item (ACC-17) and the pointer cursor; a press SHALL show the pressed state on the whole item (ACC-21, ACC-22); keyboard focus on the header SHALL be shown around the whole item (ACC-18). WHILE the pointer is over a descendant that takes a press itself, that descendant SHALL keep its own cursor, hover and press, and the item SHALL show neither its hovered state nor the pointer cursor. The content SHALL get no role, no name, no focus stop and no announcement: the header stays the one button with the expanded state, and the keyboard and assistive technology reach the item through it alone (ACC-11, ACC-24). WHERE the platform's content answers no press (on the web, without the element's script) the content SHALL show neither the pointer cursor nor a state.

Platform name mapping

Parity does not mean identical APIs. Each platform uses its own idiom for the same contract.

Spec Web SwiftUI Jetpack Compose Flutter React Native
Component tanrei-accordion TanreiAccordion TanreiAccordion TanreiAccordion Accordion
variant · Property variant variant variant variant variant
density · Property density density density density density
separation · Property separation separation separation separation separation
exclusive · Property exclusive isExclusive exclusive exclusive exclusive
Notes Form: <tanrei-accordion> is a custom element with **no shadow root**, around the product's own native <details> elements in the light DOM — <tanrei-accordion variant="outlined"><details open><summary>Colors</summary><p>…</p></details><details>…</details></tanrei-accordion>. The browser's disclosure is kept whole: it opens and closes without script, a summary is focusable and answers Enter and Space, it is exposed with its expanded state, its closed content stays in the document, find-in-page and a fragment link open it where the browser does that for a details element (ACC-16; in a browser that does not, the script listens for beforematch and hashchange where they exist, and otherwise closed content is not found, as in every details element there), and exclusive is the name attribute, which the element writes on its own <details> children with a name of its own (a nested accordion gets another, ACC-12); markup that must be exclusive without script writes name itself. Everything drawn comes from one stylesheet, @tanrei/web/accordion.css, whose selectors start at tanrei-accordion and are wrapped in :where(); it styles the page before the element is defined and without script (an element that is not yet defined is still an element), and the element adopts the same sheet into the root it is connected to, so it also works inside another component's shadow root. Props are attributes of the wrapper: variant, density, separation, exclusive. The summary is a grid (icon, text, trailing text, chevron); the browser's own marker is removed (list-style: none and the ::-webkit-details-marker rule) and the chevron is the summary's ::after in the icon font (expand_more), rotated under details[open]. The content's inset is padding on the <details> with the summary pulled back out by a negative inline margin, so no wrapper is needed around the product's content. The stylesheet alone gives the default system's whole look and every state: hover, :focus-visible with the ring as an inset outline, :active with the press-hold as a delayed transition back, the disabled look from summary[aria-disabled="true"], forced colors, and the separation through a style query on the component token's custom property with the separation attribute over it. The content's box is the details element's own ::details-content, a block formatting context (display: flow-root): the margins of the content's first and last parts stay inside it, at rest and at every height of the motion, so the height the motion starts and ends at is the height at rest (ACC-8, ACC-13). Motion (ACC-13) is CSS where the browser can move a height to auto (::details-content with block-size from 0 to calc-size(auto, size), overflow: clip, and content-visibility transitioned discretely so that closing content stays drawn until the end); elsewhere the script measures the content's height once, before the first frame, and the stylesheet moves the box between 0 and that height; where the browser has no ::details-content the item opens at once, and so it does where motion is reduced. The script reads layout from frame to frame only for an item turned back in the first half of its motion on the script's path, whose height goes on with no event to tell its end: it reads the item's height once a frame until it stands still. The package's script, where it is loaded, adds what a stylesheet cannot: the shared press primitive on the summary (ripple and dim, ACC-22), the arrow keys of ACC-11, the name of exclusive, holding the activated header's place where the browser's scroll anchoring does not (ACC-14), moving focus to the summary when an item closes around it (ACC-15), refusing a disabled summary's click, closing an open item when its content is pressed (ACC-36), and every debug report. ACC-36 is the script's alone: before the element is defined, and without script, only the summary toggles its item, and the content shows no pointer cursor and no state (they would promise a press nothing answers). Once the element is defined an open item is drawn as one target by accordion.css: the <details> paints the container color of its state (at rest the look's container color, no fill in every shipped system), the summary paints none and is not a positioning container, so its focus ring (::before, with the outer stroke as the item's own outline) and its ripple are laid out on the whole item, under the header and the content; the content's box (::details-content) takes the state's opacity. The element writes data-tanrei-hit on the item a mouse or a pen is over where a press would toggle or close it (from pointerover, by the same rule as a press), which draws it hovered and gives it cursor: pointer; over a control in the content the attribute is off, so the control's own cursor and states show and the item's none. To tell a box the product gave the pointer cursor from the content inheriting the item's, the element sets the item's cursor aside (data-tanrei-reading) while it reads the pressed element's. A press in the content is the summary's press primitive (data-pressed on the summary), drawn on the whole item, and ends when the pointer leaves the item or moves past the slop. Where the browser has no ::details-content the content's opacity is not changed. The element listens for pointerdown, pointermove and click on itself; a click closes the item when it follows a press with the primary button (any touch or pen) in that same item's content, the pointer has not moved more than 8 CSS pixels (the touch slop the sheets and the tabs use, the web naming none of its own), no modifier key is held, event.detail is 1, the document's selection is collapsed or outside the item, the innermost <details> on the event's composed path is the item itself, and nothing on that path between the target and the item is a link, a button, a field, a select, a textarea, a label, a summary, editable, a [tabindex], an element with a widget role, a custom element that delegates focus or takes it, or (the element pressed) drawn with cursor: pointer while the item, its own pointer set aside, is not. The click is decided a task after it is dispatched, so that a handler of the product's on the content is heard first: a click it cancelled (preventDefault()) closes nothing. It then sets the item's open to false, so the browser sends toggle and the motion, focus and exclusive rules run as for a press on the summary. Nothing is written into the content and no style is added to anything in it. Inside .tanrei-prose the element is an island by its tag name (PRS-16); the product makes an item's long-form content prose by its own <div class="tanrei-prose" data-measure="false">. Markdown: a renderer passes <details> through as HTML; the product wraps one or several in <tanrei-accordion>. React: Accordion and AccordionItem render the same elements. TanreiAccordion(variant:, density:, separation:, isExclusive:) { TanreiAccordionItem(…) { … } … }. The header is a button whose accessibility value is expanded or collapsed. An exclusive accordion writes false to the binding of the item it closes. Closed content is not in the view tree; state that must outlive a close is the product's (ACC-7). There is no search of the screen's text to answer (ACC-16). TanreiAccordion(variant, density, separation, exclusive) { TanreiAccordionItem(…) { … } }, items in a scope of the accordion. Controlled: an exclusive accordion calls onExpandedChange(false) on the item it closes. Closed content leaves the composition; rememberSaveable in the content keeps what the product wants kept (ACC-7). TanreiAccordion(variant:, density:, separation:, exclusive:, children: [TanreiAccordionItem(…)]). An exclusive accordion calls onExpansionChanged(false) on the item it closes. A closed item's child is not built; a PageStorageKey or the product's own state keeps what must be kept (ACC-7). <Accordion variant="outlined" exclusive> with AccordionItem children. An exclusive accordion calls onToggle(false) on the item it closes. Closed content is not mounted. Follows the app's direction where an ancestor's is not known.

Tokens

Token Aliases Resolved (light)
--tanrei-comp-accordion-chevron-size-compact sys.icon.small 20px
--tanrei-comp-accordion-chevron-size-regular sys.icon.medium 24px
--tanrei-comp-accordion-container-gap sys.space.space150 12px
--tanrei-comp-accordion-container-height-compact — 48px
--tanrei-comp-accordion-container-height-regular — 56px
--tanrei-comp-accordion-container-padding-block-compact — 12px
--tanrei-comp-accordion-container-padding-block-regular — 16px
--tanrei-comp-accordion-container-padding-inline-compact sys.space.space150 12px
--tanrei-comp-accordion-container-padding-inline-regular sys.space.space200 16px
--tanrei-comp-accordion-container-shape sys.shape.corner.none 0px
--tanrei-comp-accordion-content-padding-block-end-compact sys.space.space150 12px
--tanrei-comp-accordion-content-padding-block-end-regular sys.space.space200 16px
--tanrei-comp-accordion-disabled-opacity — 1
--tanrei-comp-accordion-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-accordion-divider-thickness sys.border.hairline 1px
--tanrei-comp-accordion-focused-opacity — 1
--tanrei-comp-accordion-hovered-opacity — 1
--tanrei-comp-accordion-icon-size-compact sys.icon.small 20px
--tanrei-comp-accordion-icon-size-regular sys.icon.medium 24px
--tanrei-comp-accordion-item-gap sys.space.space100 8px
--tanrei-comp-accordion-motion-disclosure-duration sys.motion.role.disclosure 400ms
--tanrei-comp-accordion-motion-disclosure-easing sys.motion.easing.spatial cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-accordion-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-accordion-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-accordion-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-accordion-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-accordion-outline-shape sys.shape.corner.medium 8px
--tanrei-comp-accordion-outline-width sys.border.hairline 1px
--tanrei-comp-accordion-outlined-chevron-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-outlined-container-color comp.accordion.plain.container.color #000000 · 0%
--tanrei-comp-accordion-outlined-disabled-chevron-color — #1b1b1b · 38%
--tanrei-comp-accordion-outlined-disabled-container-color — #000000 · 0%
--tanrei-comp-accordion-outlined-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-accordion-outlined-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-accordion-outlined-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-accordion-outlined-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-accordion-outlined-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-accordion-outlined-focused-chevron-color — #4b4b4b
--tanrei-comp-accordion-outlined-focused-container-color — #1b1b1b · 10%
--tanrei-comp-accordion-outlined-focused-icon-color — #4b4b4b
--tanrei-comp-accordion-outlined-focused-label-color — #1b1b1b
--tanrei-comp-accordion-outlined-focused-state-layer-color — #1b1b1b
--tanrei-comp-accordion-outlined-focused-supporting-text-color — #4b4b4b
--tanrei-comp-accordion-outlined-focused-trailing-text-color — #4b4b4b
--tanrei-comp-accordion-outlined-hovered-chevron-color — #4b4b4b
--tanrei-comp-accordion-outlined-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-accordion-outlined-hovered-icon-color — #4b4b4b
--tanrei-comp-accordion-outlined-hovered-label-color — #1b1b1b
--tanrei-comp-accordion-outlined-hovered-state-layer-color — #1b1b1b
--tanrei-comp-accordion-outlined-hovered-supporting-text-color — #4b4b4b
--tanrei-comp-accordion-outlined-hovered-trailing-text-color — #4b4b4b
--tanrei-comp-accordion-outlined-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-outlined-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-accordion-outlined-pressed-chevron-color — #4b4b4b
--tanrei-comp-accordion-outlined-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-accordion-outlined-pressed-icon-color — #4b4b4b
--tanrei-comp-accordion-outlined-pressed-label-color — #1b1b1b
--tanrei-comp-accordion-outlined-pressed-state-layer-color — #1b1b1b
--tanrei-comp-accordion-outlined-pressed-supporting-text-color — #4b4b4b
--tanrei-comp-accordion-outlined-pressed-trailing-text-color — #4b4b4b
--tanrei-comp-accordion-outlined-state-layer-color comp.accordion.outlined.label.color #1b1b1b
--tanrei-comp-accordion-outlined-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-outlined-trailing-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-plain-chevron-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-plain-container-color — #000000 · 0%
--tanrei-comp-accordion-plain-disabled-chevron-color — #1b1b1b · 38%
--tanrei-comp-accordion-plain-disabled-container-color — #000000 · 0%
--tanrei-comp-accordion-plain-disabled-icon-color — #1b1b1b · 38%
--tanrei-comp-accordion-plain-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-accordion-plain-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-accordion-plain-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-accordion-plain-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-accordion-plain-focused-chevron-color — #4b4b4b
--tanrei-comp-accordion-plain-focused-container-color — #1b1b1b · 10%
--tanrei-comp-accordion-plain-focused-icon-color — #4b4b4b
--tanrei-comp-accordion-plain-focused-label-color — #1b1b1b
--tanrei-comp-accordion-plain-focused-state-layer-color — #1b1b1b
--tanrei-comp-accordion-plain-focused-supporting-text-color — #4b4b4b
--tanrei-comp-accordion-plain-focused-trailing-text-color — #4b4b4b
--tanrei-comp-accordion-plain-hovered-chevron-color — #4b4b4b
--tanrei-comp-accordion-plain-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-accordion-plain-hovered-icon-color — #4b4b4b
--tanrei-comp-accordion-plain-hovered-label-color — #1b1b1b
--tanrei-comp-accordion-plain-hovered-state-layer-color — #1b1b1b
--tanrei-comp-accordion-plain-hovered-supporting-text-color — #4b4b4b
--tanrei-comp-accordion-plain-hovered-trailing-text-color — #4b4b4b
--tanrei-comp-accordion-plain-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-plain-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-accordion-plain-pressed-chevron-color — #4b4b4b
--tanrei-comp-accordion-plain-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-accordion-plain-pressed-icon-color — #4b4b4b
--tanrei-comp-accordion-plain-pressed-label-color — #1b1b1b
--tanrei-comp-accordion-plain-pressed-state-layer-color — #1b1b1b
--tanrei-comp-accordion-plain-pressed-supporting-text-color — #4b4b4b
--tanrei-comp-accordion-plain-pressed-trailing-text-color — #4b4b4b
--tanrei-comp-accordion-plain-state-layer-color comp.accordion.plain.label.color #1b1b1b
--tanrei-comp-accordion-plain-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-plain-trailing-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-accordion-pressed-opacity — 1
--tanrei-comp-accordion-separation sys.behavior.separation 0
--tanrei-comp-accordion-label-typescale-compact sys.typescale.label 500 14px/1.4286
--tanrei-comp-accordion-label-typescale-regular sys.typescale.heading-5 500 16px/1.5
--tanrei-comp-accordion-supporting-text-typescale-compact sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-accordion-supporting-text-typescale-regular sys.typescale.body 400 14px/1.5714
--tanrei-comp-accordion-trailing-text-typescale-compact sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-accordion-trailing-text-typescale-regular sys.typescale.body 400 14px/1.5714

Fixtures

Canonical renderings. Every platform snapshots each fixture under the same id so the results can be compared.

Generated from specs/accordion.spec.json, tokens/comp/accordion.tokens.json, design/tokens/

Guidelines

Is it an accordion

Closed content costs a press to read and cannot be seen at a glance. Fold what only some people need, and leave open what most people came for. Do not hide a form’s required fields or its errors in a closed item.

The two looks

variant Draws Use for
plain (default) Rows on the surface, with nothing around them. Content in the flow of a page or a panel.
outlined One hairline with rounded corners. A set that should read as one block among others.

Neither look fills anything at rest, and a header keeps its colors while its item is open. Open is shown by the chevron alone.

Density

density A header of one line Label Use for
regular (default) 56 16 on a line height of 24, medium A page a person reads: questions, the sections of a long form.
compact 48 14 on 20, medium A panel among controls: settings, an inspector, a card.

A header grows with its text. A compact header is a navigation drawer’s row: the same height, text style and chevron.

Telling items apart

separation plain outlined
auto (default) What the design system chose: a hairline in the default system. The same.
divider A hairline between items, none above the first or below the last. One edge around the whole set, hairlines between items.
gap Empty space and no line. Each item in an edge of its own.

One open at a time

exclusive closes the open item when another opens. Use it for items that exclude each other, such as the steps of one choice, not to keep a page short: a reader who wants two answers side by side cannot have them. The header that was pressed stays where it is in the view while the item above it closes.

On the web exclusive is the name attribute of <details>, which the element writes on its items. Markup that must be exclusive without script writes the same name on each item itself.

  • Write the label as plain text. Where the page’s outline needs the row as a heading, give a heading of the right level as the label.
  • Use the supporting text to say what an item holds, so that a person can decide without opening it.
  • Use the trailing text for a count or the present value. It is one line, and it is cut before the label is.
  • Put an action that belongs to an item into its content.

Don’t

  • Don’t put a link, a button or a switch in a header. A header is one button.
  • Don’t use color or weight to show which item is open.
  • Don’t nest accordions more than three deep.
  • Don’t open an item because something in it is invalid without telling the reader: when a form is refused, open the item that holds the first error, then move focus to the field.

Accessibility

Role
button
Labeling
Each header is a button named by its label (or by the text of the label slot's content), described by its supporting text and then its trailing text, with the expanded or collapsed state; its icon and chevron are hidden. A label given as text is plain text; a heading in the label slot keeps its level. An open item's content follows its header in reading order and has no role or name of its own. The accordion has no role and no name.
Minimum target
The hit area meets the minimum target size even when the visual size is smaller. minimum 32px · pointer 44px · spacing 8px · touch 48px

Keyboard

Keys Action
Tab / Shift+Tab Moves focus to each header that is not disabled, in order, and through the focusable content of each open item after its header. Every header is its own stop; the accordion is not one.
Enter / Space On a header, opens or closes its item. Focus stays on the header.
Right arrow / Left arrow (mirrored in right-to-left layouts) On the header of a closed item, the arrow toward the end of the line opens it; on the header of an open item, the arrow toward the start closes it. Elsewhere the keys are the platform's. As on a navigation drawer's group row.
Up arrow / Down arrow / Home / End Nothing of the accordion's: they scroll the page as they do anywhere. Focus does not rove between headers.

Notes

  • An accordion is a set of disclosures, not a tree and not tabs: a header is a button with an expanded state that shows or hides what follows it, and every header stays its own tab stop. This is the model of a navigation drawer's groups, on purpose: a group in a drawer and an item in content answer the same keys in the same way, and a person learns it once. One tab stop with roving arrow keys would make a long page of content behave like a single widget and take the arrow keys from the page.
  • WCAG 1.3.1 (info and relationships): the accordion does not decide whether a label is a heading. Most labels are names of a fold in a panel and should stay text. Where the items are the sections of a page that people navigate by heading (the questions of a help page), the product gives a heading of the right level in the label slot. Some assistive technology reads a heading inside a button as the button's name only; where the outline must not depend on that, the product puts the heading before a single-item accordion instead.
  • The content is not a region. Naming every item's content as a landmark fills the list of regions with entries a person did not ask for; six items make six regions. A product whose single disclosure is a main part of its page may give its own content element the region role and the label's name.
  • Closed content costs a press to read and cannot be seen at a glance. Do not close what most people need, do not hide a form's required fields or its errors in a closed item, and when a form is refused, open the item that holds the first error before moving focus to the field: the accordion does not open an item because something inside it is invalid.
  • exclusive moves the page: opening one item closes another, and what the reader was looking at goes. The accordion keeps the activated header where it is (ACC-14), which removes the worst of it; even so, keep exclusive for items that exclude each other. A person who wants two answers side by side cannot have them.
  • Where the platform's own search can reach closed content (the web's find-in-page), a match opens the item (ACC-16), so closing content does not hide it from someone who looks for its words. Native platforms have no such search: there, text in a closed item is found only by the product's own search, which sets the item's open.
  • A header holds no control (ACC-3). A switch, a menu button or a link beside the label would be a control inside a button: unreachable or ambiguous for assistive technology, and a press on it would also toggle the item. An action that belongs to an item goes into its content, or beside the accordion.
  • WCAG 2.5.8 (target size): a header is the whole row and at least the touch target high in both densities; rows that touch do not share or extend hit areas (ACC-19).
  • WCAG 2.1.1 (keyboard) and 4.1.2 (name, role, value): closing an item by a press on its open content (ACC-36) is a convenience for a pointer only. The header is the one control, reached by the keyboard and named with its expanded state; the content gains no role, no focus stop and no announcement, so nothing is offered to a pointer that the keyboard and assistive technology do not already have through the header. An open item is one target, as a whole clickable card is (the maintainers' decision, 2026-10-13): the region that answers a press is the region that looks it — the pointer cursor over it, the hovered and pressed states drawn on the whole item, and keyboard focus ringed around the whole item, so that hover, press and focus all show the same one target, never a header and a content styled alike. This follows the clickable-card practice: the whole card is the target's area while the controls inside it keep their own press (Bootstrap, "Stretched link"), and the pointer cursor is set by the script, since without it the style would deceive (Inclusive Components, "Cards") — without the script the content shows no pointer and no state. Where the pointer is over a control in the content, that control is the target: it keeps its own cursor and states and the item shows none, as a nested button in a card answers alone. WCAG 2.4.7, 2.4.11 and 2.4.13 (focus visible, not obscured, appearance): the ring around an open item is the system's focus ring of the same widths and colors, inside the item's edge and drawn over its content, so nothing in the item, no neighbouring item, outlined edge or scrolling ancestor covers or cuts it, and it encloses a larger area than the header's. What a press must not be is kept from the same practice: a press that ends a text selection, or that is a drag, is not taken (that card pattern tells a selection by the press's length; the accordion tells it by the selection itself and by the slop).
  • WCAG 1.4.1 (use of color): open and closed are told by the chevron's turn and by the expanded state, never by color; the header keeps its colors when open. Disabled is told by the faded header and by the disabled state.
  • WCAG 1.4.3 and 1.4.11 (contrast) in the default system: the label is on-surface and the supporting text, trailing text, icon and chevron are on-surface-variant, on the surface the accordion stands on; each is held to 4.5:1 on every surface role by the token invariants. The edge and the lines are decorative: every header is told as a button by its chevron and its states.
  • Forced colors (ACC-27): the edge and the lines take the text color and the states the platform's own; nothing about open and closed rested on a color.
  • Web: the items are the product's own `<details>` elements with a `<summary>` each, in the document, inside `<tanrei-accordion>`. They open and close, keep their place in the tab order, are found by search in the page and are read, translated and printed as the page's content before any script runs and without it; what the script adds is listed in the platform notes. Before the element is defined, and without script, the stylesheet already draws the whole look, so nothing moves when the element is upgraded.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.

Generated from specs/accordion.spec.json, tokens/sys/measure.tokens.json

What the component handles

  • Each header is the summary of a details element: one button with the expanded state, a stop of its own in the focus order. Enter and Space toggle it. The arrow toward the end of the line opens a closed item and the arrow toward the start closes an open one, following the layout direction. Up, Down, Home and End stay the page’s.
  • The header is named by its label. The element makes the supporting text and then the trailing text its description. Without the script all three are read as its name, in that order.
  • The icon and the chevron are hidden from assistive technology.
  • The content is not a region and nothing is announced: the button’s changed state is all assistive technology is told.
  • Closed content is not drawn, not focusable and not exposed, and it stays in the document: a field in it keeps its value and is sent with its form.
  • A link to a place inside a closed item opens the item, and so does the browser’s own search of the page where the browser reveals a details element for it.
  • An open item is one target, as a whole clickable card is. A press anywhere in its content closes it, as its header does. Under a pointer the whole item, header and content, shows the hovered and pressed states with the pointer cursor, and keyboard focus on the header rings the whole item. A link, a button, a field or anything else in the content that takes a press keeps its own press, cursor and states, and the item shows none while the pointer is over it. Selecting text, dragging, a press with a modifier key and a press inside a nested accordion’s item close nothing (that item’s own content closes it). The keyboard and assistive technology use the header as before, and the content gets no role and no focus stop. This needs the script: without it only the header opens and closes the item, and the content shows no pointer and no state.
  • Focus never moves when an item opens. If an item closes around the focus, focus goes to its header.
  • The focus ring is drawn inside the header’s row while the item is closed, and inside the item’s edge, around the header and the content together, while it is open, so nothing clips it.
  • The height moves over the disclosure motion and is drawn at once where motion is reduced.
  • Both densities are at least the touch target high.
  • At 200% text labels wrap, headers grow and the chevron stays on the first line.
  • In forced colors the edge and the lines are the text color, and a disabled header is the disabled text’s color.
  • A header with no name, a control in a label, an item with no content, an unknown value and more than three levels of nesting are reported in debug builds.

What you need to do

  • Decide whether a label is a heading. Most labels name a fold in a panel and stay text. The questions of a help page that people navigate by heading are headings.
  • A heading given as a label is inside the button. Some assistive technology then reads the button’s name and does not list the heading. Where the outline must not depend on that, put the heading before a single item instead.
  • Give a disabled item both aria-disabled="true" and tabindex="-1". The element adds the second where it is missing, but only once the script has run.
  • Where a label, a supporting text or a trailing text may be written in the other direction of the page, wrap the label in a <span>: each marked element then keeps its own direction. A label of bare text is laid out in the page’s direction.