Accordion
Accordion is a stacked list of expandable items that users can open and close to reveal or hide content. It helps organize information efficiently.
The accordion can be built using the following component,
- AccordionItem – The container for each item / section.
- AccordionHeader – The clickable element that toggles visibility.
- AccordionPanel – The content area that expands or collapses.
Available from eds-core/1.20.0
Quick Start
- Installation
npm install @adaptavant/eds-core- Import
import { Accordion } from '@adaptavant/eds-core';
Usage
Each AccordionItem has its own props to manage the header and content panel. By default, it behaves as an uncontrolled component as it manages its own internal state.
<Stack className="gap-3 w-full">
<AccordionItem>
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content.
It is useful when you want to display a large amount of information
but don't want to overwhelm the user.
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default.
</AccordionPanel>
</AccordionItem>
</Stack>
Open By Default
Use isDefaultOpen prop in AccordionItem to have any item open by default.
<Stack className="gap-3 w-full">
<AccordionItem isDefaultOpen={true}>
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content.
It is useful when you want to display a large amount of information
but don't want to overwhelm the user.
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default. It also supports ARIA attributes and roles for improved accessibility.
</AccordionPanel>
</AccordionItem>
</Stack>
Divider
In case a visual separator is required between Accordion items, add the hasDivider prop for the AccordionItem.
<Stack className="gap-3 w-full">
<AccordionItem hasDivider>
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content.
It is useful when you want to display a large amount of information
but don't want to overwhelm the user.
</AccordionPanel>
</AccordionItem>
<AccordionItem hasDivider>
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default. It also supports ARIA attributes and roles for improved accessibility.
</AccordionPanel>
</AccordionItem>
</Stack>
Controlled
If you want full control over the accordion behaviour, you can manage it manually using the id, isOpen and onToggle props.
- Assign a unique
idto each item. - Use the
isOpenprop to control whether the item is expanded. - Use the
onTogglecallback to update the item's state when it's toggled.
const [openItemsId, setOpenItemsId] = React.useState([]);
const handleToggle = ({ id, isOpen }) => {
setOpenItemsId((prev) => {
if (prev) {
return isOpen ? [...prev, id] : prev.filter((openId) => openId !== id);
} else {
return isOpen ? [id] : [];
}
});
};
return (
<Stack className="gap-3 w-full">
<AccordionItem
id="item-1"
isOpen={openItemsId?.includes('item-1')}
onToggle={handleToggle}
>
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content. It
is useful when you want to display a large amount of information but
don't want to overwhelm the user.
</AccordionPanel>
</AccordionItem>
<AccordionItem
id="item-2"
isOpen={openItemsId?.includes('item-2')}
onToggle={handleToggle}
>
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default.
</AccordionPanel>
</AccordionItem>
</Stack>
);
Open one at a time
If you need only one accordion item to be open at a time, you can handle it by storing the currently open item's id (like openId) in your local state.
const [openId, setOpenId] = React.useState(""); // add item id, to have an accordion item open by default.
const handleToggle = (id) => {
if (openId === id) {
setOpenId(undefined);
} else {
setOpenId(id);
}
};
return (
<Stack className="gap-3 w-full">
<AccordionItem
id="item-1"
isOpen={openId === 'item-1'}
onToggle={({ id }) => {
handleToggle(id);
}}
>
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content. It
is useful when you want to display a large amount of information but
don't want to overwhelm the user.
</AccordionPanel>
</AccordionItem>
<AccordionItem
id="item-2"
isOpen={openId === 'item-2'}
onToggle={({ id }) => {
handleToggle(id);
}}
>
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default.
</AccordionPanel>
</AccordionItem>
</Stack>
);
Unmount Panel
Use unMountOnClose prop on AccordionPanel to completely remove the content from the DOM when it's not visible.
By default, it is set to false considering SEO and server-side rendering. Enable it only if you need to remove content from the DOM for performance.
<Stack className="gap-3 w-full">
<AccordionItem id="item-1">
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel unMountOnClose={true}>
Accordion is a component that allows you to show or hide content.
It is useful when you want to display a large amount of information
but don't want to overwhelm the user.
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item-2">
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel unMountOnClose={true}>
Yes, the Accordion component is accessible by default. It also supports ARIA attributes and roles for improved accessibility.
</AccordionPanel>
</AccordionItem>
</Stack>
Custom example
To further customize the AccordionItem component, you can add classes to the styleable parts of the AccordionHeader and AccordionPanel using the classNames prop. In the following use case, we wanted to remove the default horizontal padding for the Accordion, so px-0 class is applied to the button and panelContent parts, and add divider for AccordionItem
Depending on your product's use case, you can add the divider, override spacing, alignment, or any other style in a similar way. For the list of styleable parts, refer the Style API section below.
const servicesData = [
{
serviceName: 'Standard haircut with shampoo',
duration: '1hr',
cost: '$50'
},
{
serviceName: 'Scissors cut with shampoo',
duration: '30min',
cost: '$60'
},
{
serviceName: 'Zero fade with shampoo',
duration: '45min',
cost: '$55',
description: 'A timeless gentlemans cut - short back and sides haircut, please DO NOT choose this cut if would like any type of fade as the time allocation is different'
},
];
const ServiceSlot = ({ name, duration, cost, description }) => (
<Track
verticalAlign="top"
railStart={
<Avatar name="Freddy" size="40">
<AvatarIcon icon={ProfileIcon} />
</Avatar>
}
railEnd={
<IconButton
aria-label="More details"
icon={ChevronRightIcon}
size="standard"
variant="neutralTertiary"
/>
}
className="gap-3"
>
<Stack>
<Text className="text-body-12">{name}</Text>
<Box className="inline-flex items-center gap-2 text-body-12">
<Text className="text-body-12">{duration} ·</Text>
<TextLink>Details</TextLink>
<Text className="text-body-12">· {cost}</Text>
</Box>
{ description ? <Text className="text-body-12 text-secondary pt-2">{description}</Text> : null }
</Stack>
</Track>
);
return (
<Stack className="gap-3 w-full">
<AccordionItem isDefaultOpen hasDivider>
<AccordionHeader classNames={{ button: 'px-0' }}>Haircut services</AccordionHeader>
<AccordionPanel classNames={{ panelContent: 'px-0' }}>
<Box className="flex flex-col gap-8">
{servicesData.map((service) => <ServiceSlot key={service.serviceName} name={service.serviceName} duration={service.duration} cost={service.cost} description={service.description} />
)}
</Box>
</AccordionPanel>
</AccordionItem>
<AccordionItem hasDivider>
<AccordionHeader classNames={{ button: 'px-0' }}>Salon services</AccordionHeader>
<AccordionPanel classNames={{ panelContent: 'px-0' }}>
<Box className="flex flex-col gap-8">
<ServiceSlot name="Facial with detan" duration="30min" cost="$30" />
</Box>
</AccordionPanel>
</AccordionItem>
<AccordionItem hasDivider>
<AccordionHeader classNames={{ button: 'px-0' }}>Other services</AccordionHeader>
<AccordionPanel classNames={{ panelContent: 'px-0' }}>
<Box className="flex flex-col gap-8">
<ServiceSlot name="Book a consultation" duration="30min" cost="$10" />
</Box>
</AccordionPanel>
</AccordionItem>
</Stack>
);
API Reference
AccordionHeader
| Prop | Default | Description |
|---|---|---|
children | _ | React.ReactNodeThe content of the accordion header. |
as? | h3 | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'The HTML heading level / tag to use for the header |
onClick? | _ | functionCallback function that is called when the header is clicked. |
buttonRef? | _ | React.RefObject<HTMLButtonElement>Used to set the ref of the button element. |
AccordionItem
| Prop | Default | Description |
|---|---|---|
children | _ | React.ReactNodeThe content of the accordion item. |
id? | _ | stringThe unique id of the accordion item. This id should be used to set the open/closed state of each accordion item. |
isDefaultOpen? | false | booleanUsed to set the default open state of the item when used as uncontrolled component. |
isOpen? | _ | booleanUsed to set the open state of the item when used as controlled component. |
onToggle? | _ | ({ isOpen, id } : { isOpen: boolean, id: string }) => voidCallback function that is called when the item is toggled. isOpen is a boolean that indicates the open state of the item. id is the unique id of the item. |
hasDivider? | false | booleanAdds a bottom border below the accordion item. |
AccordionPanel
| Prop | Default | Description |
|---|---|---|
children | _ | React.ReactNodeThe content of the accordion panel. |
unMountOnClose? | false | booleanUsed to remove the content when the item is closed. |
Style API
Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.
Please refer to the Style API documentation for more insights.
AccordionItem parts
<React.Fragment>
<AccordionItem className="border border-input-critical" id="item-1">
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content.
</AccordionPanel>
</AccordionItem>
<AccordionItem className="border border-input-critical" id="item-2">
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default.
</AccordionPanel>
</AccordionItem>
</React.Fragment>
No parts available. Only root.
AccordionHeader parts
<React.Fragment>
<AccordionItem id="item-1">
<AccordionHeader
className="bg-accent-secondary p-1"
classNames={{
button: 'border border-input-critical',
buttonLabel: 'text-positive p-1 border',
iconEnd: 'fill-critical',
}}
>
What is Accordion?
</AccordionHeader>
<AccordionPanel>
Accordion is a component that allows you to show or hide content.
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item-2">
<AccordionHeader
className="bg-accent-secondary p-1"
classNames={{
button: 'border border-input-critical',
buttonLabel: 'text-positive p-1 border',
iconEnd: 'fill-critical',
}}
>
Is it accessible?
</AccordionHeader>
<AccordionPanel>
Yes, the Accordion component is accessible by default.
</AccordionPanel>
</AccordionItem>
</React.Fragment>
| Stylable Parts | Description |
|---|---|
| button | The clickable element that toggles the accordion panel. |
| buttonLabel | The text content of the accordion header. |
| iconEnd | The icon displayed at the end. |
AccordionPanel parts
<React.Fragment>
<AccordionItem id="item-1" isDefaultOpen={true}>
<AccordionHeader>What is Accordion?</AccordionHeader>
<AccordionPanel classNames={{ panelContent: 'bg-accent-secondary-pressed' }}>
Accordion is a component that allows you to show or hide content.
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item-2">
<AccordionHeader>Is it accessible?</AccordionHeader>
<AccordionPanel classNames={{ panelContent: 'bg-accent-secondary-pressed' }}>
Yes, the Accordion component is accessible by default.
</AccordionPanel>
</AccordionItem>
</React.Fragment>
| Stylable Parts | Description |
|---|---|
| panelContent | The text content of the accordion panel. |