Skip to content

Accordion

<caos-accordion>

stacked sections, each opened and closed from its own header, so a long page is read a part at a time

May be placed on a record page and a page inside an app.

Name Attribute Description Type Default Required
sections property only The sections, as { value, label, open?, hidden? } objects, in the order they are drawn. value names the section and is the slot its content is placed into; label is the words on its header. The sections marked open start open, and when no section says either way the first one does. A hidden section is left out, with its content. A value that repeats an earlier one is dropped. json none declared Optional
multiple multiple, markup only Lets several sections stay open at once. Unset, opening a section closes the one that was open, and when several are marked open only the first of them starts open. boolean false Optional
remember remember, markup only A name under which the sections each person opens and closes are kept for them on this browser, so their next visit starts where they left it. Give two accordions the same name only if they are the same accordion. Left unset, nothing is kept and every visit starts as authored. text none declared Optional
Name When it fires What it carries
caos-accordion-toggle A section is about to open or close — its header is pressed, or openSection, closeSection or toggleSection is called. Cancelable: calling preventDefault leaves every section as it was. A section closed because another one opened raises nothing of its own. { value, open } — the section, and the state it is about to move to.
Name What goes in it
<section value> The content of one section: anything placed with slot set to that section’s value. Several pieces may name the same section and are drawn in order. Content naming no section, or a hidden one, is not drawn.
Design token What it controls
--caos-color-border The hairline between one section and the next.
--caos-color-text The header text, and the chevron of a hovered header.
--caos-color-text-muted The chevron at rest.
--caos-font The typeface of the headers.
--caos-space-2 The gap between the chevron and the label, and so how far content is indented.
--caos-space-3 The space above and below a header, and below an open section’s content.
--caos-focus-ring The ring drawn on a header reached by keyboard.
--caos-radius-sm The corners of that ring.
Name Description Arguments
openSection() Open a section, exactly as pressing its header does — the same cancelable event fires first. Returns false when nothing changed: the section is unknown, hidden or already open, or the event was cancelled. value: string
closeSection() Close a section, the same way. Returns false when nothing changed. value: string
toggleSection() Open a closed section or close an open one. Returns false when nothing changed. value: string
Name Type What it tells you
open string[] The values of the sections open now, in the order they are drawn.
Part Which piece of it When it is there
::part(accordion) the run of sections Always
::part(section) one section, its header and its content together Always
::part(header) the button that opens and closes a section Always
::part(label) the words on a header Always
::part(panel) the region a section’s content is drawn in Always

A page with more sections than anybody reads at once, where each is worth reaching but few are worth seeing all the time — the record page with eight sections. If a person needs to compare two sections side by side, keep them open as detail sections instead.

  • DetailSection — There is one block, or a few that have nothing to do with one another; set it collapsible if it should fold.
  • TabStrip — Only one face of the subject is worth looking at at a time, and the faces are few enough to name across a row.

Each header is a real button carrying aria-expanded, pointing aria-controls at its section, and each section is a region labelled by its header, so a screen reader announces what opens and whether it is open. Arrow Down and Arrow Up move between headers and wrap at the ends; Home and End go to the first and the last. Keys pressed inside a section’s content are left alone. A closed section’s content is hidden, not merely out of sight, so it is out of the tab order and the accessibility tree. The chevron turns toward the reading direction, so it reads correctly right to left. What is left to the author: a label that says what the section holds.

The ordinary case: the sections of a record page, one open at a time, the first open because no section says otherwise. Opening another closes it.

{
"id": "example",
"section": "Sections of a record",
"columns": 1,
"items": [
{
"id": "accordion_1",
"type": "component",
"key": "accordion",
"inputs": {
"sections": [
{
"value": "details",
"label": "Details"
},
{
"value": "billing",
"label": "Billing"
},
{
"value": "shipping",
"label": "Shipping"
}
]
},
"children": [
{
"id": "key_value_row_2",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Account",
"value": "Northwind Traders"
},
"slot": "details"
},
{
"id": "key_value_row_3",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Owner",
"value": "Dana Ruiz",
"no_border": true
},
"slot": "details"
},
{
"id": "key_value_row_4",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Terms",
"value": "Net 30",
"no_border": true
},
"slot": "billing"
},
{
"id": "key_value_row_5",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Method",
"value": "Freight",
"no_border": true
},
"slot": "shipping"
}
]
}
]
}
<caos-accordion id="accordion1">
<caos-key-value-row slot="details" label="Account" value="Northwind Traders"></caos-key-value-row>
<caos-key-value-row slot="details" label="Owner" value="Dana Ruiz" no-border></caos-key-value-row>
<caos-key-value-row slot="billing" label="Terms" value="Net 30" no-border></caos-key-value-row>
<caos-key-value-row slot="shipping" label="Method" value="Freight" no-border></caos-key-value-row>
</caos-accordion>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const accordion1 = document.getElementById('accordion1');
accordion1.sections = [
{
"value": "details",
"label": "Details"
},
{
"value": "billing",
"label": "Billing"
},
{
"value": "shipping",
"label": "Shipping"
}
];

With multiple set, sections open and close independently, and the two the author marked open both start open.

{
"id": "example",
"section": "Several open at once",
"columns": 1,
"items": [
{
"id": "accordion_1",
"type": "component",
"key": "accordion",
"inputs": {
"multiple": true,
"sections": [
{
"value": "details",
"label": "Details",
"open": true
},
{
"value": "billing",
"label": "Billing",
"open": true
},
{
"value": "notes",
"label": "Notes"
}
]
},
"children": [
{
"id": "key_value_row_2",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Account",
"value": "Northwind Traders",
"no_border": true
},
"slot": "details"
},
{
"id": "key_value_row_3",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Terms",
"value": "Net 30",
"no_border": true
},
"slot": "billing"
},
{
"id": "key_value_row_4",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Last call",
"value": "2 Sep 2026",
"no_border": true
},
"slot": "notes"
}
]
}
]
}
<caos-accordion id="accordion1" multiple>
<caos-key-value-row slot="details" label="Account" value="Northwind Traders" no-border></caos-key-value-row>
<caos-key-value-row slot="billing" label="Terms" value="Net 30" no-border></caos-key-value-row>
<caos-key-value-row slot="notes" label="Last call" value="2 Sep 2026" no-border></caos-key-value-row>
</caos-accordion>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const accordion1 = document.getElementById('accordion1');
accordion1.sections = [
{
"value": "details",
"label": "Details",
"open": true
},
{
"value": "billing",
"label": "Billing",
"open": true
},
{
"value": "notes",
"label": "Notes"
}
];

The same sections with one marked hidden: it is not drawn, and neither is what was placed in it.

{
"id": "example",
"section": "A section left out",
"columns": 1,
"items": [
{
"id": "accordion_1",
"type": "component",
"key": "accordion",
"inputs": {
"sections": [
{
"value": "details",
"label": "Details"
},
{
"value": "approvals",
"label": "Approvals",
"hidden": true
},
{
"value": "billing",
"label": "Billing"
}
]
},
"children": [
{
"id": "key_value_row_2",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Account",
"value": "Northwind Traders",
"no_border": true
},
"slot": "details"
},
{
"id": "key_value_row_3",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Approver",
"value": "Not shown",
"no_border": true
},
"slot": "approvals"
},
{
"id": "key_value_row_4",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Terms",
"value": "Net 30",
"no_border": true
},
"slot": "billing"
}
]
}
]
}
<caos-accordion id="accordion1">
<caos-key-value-row slot="details" label="Account" value="Northwind Traders" no-border></caos-key-value-row>
<caos-key-value-row slot="approvals" label="Approver" value="Not shown" no-border></caos-key-value-row>
<caos-key-value-row slot="billing" label="Terms" value="Net 30" no-border></caos-key-value-row>
</caos-accordion>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const accordion1 = document.getElementById('accordion1');
accordion1.sections = [
{
"value": "details",
"label": "Details"
},
{
"value": "approvals",
"label": "Approvals",
"hidden": true
},
{
"value": "billing",
"label": "Billing"
}
];

An accordion placed in one of another accordion’s sections. The same chevron marks every level, and the inner one is indented by that chevron alone.

{
"id": "example",
"section": "Sections inside a section",
"columns": 1,
"items": [
{
"id": "accordion_1",
"type": "component",
"key": "accordion",
"inputs": {
"sections": [
{
"value": "addresses",
"label": "Addresses"
},
{
"value": "terms",
"label": "Terms"
}
]
},
"children": [
{
"id": "accordion_2",
"type": "component",
"key": "accordion",
"inputs": {
"sections": [
{
"value": "billing",
"label": "Billing address"
},
{
"value": "shipping",
"label": "Shipping address"
}
]
},
"slot": "addresses",
"children": [
{
"id": "key_value_row_3",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "City",
"value": "Tacoma",
"no_border": true
},
"slot": "billing"
},
{
"id": "key_value_row_4",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "City",
"value": "Spokane",
"no_border": true
},
"slot": "shipping"
}
]
},
{
"id": "key_value_row_5",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Payment",
"value": "Net 30",
"no_border": true
},
"slot": "terms"
}
]
}
]
}
<caos-accordion id="accordion1">
<caos-accordion id="accordion1accordion1" slot="addresses">
<caos-key-value-row slot="billing" label="City" value="Tacoma" no-border></caos-key-value-row>
<caos-key-value-row slot="shipping" label="City" value="Spokane" no-border></caos-key-value-row>
</caos-accordion>
<caos-key-value-row slot="terms" label="Payment" value="Net 30" no-border></caos-key-value-row>
</caos-accordion>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const accordion1 = document.getElementById('accordion1');
const accordion1accordion1 = document.getElementById('accordion1accordion1');
accordion1.sections = [
{
"value": "addresses",
"label": "Addresses"
},
{
"value": "terms",
"label": "Terms"
}
];
accordion1accordion1.sections = [
{
"value": "billing",
"label": "Billing address"
},
{
"value": "shipping",
"label": "Shipping address"
}
];

An accordion inside a detail section, which brings the card. The accordion adds no spacing of its own, so its headers line up with the section’s content.

{
"id": "example",
"section": "Inside a section’s card",
"columns": 1,
"items": [
{
"id": "detail_section_1",
"type": "component",
"key": "detail_section",
"inputs": {
"heading": "Account"
},
"children": [
{
"id": "accordion_2",
"type": "component",
"key": "accordion",
"inputs": {
"sections": [
{
"value": "details",
"label": "Details"
},
{
"value": "billing",
"label": "Billing"
}
]
},
"children": [
{
"id": "key_value_row_3",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Owner",
"value": "Dana Ruiz",
"no_border": true
},
"slot": "details"
},
{
"id": "key_value_row_4",
"type": "component",
"key": "key_value_row",
"inputs": {
"label": "Terms",
"value": "Net 30",
"no_border": true
},
"slot": "billing"
}
]
}
]
}
]
}
<caos-detail-section heading="Account">
<caos-accordion id="detailSection1accordion1">
<caos-key-value-row slot="details" label="Owner" value="Dana Ruiz" no-border></caos-key-value-row>
<caos-key-value-row slot="billing" label="Terms" value="Net 30" no-border></caos-key-value-row>
</caos-accordion>
</caos-detail-section>
// What an attribute cannot hold, set as a property — the same values the preview is drawn with.
const detailSection1accordion1 = document.getElementById('detailSection1accordion1');
detailSection1accordion1.sections = [
{
"value": "details",
"label": "Details"
},
{
"value": "billing",
"label": "Billing"
}
];