Accordion
- Source: https://github.com/reach/reach-ui/tree/main/packages/accordion
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#accordion
An accordion is a vertically stacked group of collapsible sections. An accordion is composed of grouped buttons and panels. When a user selects an accordion button, its corresponding panel should switch between 'open' and 'collapsed' states.
Accordions follow many consistent patterns but do allow for some variability in behavior. For example, some accordions only allow one panel to be open at a time, where others may allow multiple or all panels to be open simultaneously. Similarly, many accordions will allow all panels to be simultaneously collapsed, while others may require one panel to be open at all times.
If you are familiar with the disclosure pattern, an accordion will feel very similar. The key distinction is that a disclosure is a standalone component that consists of a single button-panel-group. Because of this, you cannot navigate between different disclosures with a keyboard the same way you can with an accordion. To provide users with a predictable behavior between components, it is important to keep disclosures and accordions visually distinct across your app.
Installation
From the command line in your project directory, run npm install @reach/accordion
or yarn add @reach/accordion
. Then import the components and styles that you need:
npm install @reach/accordion# oryarn add @reach/accordion
import { Accordion, AccordionItem, AccordionButton, AccordionPanel,} from "@reach/accordion";import "@reach/accordion/styles.css";
Usage
function Example() { return ( <Accordion> <AccordionItem> <h3> <AccordionButton>Step 1: Do a thing</AccordionButton> </h3> <AccordionPanel> Here are some detailed instructions about doing a thing. I am very complex and probably contain a lot of content, so a user can hide or show me by clicking the button above. </AccordionPanel> </AccordionItem> <AccordionItem> <h3> <AccordionButton>Step 2: Do another thing</AccordionButton> </h3> <AccordionPanel> Here are some detailed instructions about doing yet another thing. There are a lot of things someone might want to do, so I am only going to talk about doing that other thing. I'll let my fellow accordion items go into detail about even more things. </AccordionPanel> </AccordionItem> </Accordion> );}
Accordion Headings
With most accordion components, the AccordionPanel
is treated as a semantic region
of the document, similar to an HTML section
or main
tag. By default, we assign role="region"
and to each AccordionPanel
, along with aria-labelledby
referencing the associated AccordionButton
component.
To improve the semantics of the markup further, the ARIA guidelines dictate that each accordion item's button should be wrapped in an element with role="heading"
, or more simply, and HTML heading tag. Because headings are necessarily dependent on the context of their surrounding content, we do not wrap the AccordionButton
inside of a heading tag by default. It is up to each developer to implement this detail in a way that makes sense for their application.
You can abstract a solution in a variety of ways. Perhaps you write an AccordionHeader
wrapper component that accepts a headingLevel
prop:
function AccordionHeader({ headingLevel = 2, props }) { let Comp = "h" + headingLevel; return ( <Comp> <AccordionButton {...props} /> </Comp> );}
You can also create a context-aware heading component so that the heading level increments appropriately as content is nested:
(() => { const HeadingContext = createContext(2); function MyAccordionSection(props) { return ( <div> <Heading>How to do a thing</Heading> <p> Below I am going to explain how you might do a thing, in two very important steps. </p> <Accordion> <ContextAwareAccordionItem> <AccordionHeader>Step 1: Do a thing</AccordionHeader> <AccordionPanel> Here are some detailed instructions about doing a thing. I am very complex and probably contain a lot of content, so a user can hide or show me by clicking the button above. </AccordionPanel> </ContextAwareAccordionItem> <AccordionItem> <AccordionHeader>Step 2: Do another thing</AccordionHeader> <AccordionPanel> Here are some detailed instructions about doing yet another thing. There are a lot of things someone might want to do, so I am only going to talk about doing that other thing. I'll let my fellow accordion items go into detail about even more things. </AccordionPanel> </AccordionItem> </Accordion> </div> ); } function AccordionHeader(props) { return ( <Heading> <AccordionButton {...props} /> </Heading> ); } function ContextAwareAccordionItem(props) { return ( <Section> <AccordionItem {...props} /> </Section> ); } function Heading(props) { let Comp = "h" + Math.min(useContext(HeadingContext), 6); return <Comp {...props} />; } function Section(props) { let headingLevel = useContext(HeadingContext); return ( <HeadingContext.Provider value={headingLevel + 1}> {props.children} </HeadingContext.Provider> ); } return <MyAccordionSection />;})();
Component API
Accordion
The wrapper component for all other accordion components. Each accordion component will consist of accordion items whose buttons are keyboard navigable using arrow keys.
<Accordion index={index} onChange={(value) => setIndex(value)}> <AccordionItem> <h3> <AccordionButton>Step 1: Do a thing</AccordionButton> </h3> <AccordionPanel>...</AccordionPanel> </AccordionItem> <AccordionItem> <h3> <AccordionButton>Step 2: Do another thing</AccordionButton> </h3> <AccordionPanel>...</AccordionPanel> </AccordionItem></Accordion>
Accordion CSS Selectors
Please see the styling guide.
[data-reach-accordion] {}
collapsible and multiple props
You can use the collapsible
prop to dictate that an accordion should allow all panels to be collapsed simultaneously. By default, one panel must be in an open
state at all times.
function Example() { return ( <Accordion collapsible> <AccordionItem> <h3> <AccordionButton>Step 1: Do a thing</AccordionButton> </h3> <AccordionPanel> Integer ad iaculis semper aenean nibh quisque hac eget volutpat, at dui sem accumsan cras congue mi varius egestas interdum, molestie blandit sociosqu sodales diam metus erat venenatis. </AccordionPanel> </AccordionItem> <AccordionItem> <h3> <AccordionButton>Step 2: Do another thing</AccordionButton> </h3> <AccordionPanel> Hendrerit faucibus litora justo aliquet inceptos gravida felis vel aenean, natoque fermentum nostra tempus ornare nam diam est, neque risus aliquam sapien vestibulum sociis integer eros. </AccordionPanel> </AccordionItem> </Accordion> );}
The multiple
prop dictates that any number of panels may be open at the same time. By default, when a user opens a new accordion item, the previously open item will be set to collapsed
.
function Example() { return ( <Accordion multiple> <AccordionItem> <h3> <AccordionButton>Step 1: Do a thing</AccordionButton> </h3> <AccordionPanel> Integer ad iaculis semper aenean nibh quisque hac eget volutpat, at dui sem accumsan cras congue mi varius egestas interdum, molestie blandit sociosqu sodales diam metus erat venenatis. </AccordionPanel> </AccordionItem> <AccordionItem> <h3> <AccordionButton>Step 2: Do another thing</AccordionButton> </h3> <AccordionPanel> Hendrerit faucibus litora justo aliquet inceptos gravida felis vel aenean, natoque fermentum nostra tempus ornare nam diam est, neque risus aliquam sapien vestibulum sociis integer eros. </AccordionPanel> </AccordionItem> </Accordion> );}
Using both props together dictates that any number of panels in an accordion can be open
or collapsed
at any time without regard to the state of other accordion items. These props are only relevant for uncontrolled accordion components, as the state of controlled accordion items is determined only by the index
prop.
function Example() { return ( <Accordion collapsible multiple> <AccordionItem> <h3> <AccordionButton>Step 1: Do a thing</AccordionButton> </h3> <AccordionPanel> Integer ad iaculis semper aenean nibh quisque hac eget volutpat, at dui sem accumsan cras congue mi varius egestas interdum, molestie blandit sociosqu sodales diam metus erat venenatis. </AccordionPanel> </AccordionItem> <AccordionItem> <h3> <AccordionButton>Step 2: Do another thing</AccordionButton> </h3> <AccordionPanel> Hendrerit faucibus litora justo aliquet inceptos gravida felis vel aenean, natoque fermentum nostra tempus ornare nam diam est, neque risus aliquam sapien vestibulum sociis integer eros. </AccordionPanel> </AccordionItem> </Accordion> );}
Controlled Accordion
If you want to control the accordion's open panels, you can do so by passing index
and onChange
props. The index corresponds with the order of each accordion item as they appear within an accordion. The index value passed sets its corresponding panel to an open
state.
const [index, setIndex] = React.useState(0);return ( <Accordion index={index} onChange={(value) => setIndex(value)}> <AccordionItem {...items[0].props} /> <AccordionItem {...items[1].props} /> <AccordionItem {...items[2].props} /> </Accordion>);
In a controlled accordion, multiple items can be set to open
by passing an array of indices to the index
prop.
const [indices, setIndices] = React.useState([0, 2]);function toggleItem(toggledIndex) { if (indices.includes(toggledIndex)) { setIndices(indices.filter((currentIndex) => currentIndex !== toggledIndex)); } else { setIndices([...indices, toggledIndex].sort()); }} return ( <Accordion index={indices} onChange={toggleItem}> <AccordionItem {...items[0].props} /> <AccordionItem {...items[1].props} /> <AccordionItem {...items[2].props} /> </Accordion>);
Accordion Props
Prop | Type | Required |
---|---|---|
as | string | Component | false |
children | node | true |
collapsible | boolean | false |
defaultIndex | number | number[] | false |
index | number | number[] | false |
multiple | boolean | false |
onChange | func | false |
readOnly | boolean | false |
Accordion as
as?: keyof JSX.IntrinsicElements | React.ComponentType
A string representing an HTML element or a React component that will tell the Accordion
what element to render. Defaults to div
.
NOTE: Many semantic elements, such as button
elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users.
Accordion children
children: React.ReactNode
Accordion
can accept AccordionItem
components as children.
<Accordion> <AccordionItem> <AccordionButton /> <AccordionPanel /> </AccordionItem> <AccordionItem> <AccordionButton /> <AccordionPanel /> </AccordionItem></Accordion>
Accordion collapsible
collapsible?: boolean
Whether or not all panels of an uncontrolled accordion can be toggled to a closed state. See the collapsible
and multiple
props section for details. Defaults to false
.
Accordion defaultIndex
defaultIndex?: number | number[]
A default value for the open panel's index or indices in an uncontrolled accordion component when it is initially rendered.
<Accordion defaultIndex={1}> <AccordionItem> <AccordionButton /> <AccordionPanel>I will be closed on the initial render!</AccordionPanel> </AccordionItem> <AccordionItem> <AccordionButton /> <AccordionPanel>I will be open on the initial render!</AccordionPanel> </AccordionItem></Accordion>
If an accordion has no defaultIndex, the initially rendered open panel depends on the collapsible
prop.
- If
collapsible
is set totrue
, without adefaultIndex
no panels will initially be open. Otherwise, the first panel at index0
will initially be open.
You can only pass an array of indices to defaultIndex
if you also set multiple
to true.
<Accordion defaultIndex={[0, 1]} multiple> <AccordionItem> <AccordionButton /> <AccordionPanel>I will be open on the initial render!</AccordionPanel> </AccordionItem> <AccordionItem> <AccordionButton /> <AccordionPanel>I will also be open on the initial render!</AccordionPanel> </AccordionItem></Accordion>
Accordion index
index?: number | number[]
The index or array of indices for open accordion panels. The index
prop should be used along with onChange
to create controlled accordion components.
const [indices, setIndices] = React.useState([0, 2]);function toggleItem(toggledIndex) { if (indices.includes(toggledIndex)) { setIndices(indices.filter((currentIndex) => currentIndex !== toggledIndex)); } else { setIndices([...indices, toggledIndex].sort()); }} return ( <Accordion index={indices} onChange={toggleItem}> <AccordionItem {...items[0].props} /> <AccordionItem {...items[1].props} /> <AccordionItem {...items[2].props} /> </Accordion>);
Accordion multiple
multiple?: boolean
Whether or not multiple panels in an uncontrolled accordion can be opened at the same time. See the collapsible
and multiple
props section for details. Defaults to false
.
Accordion onChange
onChange?: (value: number) => void
The callback that is fired when an accordion item's open state is changed.
Accordion readOnly
readOnly?: boolean
Whether or not an uncontrolled accordion is read-only (meaning that the user cannot toggle its state with a normal interaction). Defaults to false
.
Generally speaking, you probably want to avoid this, as it can be confusing especially when navigating by keyboard. However, this may be useful if you want to lock an accordion under certain conditions (perhaps user authentication is required to access the content). In these instances, you may want to include an alert when a user tries to activate a read-only accordion panel to let them know why it does not toggle as may be expected.
AccordionItem
A group that wraps an accordion's button and panel components.
AccordionItem CSS Selectors
Please see the styling guide.
[data-reach-accordion-item] { /* styles for all accordion items */}[data-reach-accordion-item][data-state="open"] { /* styles for all open accordion items */}[data-reach-accordion-item][data-state="collapsed"] { /* styles for all collapsed accordion items */}[data-reach-accordion-item][data-disabled] { /* styles for all disabled accordion items */}[data-reach-accordion-item][data-read-only] { /* styles for all read-only accordion items */}
AccordionItem Props
AccordionItem as
as?: keyof JSX.IntrinsicElements | React.ComponentType
A string representing an HTML element or a React component that will tell the AccordionItem
what element to render. Defaults to div
.
NOTE: Many semantic elements, such as button
elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users.
AccordionItem children
children: React.ReactNode
An AccordionItem
expects to receive an AccordionButton
and AccordionPanel
components as its children, though you can also nest other components within an AccordionItem
if you want some persistant content that is relevant to the section but not collapsible when the AccordionButton
is toggled.
<Accordion defaultIndex={[0, 1]} multiple> <AccordionItem> <AccordionButton>Step 1: Do this important thing</AccordionButton> <AccordionPanel>Detailed instructions about step 1.</AccordionPanel> </AccordionItem> <AccordionItem> <AccordionButton>Step 2: Do this other important thing</AccordionButton> <AccordionPanel>Detailed instructions about step 1.</AccordionPanel> {/* the following component will not collapse! */} <SomeCalloutBox> Important: you should always consult the user manual before doing step 2! </SomeCalloutBox> </AccordionItem></Accordion>
AccordionItem disabled
disabled?: boolean
Whether or not an accordion panel is disabled from user interaction. Defaults to false
.
AccordionButton
The trigger button a user clicks to interact with an accordion.
AccordionButton CSS Selectors
Please see the styling guide.
[data-reach-accordion-button] { /* styles for buttons in all accordion items */}[data-reach-accordion-button][aria-expanded] { /* styles for buttons in open accordion items */}[data-reach-accordion-button][disabled] { /* styles for all buttons in disabled accordion items */}
AccordionButton Props
AccordionButton as
as?: keyof JSX.IntrinsicElements | React.ComponentType
A string representing an HTML element or a React component that will tell the AccordionButton
what element to render. Defaults to button
.
NOTE: Many semantic elements, such as button
elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users.
AccordionButton children
children: React.ReactNode
Typically a text string that serves as a label for the accordion, though nested DOM nodes can be passed as well so long as they are valid children of interactive elements.
If you need to group interactive elements within an accordion's button, we recommend grouping the button inside of a wrapper element rather than including invalid HTML inside the button:
(() => { function Example() { return ( <Accordion> <AccordionItem> <GroupedAccordionHeader>Option 1</GroupedAccordionHeader> <StyledAccordionPanel> Ante rhoncus facilisis iaculis nostra faucibus vehicula ac consectetur pretium, lacus nunc consequat id viverra facilisi ligula eleifend, congue gravida malesuada proin scelerisque luctus est convallis. </StyledAccordionPanel> </AccordionItem> <AccordionItem> <GroupedAccordionHeader>Option 2</GroupedAccordionHeader> <StyledAccordionPanel> Ante rhoncus facilisis iaculis nostra faucibus vehicula ac consectetur pretium, lacus nunc consequat id viverra facilisi ligula eleifend, congue gravida malesuada proin scelerisque luctus est convallis. </StyledAccordionPanel> </AccordionItem> <AccordionItem> <GroupedAccordionHeader>Option 3</GroupedAccordionHeader> <StyledAccordionPanel> Ante rhoncus facilisis iaculis nostra faucibus vehicula ac consectetur pretium, lacus nunc consequat id viverra facilisi ligula eleifend, congue gravida malesuada proin scelerisque luctus est convallis. </StyledAccordionPanel> </AccordionItem> </Accordion> ); } function StyledAccordionPanel(props) { return <AccordionPanel style={{ padding: 10 }} {...props} />; } function GroupedAccordionHeader({ children }) { return ( <div style={{ alignItems: "center", background: "#eee", border: "1px solid #888", borderRadius: 3, margin: "9px 0", display: "flex", justifyContent: "space-between", padding: "4px 10px", }} > <AccordionButton style={{ appearance: "none", background: 0, border: 0, boxShadow: "none", color: "inherit", display: "block", textAlign: "inherit", flexGrow: 1, flexShrink: 0, font: "inherit", fontWeight: "bolder", margin: 0, padding: "10px 0", }} > {children} </AccordionButton> <Menu> <MenuButton style={{ margin: 0 }}> <span>Actions</span> </MenuButton> <MenuList> <MenuItem onSelect={() => console.log("Download")}> Download </MenuItem> <MenuItem onSelect={() => console.log("Copy")}> Create a Copy </MenuItem> </MenuList> </Menu> </div> ); } return <Example />;})();
- Further reading: Be Wary of Nesting Roles by Adrian Roselli
AccordionPanel
The collapsible panel in which inner content for an accordion item is rendered.
AccordionPanel CSS Selectors
Please see the styling guide.
[data-reach-accordion-panel] { /* styles for all accordion panels */}[data-reach-accordion-panel][data-state="open"] { /* styles for all open accordion panels */}[data-reach-accordion-panel][data-state="collapsed"] { /* styles for all collapsed accordion panels */}[data-reach-accordion-panel][data-disabled] { /* styles for all disabled accordion panels */}
AccordionPanel Props
AccordionPanel as
as?: keyof JSX.IntrinsicElements | React.ComponentType
A string representing an HTML element or a React component that will tell the AccordionPanel
what element to render. Defaults to div
.
NOTE: Many semantic elements, such as button
elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users.
AccordionPanel children
children: React.ReactNode
Inner collapsible content for the accordion item.
useAccordionContext
function useAccordionContext(): { id: string | undefined; openPanels: number[] }
A hook that exposes data for a given Accordion
component to its descendants.
useAccordionItemContext
function useAccordionItemContext(): { index: number; isExpanded: boolean }
A hook that exposes data for a given AccordionItem
component to its descendants.