Skip to content

Accordion

Edit on GitHub
@volvo-cars/css v2.3.0@volvo-cars/react-accordion v2.3.20

Compose AccordionDetails and AccordionSummary to create expandable content sections built on the native <details> and <summary> elements.

Exterior

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.

Charging

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.

Climate

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.

import {
AccordionDetails,
AccordionSummary,
} from '@volvo-cars/react-accordion';
export function BasicAccordion() {
return (
<>
{['Exterior', 'Charging', 'Climate'].map((item) => (
<AccordionDetails key={item}>
<AccordionSummary>{item}</AccordionSummary>
<p className="pb-24 text-secondary">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
malesuada lacus ex, sit amet blandit leo lobortis eget.
</p>
</AccordionDetails>
))}
</>
);
}

AccordionDetails wraps each section. Its first child should be an AccordionSummary — this becomes the toggle. Everything else is the collapsible content.

AccordionSummary renders a <summary> element with an implicit button role. It must not contain interactive children (links, buttons, checkboxes).

Control where the chevron icon appears with iconPlacement. Use iconAlignment to adjust vertical alignment when labels wrap.

Icon at end (default)

The chevron appears after the label.

Icon at start

The chevron appears before the label.

import {
AccordionDetails,
AccordionSummary,
} from '@volvo-cars/react-accordion';
export function IconPlacement() {
return (
<>
<AccordionDetails>
<AccordionSummary iconPlacement="end">
Icon at end (default)
</AccordionSummary>
<p className="pb-24 text-secondary">
The chevron appears after the label.
</p>
</AccordionDetails>
<AccordionDetails>
<AccordionSummary iconPlacement="start">Icon at start</AccordionSummary>
<p className="pb-24 text-secondary">
The chevron appears before the label.
</p>
</AccordionDetails>
</>
);
}
PropValuesDefault
iconPlacement'start', 'end''end'
iconAlignment'top', 'center''top'

By default, the browser manages toggle state via native <details>. Use defaultOpen to start a section expanded:

<AccordionDetails defaultOpen>
<AccordionSummary>Open by default</AccordionSummary>
<p>Content</p>
</AccordionDetails>

Use open and onToggle for full control — useful for wizard-style flows or syncing with external state:

Step 1

Content for Step 1. Use the `open` and `onToggle` props to fully control which item is expanded.

Step 2

Content for Step 2. Use the `open` and `onToggle` props to fully control which item is expanded.

Step 3

Content for Step 3. Use the `open` and `onToggle` props to fully control which item is expanded.

import {
AccordionDetails,
AccordionSummary,
} from '@volvo-cars/react-accordion';
import { useState } from 'react';
export function ControlledAccordion() {
const [openIndex, setOpenIndex] = useState(-1);
return (
<>
{['Step 1', 'Step 2', 'Step 3'].map((label, i) => (
<AccordionDetails
key={label}
open={openIndex === i}
onToggle={(open) => setOpenIndex(open ? i : -1)}
>
<AccordionSummary>{label}</AccordionSummary>
<p className="pb-24 text-secondary">
Content for {label}. Use the `open` and `onToggle` props to fully
control which item is expanded.
</p>
</AccordionDetails>
))}
</>
);
}

Wrap items in AccordionController to allow only one open at a time. Set exclusive="until-lg" to make it exclusive on mobile/tablet but allow multiple on desktop.

Exterior

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.

Charging

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.

Climate

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.

import {
AccordionController,
AccordionDetails,
AccordionSummary,
} from '@volvo-cars/react-accordion';
export function ExclusiveAccordion() {
return (
<AccordionController exclusive>
{['Exterior', 'Charging', 'Climate'].map((item) => (
<AccordionDetails key={item}>
<AccordionSummary>{item}</AccordionSummary>
<p className="pb-24 text-secondary">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
malesuada lacus ex, sit amet blandit leo lobortis eget.
</p>
</AccordionDetails>
))}
</AccordionController>
);
}

The Stepped Accordion organizes content into collapsible sections displayed vertically with a connecting left border. Only one step can be open at a time, guiding users through sequential information.

Step one
Content for step one.
Step two
Content for step two.
Step three
Content for step three.
import {
SteppedAccordion,
SteppedAccordionDetails,
SteppedAccordionSummary,
} from '@volvo-cars/react-accordion';
import { useState } from 'react';
export function BasicSteppedAccordion() {
const [currentStep, setCurrentStep] = useState(0);
const steps = [
{ id: 0, title: 'Step one', content: 'Content for step one.' },
{ id: 1, title: 'Step two', content: 'Content for step two.' },
{ id: 2, title: 'Step three', content: 'Content for step three.' },
];
return (
<SteppedAccordion>
{steps.map(({ id, title, content }) => (
<SteppedAccordionDetails
key={id}
open={currentStep === id}
onToggle={() => setCurrentStep(id)}
>
<SteppedAccordionSummary>{title}</SteppedAccordionSummary>
<div className="pb-8 px-24 text-secondary">{content}</div>
</SteppedAccordionDetails>
))}
</SteppedAccordion>
);
}

The component uses three parts:

  • SteppedAccordion – Container that provides the vertical layout and left border
  • SteppedAccordionDetails – Wraps each step, controls open state via the open prop
  • SteppedAccordionSummary – The clickable header for each step

The stepped accordion is always controlled. Track the current step with state and update it via the onToggle callback. An open step cannot be closed directly—opening another step closes the current one.

const [currentStep, setCurrentStep] = useState(0);
<SteppedAccordionDetails
open={currentStep === 0}
onToggle={() => setCurrentStep(0)}
>

Add content inside each step after the summary. Use the standard padding classes for consistent spacing.

Immersive listening
This advanced sound system employs 21 speakers and two amplifiers to deliver high-fidelity audio in 3D throughout the cabin.
Integrated speakers
With the new soundbar concept, a 1040W amplifier and nine high-performance speakers, this state-of-the-art system delivers immersive surround sound for everyone.
Customisable audio
Every aspect of the audio system is tuned to the interior. Select Dynamic, Soft or Voice mode to deepen your listening experience.
import {
SteppedAccordion,
SteppedAccordionDetails,
SteppedAccordionSummary,
} from '@volvo-cars/react-accordion';
import { useState } from 'react';
export function SteppedAccordionWithContent() {
const [currentStep, setCurrentStep] = useState(0);
const steps = [
{
id: 0,
title: 'Immersive listening',
content:
'This advanced sound system employs 21 speakers and two amplifiers to deliver high-fidelity audio in 3D throughout the cabin.',
},
{
id: 1,
title: 'Integrated speakers',
content:
'With the new soundbar concept, a 1040W amplifier and nine high-performance speakers, this state-of-the-art system delivers immersive surround sound for everyone.',
},
{
id: 2,
title: 'Customisable audio',
content:
'Every aspect of the audio system is tuned to the interior. Select Dynamic, Soft or Voice mode to deepen your listening experience.',
},
];
return (
<SteppedAccordion>
{steps.map(({ id, title, content }) => (
<SteppedAccordionDetails
key={id}
open={currentStep === id}
onToggle={() => setCurrentStep(id)}
>
<SteppedAccordionSummary>{title}</SteppedAccordionSummary>
<div className="pb-8 px-24 text-secondary">{content}</div>
</SteppedAccordionDetails>
))}
</SteppedAccordion>
);
}

Key consumer responsibilities from the WCAG audit:

  • Accessible name — provide clear accordion header text. If using icons or non-text indicators, add aria-label or aria-labelledby.
  • No interactive children in summary<summary> has an implicit button role. Nesting links, buttons, or other interactive elements inside AccordionSummary is invalid.
  • Expand/collapse via keyboard — handled natively (Space / Enter on <summary>). Ensure users can tab in and out without getting trapped.
  • Consistent labeling — use the same naming pattern for accordion headers across your interface.
Prop Type Required Default
hidden boolean - -
id string - -
title string - -
dir string - -
lang string - -
slot string - -
translate "yes" | "no" - -
style CSSProperties - -
onClick MouseEventHandler<HTMLDetailsElement> - -
onPointerEnter PointerEventHandler<HTMLDetailsElement> - -
onPointerMove PointerEventHandler<HTMLDetailsElement> - -
onPointerDown PointerEventHandler<HTMLDetailsElement> - -
onPointerUp PointerEventHandler<HTMLDetailsElement> - -
onPointerLeave PointerEventHandler<HTMLDetailsElement> - -
children ReactNode -
The content of the accordion. The first child should be the `AccordionSummary` component.
className string - -
Custom class name, merged with existing classes.
onToggle ((open: boolean) => void) - -
Called when the accordion is toggled.
onAnimationFinish ((open: boolean) => void) - -
Called when the toggle animation is finished.
open boolean - -
If set, the accordion will be controlled by the value of this prop. Must be used together with the `onToggle` prop.
defaultOpen boolean - -
If true, the accordion will be open by default but can then be toggled freely by the user.
Prop Type Required Default
hidden boolean - -
id string - -
title string - -
dir string - -
lang string - -
slot string - -
translate "yes" | "no" - -
style CSSProperties - -
onFocus FocusEventHandler<HTMLElement> - -
onBlur FocusEventHandler<HTMLElement> - -
onClick MouseEventHandler<HTMLElement> - -
onKeyDown KeyboardEventHandler<HTMLElement> - -
onKeyUp KeyboardEventHandler<HTMLElement> - -
onPointerEnter PointerEventHandler<HTMLElement> - -
onPointerMove PointerEventHandler<HTMLElement> - -
onPointerDown PointerEventHandler<HTMLElement> - -
onPointerUp PointerEventHandler<HTMLElement> - -
onPointerLeave PointerEventHandler<HTMLElement> - -
children ReactNode -
The label of the summary. Should not contain interactive elements.
icon ReactNode - -
The icon to display.
iconPlacement "start" | "end" - end
The placement of the icon - before or after the label.
iconAlignment "top" | "center" - top
The vertical alignment of the icon.
className string - -
Custom class name, merged with existing classes.
Prop Type Required Default
exclusive boolean | "until-lg" - true
Prop Type Required Default
className string - -
onClick MouseEventHandler<HTMLDivElement> - -
onMouseOver MouseEventHandler<HTMLDivElement> - -
onMouseOut MouseEventHandler<HTMLDivElement> - -
style CSSProperties - -
Prop Type Required Default
open boolean -
Whether the details are open.
hidden boolean - -
id string - -
title string - -
dir string - -
lang string - -
slot string - -
translate "yes" | "no" - -
style CSSProperties - -
onClick MouseEventHandler<HTMLDetailsElement> - -
onPointerEnter PointerEventHandler<HTMLDetailsElement> - -
onPointerMove PointerEventHandler<HTMLDetailsElement> - -
onPointerDown PointerEventHandler<HTMLDetailsElement> - -
onPointerUp PointerEventHandler<HTMLDetailsElement> - -
onPointerLeave PointerEventHandler<HTMLDetailsElement> - -
children ReactNode -
The content of the accordion. The first child should be the `AccordionSummary` component.
className string - -
Custom class name, merged with existing classes.
onToggle ((open: boolean) => void) - -
Called when the accordion is toggled.
onAnimationFinish ((open: boolean) => void) - -
Called when the toggle animation is finished.
Prop Type Required Default
hidden boolean - -
id string - -
title string - -
dir string - -
lang string - -
slot string - -
translate "yes" | "no" - -
style CSSProperties - -
onFocus FocusEventHandler<HTMLElement> - -
onBlur FocusEventHandler<HTMLElement> - -
onClick MouseEventHandler<HTMLElement> - -
onKeyDown KeyboardEventHandler<HTMLElement> - -
onKeyUp KeyboardEventHandler<HTMLElement> - -
onPointerEnter PointerEventHandler<HTMLElement> - -
onPointerMove PointerEventHandler<HTMLElement> - -
onPointerDown PointerEventHandler<HTMLElement> - -
onPointerUp PointerEventHandler<HTMLElement> - -
onPointerLeave PointerEventHandler<HTMLElement> - -
children ReactNode -
The label of the summary. Should not contain interactive elements.
icon ReactNode - -
The icon to display.
iconPlacement "start" | "end" - -
The placement of the icon - before or after the label.
iconAlignment "top" | "center" - -
The vertical alignment of the icon.
className string - -
Custom class name, merged with existing classes.