Accordion
A stack of sections that open and close. One section is open at a time unless multiple is set.
import '@usezeno/tokens/css';
import '@usezeno/core/web-components';
Examples
Basic
Sections are set as the items property. Each item needs a value, a label and a content string. Opening one section closes the others.
<zeno-accordion></zeno-accordion>
document.querySelector('zeno-accordion').items = [
{ value: 'delivery', label: 'Delivery', content: 'Orders leave the warehouse within two working days.' },
{ value: 'returns', label: 'Returns', content: 'You can send an item back within 30 days.' },
{ value: 'payment', label: 'Payment', content: 'We accept cards and bank transfer.' },
];
Several sections open
With multiple, a section stays open when another one opens. toggleItem() opens or closes a section from code.
<zeno-accordion multiple></zeno-accordion>
const accordion = document.querySelector('zeno-accordion');
accordion.items = [
{ value: 'profile', label: 'Profile', content: 'Name, photo and time zone.' },
{ value: 'security', label: 'Security', content: 'Password and sign-in methods.' },
{ value: 'billing', label: 'Billing', content: 'Plan, invoices and payment method.' },
];
accordion.toggleItem('profile');
accordion.toggleItem('billing');
Variants
The default look, then bordered, filled, ghost and flush.
<zeno-accordion></zeno-accordion>
<zeno-accordion variant="bordered"></zeno-accordion>
<zeno-accordion variant="filled"></zeno-accordion>
<zeno-accordion variant="ghost"></zeno-accordion>
<zeno-accordion variant="flush"></zeno-accordion>
document.querySelectorAll('zeno-accordion').forEach(accordion => {
accordion.items = [
{ value: 'summary', label: 'Summary', content: 'Three files changed this week.' },
{ value: 'details', label: 'Details', content: 'report.pdf, notes.txt and budget.csv.' },
];
});
Sizes, icons and a disabled section
A disabled section cannot be opened and the arrow keys skip it. An item can name an icon to show before its label.
<zeno-accordion size="sm"></zeno-accordion>
<zeno-accordion size="lg"></zeno-accordion>
document.querySelectorAll('zeno-accordion').forEach(accordion => {
accordion.items = [
{ value: 'settings', label: 'Settings', icon: 'settings', content: 'Language and notifications.' },
{ value: 'archive', label: 'Archive', content: 'Nothing here yet.', disabled: true },
];
});
Attributes and properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
items | items | AccordionItem[] | [] | |
multiple | multiple | boolean | false | |
variant | variantreflects | 'default' | 'bordered' | 'ghost' | 'filled' | 'flush' | 'default' | |
size | sizereflects | 'sm' | 'md' | 'lg' | 'md' | |
defaultExpanded | defaultexpanded | string[] | [] | |
testId | string | Test ID for automated testing From ZenoElement. |
Methods
| Method | Description |
|---|---|
toggleItem(value: string): void | Toggle item expansion |
expandAll(): void | Expand all items |
collapseAll(): void | Collapse all items |
isExpanded(value: string): boolean | Check if item is expanded |
focus(): void | Focus the component or its first focusable element From ZenoElement. |
blur(): void | Blur the component or its focused element From ZenoElement. |
Events
| Event | Detail | Description |
|---|---|---|
expand | { value: string; item: AccordionItem } | A section opened. |
collapse | { value: string; item: AccordionItem } | A section closed. |
change | { expanded: string[] } | The set of open sections changed. |
expand-all | expandAll() opened every section. | |
collapse-all | collapseAll() closed every section. |
Slots
This element has no slots.
CSS parts
| Part |
|---|
base |
content |
content-wrapper |
expand-icon |
header |
icon |
item |
title |
CSS custom properties
This element documents no custom properties of its own. Style it through its parts and through the tokens it reads.
The element's own source reads no tokens.
Types
interface AccordionItem {
id: string;
value: string;
title: string;
label: string;
content: string;
icon?: string;
disabled?: boolean;
expanded?: boolean;
}
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the next section header. |
| EnterSpace | Opens or closes the focused section. |
| ArrowDown | Moves focus to the next header that is not disabled. It stops at the last one. |
| ArrowUp | Moves focus to the previous header that is not disabled. It stops at the first one. |
| Home | Moves focus to the first header that is not disabled. |
| End | Moves focus to the last header that is not disabled. |
Forms
This element is not a form control. It adds nothing to a form's data.
Notes
- Each header is a native
<button>witharia-expanded, andaria-controlsthat points at its panel. - Each panel has
role="region"and takes its name from its header witharia-labelledby. - A disabled section sets the native
disabledattribute on its header, so the header leaves the tab order. - The element wraps its sections in
role="region". The name is thearia-labelyou set, or “Accordion”. - The chevron and item icons are decoration and hidden from assistive technology.
Tests and known gaps
- No tests of its own. The only check is that the element registers and renders (
smoke.test.ts). - Not checked with axe-core.
- Has no Storybook story.
13 of the 25 properties, methods, events, slots and parts above have no description in the source
- the property
items - the property
multiple - the property
variant - the property
size - the property
defaultExpanded - the part
base - the part
content - the part
content-wrapper - the part
expand-icon - the part
header - the part
icon - the part
item - the part
title
Known gaps:
defaultExpandedis read only once, after the first render, and does not ask for a new render. Sections named there stay closed until something else updates the element, and a value set later is ignored. UsetoggleItem()for now.- The attribute for
defaultExpandedisdefaultexpanded, with no hyphen. Other elements use hyphens, as inmax-items. - Panel content is a plain string. There is no slot, so a panel cannot hold links, lists or other elements.
- The
AccordionItemtype requiresidandtitleand offersexpanded, but the element never reads them. - Header buttons are not inside heading elements, so screen reader users cannot move between sections by heading.
- The wrapper and every panel are
regionlandmarks. An accordion with many sections fills the landmark list. - The fallback name “Accordion” is English and can only be changed with
aria-label. - A closed panel is hidden only with
height: 0andoverflow: hidden. Its text stays in the accessibility tree, so screen readers still read it. - Opening and closing do not animate.
--accordion-transitionalready starts withall, sotransition: height var(--accordion-transition)and the chevron’stransition: transform …are invalid and the browser drops them. - The bottom border of the last section is cut off in the default look. Every section is pulled up by 1px to share a border with the next one, and the wrapper hides the border of the last one.
expandAll()opens every section even withoutmultiple.expandAll()andcollapseAll()do not firechange.