Skip to content
ShineUI
Install

Accordion

Sections that take turns.

<sui-accordion><sui-accordion-item>PanelsStable

About

Stacked sections that expand one at a time, or several with multiple. Each section is a child sui-accordion-item with its own header row; this element owns the open set, routes header clicks and moves focus between headers. Reach for it when a page groups settings or details a reader opens on demand.

Usage

<sui-accordion aria-label="Basic"> <sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </sui-accordion> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-accordion"); el.expanded = []; /* number[] */ el.addEventListener("suiExpandedChange", (e) => console.log(e.detail)); </script>
import { SuiAccordion } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const expanded = []; /* number[] */ return ( <SuiAccordion aria-label="Basic" expanded={expanded} onSuiExpandedChange={(e) => console.log(e.detail)}> <sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </SuiAccordion> ); }
<script setup> import { SuiAccordion } from "@shineui/vue"; import "@shineui/core/shineui.css"; const expanded = []; // number[] const onChange = (e) => console.log(e.detail); </script> <template> <SuiAccordion aria-label="Basic" :expanded="expanded" @sui-expanded-change="onChange"><sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item></SuiAccordion> </template>
import { Component } from "@angular/core"; import { SuiAccordion } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiAccordion], template: `<sui-accordion aria-label="Basic" [(expanded)]="expanded"><sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item></sui-accordion>`, }) export class Example { expanded: number[] = []; }

Basic

Every section starts closed; expanded on an item opens it from plain HTML, and the accordion keeps its own expanded list in step. Sections take turns, so opening one closes the rest.

Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150. Indiana sales tax registered. Kentucky registration is pending. Invoices continue from INV-1043. Credit notes use the CN prefix.
<sui-accordion aria-label="Basic"> <sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </sui-accordion>

Several open

multiple lets sections stand alone. expanded is the open index list and a property, not an attribute.

Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150. Indiana sales tax registered. Kentucky registration is pending. Invoices continue from INV-1043. Credit notes use the CN prefix.
<sui-accordion id="acc-multi" aria-label="Several open" multiple> <sui-accordion-item heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </sui-accordion>
// setup(root): properties and events setup(root) { root.querySelector('#acc-multi').expanded = [0, 2]; }

Disabled section

A disabled header grays out, refuses clicks, and the arrow keys step over it.

Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150. Indiana sales tax registered. Kentucky registration is pending. Invoices continue from INV-1043. Credit notes use the CN prefix.
<sui-accordion id="acc-off" aria-label="Disabled section"> <sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next" disabled>Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </sui-accordion>

Events

suiExpandedChange carries the whole open index list and does not bubble, so an inner accordion never reports as the outer one; a field's suiChange inside a section does bubble. Each header is a button with aria-expanded and aria-controls, each body a region named by its header. A closed body is hidden.

Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150. Indiana sales tax registered. Kentucky registration is pending. Invoices continue from INV-1043. Credit notes use the CN prefix. Open: Company details.
<div style="display:flex;flex-direction:column;gap:10px"> <sui-accordion id="acc-events" aria-label="Events" multiple> <sui-accordion-item expanded heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </sui-accordion> <span id="acc-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Open: Company details.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#acc-log'); root.querySelector('#acc-events').addEventListener('suiExpandedChange', (e) => { const names = e.detail.map((i) => GROUPS[i].heading); log.textContent = names.length ? `Open: ${names.join(', ')}.` : 'All sections closed.'; }); }

Driving it from script

Writing expanded opens the sections and emits nothing, so a host that already knows the state never hears its own write echoed back. Writing an item's expanded does the same through the item, and the list follows.

Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150. Indiana sales tax registered. Kentucky registration is pending. Invoices continue from INV-1043. Credit notes use the CN prefix.
<div style="display:flex;flex-direction:column;gap:10px"> <div style="display:flex;flex-wrap:wrap;gap:8px"> <sui-button id="acc-jump-tax" variant="outline" size="sm" label="Open tax settings"></sui-button> <sui-button id="acc-jump-all" variant="outline" size="sm" label="Open every section"></sui-button> <sui-button id="acc-jump-none" variant="ghost" size="sm" label="Close all"></sui-button> </div> <sui-accordion id="acc-driven" aria-label="Driven from script" multiple> <sui-accordion-item heading="Company details" subtitle="Name, EIN and addresses">Falls City Supply Co. EIN 35-1882210. 410 Pearl Street, New Albany, IN 47150.</sui-accordion-item> <sui-accordion-item heading="Tax settings" meta="2 to review">Indiana sales tax registered. Kentucky registration is pending.</sui-accordion-item> <sui-accordion-item heading="Numbering" meta="INV-1044 next">Invoices continue from INV-1043. Credit notes use the CN prefix.</sui-accordion-item> </sui-accordion> </div>
// setup(root): properties and events setup(root) { const acc = root.querySelector('#acc-driven'); root.querySelector('#acc-jump-tax').addEventListener('click', () => { acc.expanded = [1]; }); root.querySelector('#acc-jump-all').addEventListener('click', () => { acc.expanded = GROUPS.map((_, i) => i); }); root.querySelector('#acc-jump-none').addEventListener('click', () => { acc.expanded = []; }); }

Attributes are livesui-accordion-item

Each header is drawn from its item's heading, subtitle, meta and disabled, and it redraws when one changes from script: a count can fall as work clears and a locked section can open once an earlier step is done, with no call on the parent. expanded opens a section from markup.

Indiana sales tax registered. Kentucky registration is pending.
Close September and roll balances into October.
<div style="display:flex;flex-direction:column;gap:12px"> <sui-accordion id="ai-live"> <sui-accordion-item id="ai-live-tax" heading="Tax settings" meta="2 to review" expanded><div style="font-size:13px;color:var(--ink-muted);line-height:1.7">Indiana sales tax registered. Kentucky registration is pending.</div></sui-accordion-item> <sui-accordion-item id="ai-live-close" heading="Period close" subtitle="Opens once tax settings are reviewed" disabled><div style="font-size:13px;color:var(--ink-muted);line-height:1.7">Close September and roll balances into October.</div></sui-accordion-item> </sui-accordion> <div style="display:flex;gap:8px;flex-wrap:wrap"> <sui-button id="ai-live-review" variant="outline" size="sm" label="Review one"></sui-button> </div> </div>
// setup(root): properties and events setup(root) { const tax = root.querySelector('#ai-live-tax'); const close = root.querySelector('#ai-live-close'); let left = 2; root.querySelector('#ai-live-review').addEventListener('click', () => { if (left === 0) return; left -= 1; tax.meta = left ? `${left} to review` : 'Reviewed'; if (left === 0) { close.disabled = false; close.subtitle = 'Ready to run'; } }); }

Keyboard

KeyAction
Tab sui-accordionMoves to the next header, or out of the accordion. Every enabled header is a tab stop.
Enter Space sui-accordionToggles the focused section.
ArrowDown sui-accordionMoves focus to the next enabled header, wrapping to the first.
ArrowUp sui-accordionMoves focus to the previous enabled header, wrapping to the last.
Home sui-accordionMoves focus to the first enabled header.
End sui-accordionMoves focus to the last enabled header.

API

<sui-accordion>

Properties

PropertyAttributeTypeDefaultDescription
expandedproperty onlynumber[][]Indexes of the open sections, in ascending order. Property, not attribute. Every section starts closed; a section whose item carries expanded joins the list when the accordion loads, so plain HTML opens one with the attribute. The list and the items' expanded stay in step both ways: writing the list opens and closes items, and writing an item's expanded updates the list. A write from script, to either, emits nothing.
multiplemultiplebooleanfalseLets several sections stay open; otherwise opening one closes the rest.

Events

EventDetailReactVueDescription
suiExpandedChangenumber[]onSuiExpandedChange@sui-expanded-changeFires after a header toggles, or when sections come or go and the open indexes move or drop; detail is the new expanded index list. Does not bubble, so an accordion inside a section never reports as the outer one.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the first enabled header.

Slots

SlotDescription
(default)sui-accordion-item children, in order.

<sui-accordion-item>

Properties

PropertyAttributeTypeDefaultDescription
disableddisabledbooleanfalseGreys the header and blocks toggling.
expandedexpandedbooleanfalseWhether the content is shown. Put the attribute on an item to open it from plain HTML. Inside a sui-accordion the two stay in step: the accordion writes it from its expanded list, and a write here from script updates that list, closing the other sections unless the accordion is multiple; neither write emits anything. The panel's height opens and closes over the base duration, with the chevron; the content stays in the DOM through the collapse and is hidden once it has closed.
headingheadingstring''Section title (the reference title; renamed for the native attribute). It names the panel's region; when the parent sui-accordion carries an aria-label, the region is named "heading, accordion name", so two accordions with the same sections read apart in a landmark list.
metametastringstring | undefinedMuted tabular text at the right of the header (counts, statuses).
subtitlesubtitlestringstring | undefinedFaint line under the title.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the header button. Does nothing while the item is disabled.

Slots

SlotDescription
(default)Section content (13px muted body copy, indented under the title).

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also