Skip to main content

Accordion

Controlled collapsible sections with smooth CSS grid animation.

Quick start
import { Accordion } from "mates-ui";

Accordion({
  items: [{ id: "a", title: "One", content: "…" }],
  openIds: [],
  onToggle: () => {},
});

Default — single open

import { html, atom, renderApp } from 'mates';
import { Accordion } from 'mates-ui';

const ITEMS = [
{ id: "q1", title: "What is mates-design?", content: "A headless-inspired UI library built on lit-html and the mates reactive framework." },
{ id: "q2", title: "How do I install it?", content: 'npm install mates-design mates lit-html' },
{ id: "q3", title: "How does theming work?", content: "Entirely via CSS custom properties in tokens.css. Toggle dark mode with data-theme=\"dark\"." },
{ id: "q4", title: "Is it accessible?", content: "Yes — WAI-ARIA patterns, keyboard navigation, and proper ARIA attributes throughout." },
];

const App = () => {
const open = atom([]);
return () => html`
<div style="max-width:36rem;width:100%;">
${Accordion({ items: ITEMS, openIds: open(), multiple: false, onToggle: (id, isOpen) => open.set(isOpen ? [id] : []) })}
</div>
`;
};

renderApp(App, document.getElementById('app'));

Multiple open

import { html, atom, renderApp } from 'mates';
import { Accordion } from 'mates-ui';

const ITEMS = [
{ id: "q1", title: "What is mates-design?", content: "A headless-inspired UI library built on lit-html and the mates reactive framework." },
{ id: "q2", title: "How do I install it?", content: 'npm install mates-design mates lit-html' },
{ id: "q3", title: "How does theming work?", content: "Entirely via CSS custom properties in tokens.css. Toggle dark mode with data-theme=\"dark\"." },
{ id: "q4", title: "Is it accessible?", content: "Yes — WAI-ARIA patterns, keyboard navigation, and proper ARIA attributes throughout." },
];

const App = () => {
const open = atom(["q1", "q3"]);
return () => html`
<div style="max-width:36rem;width:100%;">
${Accordion({
items: ITEMS, openIds: open(), multiple: true,
onToggle: (id, isOpen) => open.set(isOpen ? [...open(), id] : open().filter((x) => x !== id)),
})}
</div>
`;
};

renderApp(App, document.getElementById('app'));

Bordered variant

import { html, atom, renderApp } from 'mates';
import { Accordion } from 'mates-ui';

const ITEMS = [
{ id: "q1", title: "What is mates-design?", content: "A headless-inspired UI library built on lit-html and the mates reactive framework." },
{ id: "q2", title: "How do I install it?", content: "npm install mates-design mates lit-html" },
{ id: "q3", title: "How does theming work?", content: "Entirely via CSS custom properties. Toggle dark with data-theme=\"dark\"." },
];

const App = () => {
const open = atom([]);
return () => html`
<div style="max-width:36rem;width:100%;">
${Accordion({ items: ITEMS, openIds: open(), variant: "bordered", onToggle: (id, isOpen) => open.set(isOpen ? [id] : []) })}
</div>
`;
};

renderApp(App, document.getElementById('app'));

Filled variant

import { html, atom, renderApp } from 'mates';
import { Accordion } from 'mates-ui';

const ITEMS = [
{ id: "q1", title: "What is mates-design?", content: "A headless-inspired UI library built on lit-html and the mates reactive framework." },
{ id: "q2", title: "How do I install it?", content: "npm install mates-design mates lit-html" },
{ id: "q3", title: "How does theming work?", content: "Entirely via CSS custom properties. Toggle dark with data-theme=\"dark\"." },
];

const App = () => {
const open = atom(["q2"]);
return () => html`
<div style="max-width:36rem;width:100%;">
${Accordion({
items: ITEMS, openIds: open(), variant: "filled", multiple: true,
onToggle: (id, isOpen) => open.set(isOpen ? [...open(), id] : open().filter((x) => x !== id)),
})}
</div>
`;
};

renderApp(App, document.getElementById('app'));

Accordion

import { Accordion } from "mates-ui";
Prop Type Default Description
attr AttrMap Extra HTML attributes applied to the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
items* AccordionItemData[] Array of accordion items to render.
multiple boolean false When true, multiple items can be open at the same time.
onToggle (id: string, open: boolean) => void Called when an item is opened or closed.
openIds string[] Controlled list of currently open item IDs.
variant "default" | "bordered" | "filled" "default" Visual style of the accordion.

AccordionItemData

Prop Type Default Description
content* TemplateResult | string Body content shown when the item is open.
disabled boolean When true, the item cannot be opened.
icon string Material Symbol name shown before the title.
id* string Unique identifier for the item.
title* string Header label displayed in the trigger button.

CSS tokens

Token Role
--md-color-border Divider and border colour.
--md-color-surface-raised Item background colour.
--md-color-surface-variant Filled variant background colour.
--md-radius-md Border radius of each item.

Caveats

  • Always control `openIds` externally — Accordion is fully controlled.
  • With `multiple: false`, always pass at most one ID in `openIds`.
  • Disabled items cannot be opened via keyboard or pointer.