Skip to main content
xriptxr

Accordion

Info:Layout
27

A stack of collapsible sections, one or many open at a time, driven by pointer or keyboard.

collapse expander disclosure faq collapsible sections

Live demo

live · @xtyle/astro

Accordion

Single-open

What's your shipping time?

Orders placed before 2pm ship the same business day; everything else ships the next morning.

How do returns work?

Unworn items are accepted within 30 days for a full refund — no restocking fee, no questions asked.

Is there a warranty?

Every piece is covered against manufacturing defects for one year from the delivery date.

Rich headers · nested components

Settings Info:3

A panel is a full render slot, so components nest inside it.

Including ones with their own chrome.

Saved

Headers carry markup too, not just text.

Multiple open · compact

Availability

In stock, on sale, free shipping.

Sort

Price, rating, newest first.

Saved searches

Sign in to save a search.

Controls on the header row

A data-xtyle-actions child sits on its section's header row, beside the trigger rather than inside it, so its buttons are real buttons: they never toggle the section, and they stay put while it is closed.

Outcomes

Each run's result, newest first.

Roster

Who is on shift, as chips.

Notes

A section without controls keeps the plain header row.

A different marker

What is a marker glyph?

Any name the icon roster can draw, so a mod that fills xtyle.icons can supply its own and name it here.

Does it stay themed?

Yes. The glyph inherits the derived color like every other icon.

Accordion stacks a set of disclosure sections that expand and collapse. Each section pairs a [slot="header"] header with the [slot="panel"] that follows it; the component wraps every header in a heading and a role="button" trigger carrying aria-expanded and aria-controls, and turns each panel into a labelled role="region" that hides when collapsed.

By default it is single-open: opening one section closes the rest. multiple lets several stay open at once. Mark a header open to expand its section initially, or disabled to lock it. The heading level is h3 by default and settable with headingLevel, and three sizes (sm, md, lg) scale the trigger density. A chevron rotates with the open state, and pointer, Enter/Space, and the arrow/Home/End keys all drive it. A section can also carry [slot="actions"] controls (reorder arrows, a view switch) that sit on its header row beside the trigger rather than inside it, so they stay real buttons, never toggle the section, and stay visible while it is closed. Sections are matched by value, so reordering the authored pairs keeps each header, panel, open state, and keyboard focus with its section.

When to use

How this component composes with the rest of the set.

Pair headers and panels in order (a [slot="header"] followed by its [slot="panel"]) and repeat for each section.
The slotted form is for raw HTML and Astro; under Astro use the data-xtyle-header / data-xtyle-panel markers, because its named-slot handling consumes slot before the element sees it. @xtyle/svelte takes its sections as data instead — a sections array and a panel snippet — and renders the pairs itself, so slotted children handed to it are not read.
Leave multiple off for an FAQ where one answer shows at a time; turn it on for independent filter or settings groups.
For a small fixed set of mutually exclusive views with their own content area, reach for Tabs instead.

Props

8 props, straight from the manifest.

PropTypeDefaultBindingsDescription
items AccordionSection[] —
html astro
The sections as data instead of authored [slot="header"] / [slot="panel"] pairs: an array of { value, header, body?, disabled? }, serialized to JSON on the attribute. The Svelte binding spells the same list sections and takes its bodies from a panel snippet.
sections AccordionSection[] —
svelte
Svelte only: the sections as data, with each panel rendered by the panel snippet keyed by value. The same list is items on every other binding.
header
property
Snippet<[string]> —
svelte
Svelte only: rich header content for each section, given its value, in place of the section's header text. It renders inside the trigger, so keep it non-interactive; controls belong in actions.
actions
property
Snippet<[string]> —
svelte
Svelte only: a section's header-row controls, given its value. They render beside the trigger, not inside it.
multiple boolean false
html svelte astro
Allows several sections to stay open at once; when off, opening one closes the others.
size Size
sm md lg
md
html svelte astro
Trigger density: sm, md, or lg.
headingLevel html: heading-level 2 | 3 | 4 | 5 | 6 3
html svelte astro
The heading level wrapping each trigger, so the accordion sits correctly in the document outline.
chevronIcon html: chevron-icon string "chevron-down"
html svelte astro
The roster glyph drawn as the disclosure marker. Any name the icon roster can draw, including one a mod contributed through the xtyle.icons slot.

Events

What the component emits, and what rides along on event.detail. The name in the first column is the one addEventListener takes.

EventDetailBindingsDescription
toggle { value, open, values }
html svelte astro
A section opened or closed.

Appearance

Sizes

sm

.xtyle-accordion--sm

Compact triggers.

md

default
.xtyle-accordion

Default.

lg

.xtyle-accordion--lg

Roomy triggers.

States

open

.xtyle-accordion__trigger[aria-expanded="true"]

An expanded section: the trigger reads aria-expanded="true" and the chevron rotates.

trigger-hover

.xtyle-accordion__trigger:hover

Pointer over a header: the hover tint paints behind it.

trigger-focus-visible

.xtyle-accordion__trigger:focus-visible

Keyboard focus on a header: an inset token ring plus the transparent outline promoted in forced-colors mode.

disabled

.xtyle-accordion__trigger:disabled

A locked header: muted and non-interactive.

Anatomy

The named parts that make up the component. Created from script it renders a shadow root, where a ::part() handle is reachable from your own stylesheet and the class beside it is internal. Upgraded over server-rendered markup it renders light DOM, where the class is the handle.

accordion

Success:::part(accordion) .xtyle-accordion

The bordered container stacking the sections, with hairlines between them.

--font-sans --fg-0 --bg-1 --border-thin --line --radius-md

section

Success:::part(section) .xtyle-accordion__section

One section: its <details> disclosure plus, when it has any, the header-row actions beside it. The hairline between sections sits here.

--border-thin --line

actions

Success:::part(actions) .xtyle-accordion__actions

The authored header-row controls, laid over the trigger's row just before the chevron; the label keeps clear of them. A sibling of the <summary>, never inside it.

--space-1 --space-3 --space-4 --text-body --leading-tight

trigger

Success:::part(trigger) .xtyle-accordion__trigger

The full-width <summary> that toggles its section; carries the hover/press overlay. The native disclosure marker is suppressed in favor of the chevron.

--text-body --weight-medium --leading-tight --fg-0 --space-3 --space-4 --state-hover --state-press --border-normal --border-thick --ring --fg-disabled --duration-fast --ease-standard

chevron

Success:::part(chevron) .xtyle-accordion__chevron

The disclosure caret in the trigger corner; rotates 180° when the section is open. It is an <xtyle-icon name="chevron-down"> inside the accordion's fragment, so the glyph renders through the Icon component and a mod can swap it without touching the accordion.

--fg-2 --duration-fast --ease-standard

panel

Success:::part(panel) .xtyle-accordion__panel

The collapsible role="region" holding the section content; the enclosing <details> collapses it, so it carries no hidden of its own.

--fg-1 --space-1 --space-4 --text-body --leading-normal

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 27/27 consumed tokens produced default register: 311 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors }) → coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--bg-1 --border-normal --border-thick --border-thin --duration-fast --ease-standard --fg-0 --fg-1 --fg-2 --fg-disabled --font-sans --leading-normal --leading-tight --line --radius-md --ring --space-1 --space-2 --space-3 --space-4 --space-5 --state-hover --state-press --text-body --text-lg --text-sm --weight-medium

Slots

header
html svelte astro

Each section's header label, marked slot="header" or data-xtyle-header; add open to expand it initially or disabled to lock it. Astro consumes slot to route children, so use data-xtyle-header there. Carries markup, not just text: an icon, a badge, and nested components all survive into the render, including the static one.

panel
html svelte astro

Each section's collapsible content, marked slot="panel" or data-xtyle-panel (use data-xtyle-panel under Astro), paired to the header before it by order. A full render slot: nested components keep working, and under Astro the pairing resolves at build time so the sections are complete before any script runs.

actions
html svelte astro

Optional controls for a section's header row, marked slot="actions" or data-xtyle-actions (use the marker under Astro) and placed after the header they belong to. They render beside the trigger, not inside it, so a button there is a real button. In Svelte, pass an actions snippet that receives the section's value.

Accessibility

Each section is a native <details>/<summary> disclosure, so it opens and closes with the runtime never loading and the browser announces the expanded state itself rather than a hand-maintained aria-expanded.
The summary holds a heading (h3 by default, set with headingLevel) so the sections still land in the document outline and screen-reader rotor.
Single-open mode gives every section a shared name, which is what makes opening one collapse the rest; the browser enforces it, with no script involved. Under multiple the grouping is dropped.
The panel is a role="region" wired back with aria-labelledby; it never carries hidden, because the disclosure already owns its own visibility.
Pointer, Enter, and Space toggle a section; Up/Down arrows move focus between headers and Home/End jump to the first and last.
A header marked disabled is skipped by the arrow keys and cannot toggle. <summary> has no native disabled state, so it is aria-disabled and the toggle is cancelled; it stays focusable, which is the ARIA-preferred treatment.
Focus on a header shows an inset token ring plus a transparent outline the forced-colors base rule promotes to a real system outline.
Header-row actions are siblings of the <summary>, never children of it: a <summary> is a button, and a button nested inside one is announced wrongly and toggles the section when pressed. Give an icon-only action an aria-label that names its section ("Move Roster up"), since it sits outside the heading.

Code

Single-open FAQ

Three sections where opening one collapses the others; the second starts open.

<xtyle-accordion>
	<span slot="header">Shipping</span>
	<div slot="panel">Orders ship within two business days.</div>
	<span slot="header" open>Returns</span>
	<div slot="panel">Unworn items are accepted within 30 days.</div>
	<span slot="header">Warranty</span>
	<div slot="panel">Covered against defects for one year.</div>
</xtyle-accordion>

Multiple open, with a disabled section

A compact accordion that lets several panels stay open, with one locked header.

<xtyle-accordion multiple size="sm">
	<span slot="header" open>Filters</span>
	<div slot="panel">In stock, on sale, free shipping.</div>
	<span slot="header" open>Sort</span>
	<div slot="panel">Price, rating, newest.</div>
	<span slot="header" disabled>Saved searches</span>
	<div slot="panel">Sign in to save a search.</div>
</xtyle-accordion>

Sections as data

The same accordion declared as a list instead of authored pairs. The attribute takes JSON, the Astro binding takes the array, and the Svelte binding spells it sections with a panel snippet.

<xtyle-accordion items='[
	{ "value": "shipping", "header": "Shipping", "panel": "Orders ship within two business days." },
	{ "value": "returns", "header": "Returns", "panel": "Unworn items are accepted within 30 days.", "open": true }
]'></xtyle-accordion>

Controls on the header row

A view switch and reorder arrows beside each trigger. Reordering the sections keeps every panel, open state, and focused header with its section.

<xtyle-accordion multiple>
	<span slot="header" value="outcomes" open>Outcomes</span>
	<span slot="actions">
		<xtyle-segmented size="sm" options="Rows,Chart" value="Rows" aria-label="Outcome view"></xtyle-segmented>
	</span>
	<div slot="panel">Each run's result, newest first.</div>
	<span slot="header" value="roster">Roster</span>
	<span slot="actions">
		<xtyle-button variant="ghost" tone="neutral" size="sm" icon-only aria-label="Move Roster up">
			<xtyle-icon slot="icon-start" name="chevron-up"></xtyle-icon>
		</xtyle-button>
	</span>
	<div slot="panel">Who is on shift, as chips.</div>
</xtyle-accordion>

A different marker glyph

The disclosure marker drawn from any name the icon roster can draw, including one a mod contributed.

<xtyle-accordion chevron-icon="plus">
	<span slot="header">What is a marker glyph?</span>
	<div slot="panel">Any name the icon roster can draw.</div>
	<span slot="header">Can a mod add one?</span>
	<div slot="panel">Yes, by filling the <code>xtyle.icons</code> slot.</div>
</xtyle-accordion>