Accordion
A stack of collapsible sections, one or many open at a time, driven by pointer or keyboard.
Live demo
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.
When to use
How this component composes with the rest of the set.
Props
6 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
[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. | ||||
panel snippet keyed by value. The same list is items on every other binding. | ||||
sm, md, or lg. | ||||
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.
| Event | Detail | Bindings | Description |
|---|---|---|---|
Appearance
Sizes
sm
Compact triggers.
md
Default.
lg
Roomy triggers.
States
open
An expanded section: the trigger reads aria-expanded="true" and the chevron rotates.
trigger-hover
Pointer over a header: the hover tint paints behind it.
trigger-focus-visible
Keyboard focus on a header: an inset token ring plus the transparent outline promoted in forced-colors mode.
disabled
A locked header: muted and non-interactive.
Anatomy
The named parts that make up the component. A ::part() handle is reachable from your own stylesheet; the class beside it is the component's internal selector, which a shadow boundary keeps to itself.
accordion
The bordered container stacking the sections, with hairlines between them.
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.
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.
panel
The collapsible role="region" holding the section content; the enclosing <details> collapses it, so it carries no hidden of its own.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
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-mediumSlots
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.
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.
Accessibility
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><script lang="ts">
import { Accordion } from "@xtyle/svelte";
const sections = [
{ value: "shipping", header: "Shipping" },
{ value: "returns", header: "Returns", open: true },
];
</script>
<Accordion {sections}>
{#snippet panel(value)}
{#if value === "shipping"}Orders ship within two business days.{/if}
{#if value === "returns"}Unworn items are accepted within 30 days.{/if}
{/snippet}
</Accordion>---
import { Accordion } from "@xtyle/astro";
---
<!-- Astro consumes a child's `slot` attribute to route it, so mark headers and
panels with `data-xtyle-header` / `data-xtyle-panel` here. -->
<Accordion multiple>
<span data-xtyle-header>Shipping</span>
<div data-xtyle-panel>Orders ship within two business days.</div>
<span data-xtyle-header open>Returns</span>
<div data-xtyle-panel>Unworn items are accepted within 30 days.</div>
</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><script lang="ts">
import { Accordion } from "@xtyle/svelte";
const sections = [
{ value: "shipping", header: "Shipping" },
{ value: "returns", header: "Returns", open: true },
];
</script>
<Accordion {sections}>
{#snippet panel(value)}
{#if value === "shipping"}Orders ship within two business days.{/if}
{#if value === "returns"}Unworn items are accepted within 30 days.{/if}
{/snippet}
</Accordion>---
import { Accordion } from "@xtyle/astro";
---
<!-- Astro consumes a child's `slot` attribute to route it, so mark headers and
panels with `data-xtyle-header` / `data-xtyle-panel` here. -->
<Accordion multiple>
<span data-xtyle-header>Shipping</span>
<div data-xtyle-panel>Orders ship within two business days.</div>
<span data-xtyle-header open>Returns</span>
<div data-xtyle-panel>Unworn items are accepted within 30 days.</div>
</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><script lang="ts">
import { Accordion } from "@xtyle/svelte";
const sections = [
{ value: "shipping", header: "Shipping" },
{ value: "returns", header: "Returns", open: true },
];
</script>
<Accordion {sections}>
{#snippet panel(value)}
{#if value === "shipping"}Orders ship within two business days.{/if}
{#if value === "returns"}Unworn items are accepted within 30 days.{/if}
{/snippet}
</Accordion>---
import Accordion from "@xtyle/astro/Accordion.astro";
const sections = [
{ 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 },
];
---
<Accordion items={sections} />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><script lang="ts">
import { Accordion } from "@xtyle/svelte";
const sections = [{ value: "marker", header: "What is a marker glyph?" }];
</script>
<Accordion chevronIcon="plus" {sections}>
{#snippet panel()}Any name the icon roster can draw.{/snippet}
</Accordion>---
import Accordion from "@xtyle/astro/Accordion.astro";
---
<Accordion chevronIcon="plus">
<span data-xtyle-header>What is a marker glyph?</span>
<div data-xtyle-panel>Any name the icon roster can draw.</div>
</Accordion>