Accordion
A hand-drawn accordion of collapsible sections (disclosure pattern).
Overview
An Accordion stacks collapsible sections so people can expand just the content they need. Use
type="single" to keep at most one section open (like an FAQ), or type="multiple" to let several
stay open at once.
Anatomy
A vertical stack of sketchy boxes (@ghds/sketch-core), each with a header button (aria-expanded,
aria-controls) and a collapsible role="region". The header’s chevron rotates when open. The API
is data-driven — pass an items array of { value, label, content }. Open state is a set of
values (string[]), controlled or uncontrolled.
Variants & States
| State | Status | Notes |
|---|---|---|
| Collapsed | Implemented | Default; content hidden, aria-expanded=false. |
| Expanded | Implemented | Content revealed, chevron rotated, aria-expanded=true. |
| Single vs multiple | Implemented | type controls whether one or many can be open. |
| Disabled | Implemented | Per-item; header not togglable and skipped by arrow keys. |
Usage
Do
- Use single mode for mutually-exclusive content (FAQs); multiple when sections are independent.
- Write concise, scannable header labels.
- Keep it controlled (value + onValueChange) if open state must sync with other UI.
Don't
- Don't hide critical content behind an accordion that users must see.
- Don't nest accordions deeply — it buries content.
Accessibility
- React / Web Components: each header is a
<button>witharia-expandedandaria-controlspointing at itsrole="region"(labelled back viaaria-labelledby). Arrow Up/Down and Home/End move focus between headers; Enter/Space toggles. Web Components dispatches avalue-changeevent. - React Native: headers are
accessibilityRole="button"withaccessibilityState.expanded; toggle by tap. Arrow-key header navigation is web-only (noonKeyDownon RNPressable). - See the Accessibility Guide.
| Key | Action |
|---|---|
Enter / Space | Toggle the focused section. |
Arrow Up / Down | Move focus to the previous / next header (wraps). |
Home / End | Move focus to the first / last header. |
Content
A short header label per section and the revealed content. Content is a ReactNode on
React/React Native; on Web Components it is section text (content string).
Props API
React@ghds/react
| Prop | Type | Default | Description |
|---|---|---|---|
items | AccordionItem[] | — | { value, label, content, disabled? } per section. |
type | 'single' | 'multiple' | 'single' | How many sections may be open. |
value | string[] | — | Controlled set of open values. |
defaultValue | string[] | — | Initial open values when uncontrolled. |
onValueChange | (values: string[]) => void | — | Fires when the open set changes. |
Web Components@ghds/web-components
| Prop | Type | Default | Description |
|---|---|---|---|
items | GhAccordionItemData[] | — | { value, label, content?: string, disabled? } (property). |
type | 'single' | 'multiple' | 'single' | How many sections may be open. |
value | string[] | — | Controlled set of open values (property). |
value-change event | CustomEvent<string[]> | — | Dispatched with the open set. |
React Native@ghds/react-native
| Prop | Type | Default | Description |
|---|---|---|---|
items | AccordionItem[] | — | { value, label, content, disabled? } per section. |
type | 'single' | 'multiple' | 'single' | How many sections may be open. |
value | string[] | — | Controlled set of open values. |
defaultValue | string[] | — | Initial open values when uncontrolled. |
onValueChange | (values: string[]) => void | — | Fires when the open set changes. |
testID | string | — | Test handle for queries. |
Code examples
// React — @ghds/react
import { Accordion } from '@ghds/react/accordion';
<Accordion
type="single"
defaultValue={['shipping']}
items={[
{ value: 'shipping', label: 'Shipping', content: <p>Ships in 2–3 business days.</p> },
{ value: 'returns', label: 'Returns', content: <p>Free returns within 30 days.</p> },
]}
/>;
<!-- Web Components — @ghds/web-components -->
<script type="module">
import '@ghds/web-components/accordion';
const el = document.getElementById('acc');
el.type = 'single';
el.value = ['shipping'];
el.items = [
{ value: 'shipping', label: 'Shipping', content: 'Ships in 2–3 business days.' },
{ value: 'returns', label: 'Returns', content: 'Free returns within 30 days.' },
];
el.addEventListener('value-change', (e) => {
el.value = e.detail;
});
</script>
<gh-accordion id="acc"></gh-accordion>
// React Native — @ghds/react-native
import { Accordion } from '@ghds/react-native/accordion';
import { Text } from '@ghds/react-native/theme';
<Accordion
type="single"
items={[{ value: 'shipping', label: 'Shipping', content: <Text>Ships in 2–3 days.</Text> }]}
/>;