Accordion
From the ARIA Authoring Practices Guide (APG):
An accordion is a vertically stacked set of interactive headings that each contain a title, content snippet, or thumbnail representing a section of content. The headings function as controls that enable users to reveal or hide their associated sections of content. Accordions are commonly used to reduce the need to scroll when presenting multiple sections of content on a single page.
Usage and Examples
Components and hooks for the accordion pattern can be imported directly from the package itself, or from the /accordion
sub-module.
import { Accordion } from 'react-aria-widgets';
import { Accordion } from 'react-aria-widgets/accordion';
Importing from the sub-module means potentially smaller bundle sizes, but if you're writing a TypeScript application, you may need to change moduleResolution
to node16
in your tsconfig.json
. For more information, see the FAQ.
Additionally, because these components come with no styling, they will not have the proper expand/collapse behavior out of the box. For the sake of demonstrating the proper behavior, the examples on this page will be given the following styles:
/*
* .react-aria-widgets-accordion-panel is a CSS class provided by default,
* and React ARIA Widgets exposes the accordion's state via HTML attributes
* so they can be targeted with CSS.
*/
.react-aria-widgets-accordion-panel[data-expanded=false] {
display: none;
}
For more information, see the styling section.
Basic Usage
A basic accordion consists of an <Accordion>
wrapping around one or more <AccordionItem>
s, where each <AccordionItem>
has an <AccordionHeader>
and an <AccordionPanel>
.
Out of the box, React ARIA Widgets provides the focus control as described in the APG. You can use ArrowDown, ArrowUp, Home, and End to switch focus between each item.
A headerLevel
prop must be supplied to the <Accordion>
, and each <AccordionItem>
must have a unique (amongst its siblings) id
prop.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
function BasicAccordion() {
return (
<Accordion headerLevel={ 4 }>
<AccordionItem id="item1">
<AccordionHeader>
Accordion Item 1
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item2">
<AccordionHeader>
Accordion Item 2
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item3">
<AccordionHeader>
Accordion Item 3
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
</Accordion>
);
}
Disable Multiple Expanded Sections
By default, multiple sections can be expanded and collapsed at the same time, but this behavior can be turned off with the allowMultiple
prop.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
function DisableMultipleAccordion() {
return (
<Accordion headerLevel={ 4 } allowMultiple={ false }>
<AccordionItem id="item1">
<AccordionHeader>
Accordion Item 1
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item2">
<AccordionHeader>
Accordion Item 2
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item3">
<AccordionHeader>
Accordion Item 3
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
</Accordion>
);
}
Disable Collapsing All Sections
By default, all of the accordion items can be simultaneously collapsed. If the allowCollapseLast
prop is false
, the final expanded section cannot be collapsed.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
function DisableCollapseLastAccordion() {
return (
<Accordion headerLevel={ 4 } allowCollapseLast={ false }>
<AccordionItem id="item1">
<AccordionHeader>
Accordion Item 1
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item2">
<AccordionHeader>
Accordion Item 2
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item3">
<AccordionHeader>
Accordion Item 3
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
</Accordion>
);
}
Disabling allowMultiple
and allowCollapseLast
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
function DisableBothAccordion() {
return (
<Accordion headerLevel={ 4 } allowMultiple={ false } allowCollapseLast={ false }>
<AccordionItem id="item1">
<AccordionHeader>
Accordion Item 1
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item2">
<AccordionHeader>
Accordion Item 2
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item3">
<AccordionHeader>
Accordion Item 3
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
</Accordion>
);
}
Rendering with Render Props
In addition to normal React nodes, <AccordionHeader>
and <AccordionPanel>
both accept a render function as children
. These render functions have access to all of the fields and methods that pertain to the accordion.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
const ITEMS = [ 'item1', 'item2', 'item3' ];
function RenderPropAccordion() {
return (
<Accordion headerlevel={ 4 }>
{ ITEMS.map((id, index) => (
<AccordionItem key={ id } id={ id }>
<AccordionHeader>
{ ({ id, getIsExpanded }) => (
<>
Accordion Item { index + 1 }: Expanded = <code>{ getIsExpanded(id).toString() }</code>
</>
) }
</AccordionHeader>
<AccordionPanel>
{ ({ id, headerLevel, allowMultiple, allowCollapseLast }) => (
<ul className="mb-4">
<li><code>id</code> = <code>{ id }</code></li>
<li><code>headerLevel</code> = <code>{ headerLevel }</code></li>
<li><code>allowMultiple</code> = <code>{ allowMultiple.toString() }</code></li>
<li><code>allowCollapseLast</code> = <code>{ allowCollapseLast.toString() }</code></li>
</ul>
) }
</AccordionPanel>
</AccordionItem>
)) }
</Accordion>
);
}
Prevent Expanding/Collapsing Accordion Items
Accordion items can be manually disabled with the method toggleDisabled
, preventing them from being expanded/collapsed. Note that <AccordionHeader>
will set the aria-disabled
attribute, but not the disabled
attribute, which has various implications. For example, though the panel will not expand or collapse, the button is still focusable and will still trigger events.
See the MDN Web Docs for more information.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
const ITEMS = [ 'item1', 'item2', 'item3' ];
function DisableItemAccordion() {
return (
<Accordion headerLevel={ 4 }>
{ ITEMS.map((id, index) => (
<AccordionItem key={ id } id={ id }>
<AccordionHeader>
{ ({ id, getIsDisabled }) => (
<>
Accordion Item { index + 1 }: Disabled = <code>{ getIsDisabled(id).toString() }</code>
</>
) }
</AccordionHeader>
<AccordionPanel>
{ ({ id, toggleDisabled, getIsDisabled }) => (
<button className="button is-primary mb-4" type="button" onClick={ () => toggleDisabled(id) }>
{ getIsDisabled(id) ? 'Enable' : 'Disable' } Item
</button>
) }
</AccordionPanel>
</AccordionItem>
)) }
</Accordion>
);
}
Initialize Expanded/Disabled State
By default, all accordion items are collapsed and enabled. You can initialize certain items to be expanded or disabled by passing arrays of string IDs to the initialExpanded
and initialDisabled
props.
If allowMultiple
is disabled, React ARIA Widgets will only expand the first ID in initialExpanded
. However, it does so naively - it essentially just checks initialExpanded[0]
. Due to implementation limitations, it currently cannot validate that the supplied IDs actually pertain to an accordion item and intelligently pick the first valid ID.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
const ITEMS = [ 'item1', 'item2', 'item3' ];
function InitializeStateAccordion() {
return (
<Accordion
headerLevel={ 4 }
initialExpanded={ [ 'item1', 'item2' ] }
initialDisabled={ [ 'item2' ] }
{ ...props }
>
{ ITEMS.map((id, index) => (
<AccordionItem key={ id } id={ id }>
<AccordionHeader>
Accordion Item { index + 1 }
</AccordionHeader>
<AccordionPanel>
{ ({ id, getIsExpanded, getIsDisabled }) => (
<ul className="mb-4">
<li><code>getIsExpanded(id)</code> = <code>{ getIsExpanded(id).toString() }</code></li>
<li><code>getIsDisabled(id)</code> = <code>{ getIsDisabled(id).toString() }</code></li>
</ul>
) }
</AccordionPanel>
</AccordionItem>
)) }
</Accordion>
);
}
Focusing Items
<AccordionHeader>
automatically attaches a keydown event that implements the keyboard/focus behavior described in the APG. Try tabbing to one of the buttons and pressing the ArrowDown, ArrowUp, Home, or End keys. Additionally, you can manually focus accordion items through the methods provided by React ARIA Widgets.
import { useState } from 'react';
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
const ITEMS = [ 'item1', 'item2', 'item3' ];
function FocusAccordion() {
return (
<Accordion headerLevel={ 4 }>
{ ITEMS.map((id, index) => (
<AccordionItem key={ id } id={ id }>
<AccordionHeader>
Accordion Item { index + 1 }: ID = <code>{ id }</code>
</AccordionHeader>
<AccordionPanel>
{ (args) => <FocusForm { ...args } /> }
</AccordionPanel>
</AccordionItem>
)) }
</Accordion>
);
}
function FocusForm({
id,
focusItemId,
focusPrevItem,
focusNextItem,
focusFirstItem,
focusLastItem,
}) {
const [ inputItemId, setInputItemId ] = useState('');
return (
<form className="mb-4" onSubmit={ (e) => { e.preventDefault(); focusItemId(inputItemId); } }>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label" htmlFor={ `${id}-focus-input` }>
Item ID:
</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
id={ `${id}-focus-input` }
type="text"
onChange={ (e) => setInputItemId(e.target.value) }
className="input"
/>
</div>
</div>
<div className="field">
<div className="control">
<button className="button is-primary" type="submit">
Focus Item
</button>
</div>
</div>
</div>
</div>
<div className="field is-grouped is-grouped-centered">
<div className="control">
<button className="button" type="button" onClick={ () => focusFirstItem() }>
Focus First Item
</button>
</div>
<div className="control">
<button className="button" type="button" onClick={ () => focusPrevItem(id) }>
Focus Previous Item
</button>
</div>
<div className="control">
<button className="button" type="button" onClick={ () => focusNextItem(id) }>
Focus Next Item
</button>
</div>
<div className="control">
<button className="button" type="button" onClick={ () => focusLastItem() }>
Focus Last Item
</button>
</div>
</div>
</form>
);
}
Callbacks on State Changes
You can pass callback functions that fire after state changes:
onToggleExpanded
- receives the items that are expandedonToggleDisabled
- receives the items that are disabledonFocusItem
- receives the item that was focused
Note that onFocusItem
doesn't trigger for focus events in general, but rather, when React ARIA Widgets' focus methods are called. In other words, tabbing to a button won't trigger it, but pressing ArrowDown will.
Try opening your browser's developer tools and playing with the example below.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
const ITEMS = [ 'item1', 'item2', 'item3' ];
function CallbackAccordion() {
return (
<Accordion
headerlevel={ 4 }
onToggleExpanded={ expandedItems => console.log(expandedItems) }
onToggleDisabled={ disabledItems => console.log(disabledItems) }
onFocusChange={ ({ elem, index, id }) => console.log(elem, index, id) }
>
{ ITEMS.map((id, index) => (
<AccordionItem key={ id } id={ id }>
<AccordionHeader>
{ ({ id, getIsDisabled }) => (
<>
Accordion Item { index + 1 }: Disabled = <code>{ getIsDisabled(id).toString() }</code>
</>
) }
</AccordionHeader>
<AccordionPanel>
{ ({ id, toggleDisabled, getIsDisabled }) => (
<button className="button is-primary mb-4" type="button" onClick={ () => toggleDisabled(id) }>
{ getIsDisabled(id) ? 'Enable' : 'Disable' } Item
</button>
) }
</AccordionPanel>
</AccordionItem>
)) }
</Accordion>
);
}
Controlling State
As previously demonstrated, state can be manually controlled from "below" the accordion by using render props. One could also create custom implementations of <AccordionHeader
or <AccordionPanel>
by using the hooks useAccordionContext
and useAccordionItemContext
.
State can be controlled from "above" the accordion by using useAccordion
, a hook that provides methods to manage the state, and <ControlledAccordion>
, a thin wrapper over the context provider that passes those methods down. Unlike <Accordion>
, <ControlledAccordion>
doesn't call useAccordion
internally, allowing you to choose where to use it.
import { useAccordion, ControlledAccordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
const ITEMS = [ 'item1', 'item2', 'item3' ];
function RemoteControlAccordion(props) {
const contextValue = useAccordion(props);
const { toggleExpanded, toggleDisabled, focusItemId, getIsDisabled } = contextValue;
const toggleExpandButtons = [];
const toggleDisableButtons = [];
const focusButtons = [];
const accordionItems = [];
ITEMS.forEach((id, index) => {
toggleExpandButtons.push(
<div className="control" key={ id }>
<button
type="button"
value={ id }
onClick={ (e) => toggleExpanded(e.currentTarget.value) }
className="button is-primary"
>
Expand/Collapse { id }
</button>
</div>
);
toggleDisableButtons.push(
<div className="control" key={ id }>
<button
type="button"
value={ id }
onClick={ (e) => toggleDisabled(e.currentTarget.value) }
className="button is-primary"
>
Enable/Disable { id }
</button>
</div>
);
focusButtons.push(
<div className="control" key={ id }>
<button
type="button"
value={ id }
onClick={ (e) => focusItemId(e.currentTarget.value) }
className="button is-primary"
>
Focus { id }
</button>
</div>
);
accordionItems.push(
<AccordionItem id={ id } key={ id }>
<AccordionHeader>
Accordion Item { index + 1 }: Disabled = <code>{ getIsDisabled(id).toString() }</code>
</AccordionHeader>
<AccordionPanel>
<p className="mb-4">Hello world!</p>
</AccordionPanel>
</AccordionItem>
);
});
return (
<>
<form onSubmit={ e => e.preventDefault() } style={{ paddingBottom: '1rem' }}>
<fieldset className="field is-grouped">
<legend className="has-text-weight-semibold">Expand/Collapse Items</legend>
{ toggleExpandButtons }
</fieldset>
<fieldset className="field is-grouped">
<legend className="has-text-weight-semibold">Enable/Disable Items</legend>
{ toggleDisableButtons }
</fieldset>
<fieldset className="field is-grouped">
<legend className="has-text-weight-semibold">Focus Items</legend>
{ focusButtons }
</fieldset>
</form>
<ControlledAccordion contextValue={ contextValue }>
{ accordionItems }
</ControlledAccordion>
</>
);
}
Styling
There are a few different ways to style <AccordionHeader>
and <AccordionPanel>
:
- Write CSS that targets the default classes applied by React ARIA Widgets
- Supply a function for
className
that receives state and returns a string - Supply a string for
className
- Supply a function for
style
that receives state and returns an object - Supply an object for
style
And, as previously alluded to, you can dynamically style your content by using a render function to render your content.
There are a few things to note when styling your content:
- If you supply a string or function for
className
, it will replace the default class rather than append to it - React ARIA Widgets exposes the accordion's state onto the DOM using various HTML attributes, allowing them to be targeted with CSS selectors
- If you wish you use the
hidden
attribute to collapse your panels, unfortunately React ARIA Widgets currently does not have a good API for doing so. You can create your own accordion panel implementation using the hooks provided, but that's an admittedly awkward workaround. It's arguable though that in most cases, usingdisplay: none;
has better semantics thanhidden
. For more information, see the FAQ.
Component | HTML Element | Default CSS Class | State Selector | Selector Description |
---|---|---|---|---|
<AccordionHeader> | <h1> to <h6> | react-aria-widgets-accordion-header | [data-expanded=true | false] | Whether the panel is expanded or collapsed. |
[data-disabled=true | false] | Whether toggling visibility is enabled or disabled. | |||
<AccordionHeader> | <button> | react-aria-widgets-accordion-button | [aria-expanded=true | false] | Whether the panel is expanded or collapsed. |
[aria-disabled=true | false] | Whether toggling visibility is enabled or disabled. | |||
<AccordionPanel> | <section> by default | react-aria-widgets-accordion-panel | [data-expanded=true | false] | Whether the panel is expanded or collapsed. |
[data-disabled=true | false] | Whether toggling visibility is enabled or disabled. |
The rendered markup looks something like this:
<h1 class="react-aria-widgets-accordion-header" data-expanded="false" data-disabled="false">
<button class="react-aria-widgets-accordion-button" aria-expanded="false" aria-disabled="false">
Accordion Item Header
</button>
</h1>
<section class="react-aria-widgets-panel" data-expanded="false" data-disabled="false">
Hello world!
</section>
In the following example, each of the accordion items are styled using one of the provided methods.
This accordion item is styled by passing in strings for className
and CSS that targets the supplied classes and the state exposed by React ARIA Widgets.
This accordion item is styled by passing in functions for className
. These functions have access to the accordion's state, allowing you to dynamically apply classes.
import { Accordion, AccordionItem, AccordionHeader, AccordionPanel } from 'react-aria-widgets/accordion';
function StyledAccordion() {
return (
<Accordion headerLevel={ 4 }>
<AccordionItem id="item1">
<AccordionHeader>
Accordion Item 1
</AccordionHeader>
<AccordionPanel>
<p>
This accordion item is styled by CSS that targets the default classes provided by React ARIA
Widgets. Since React ARIA Widgets also exposes the accordion's state via HTML data attributes,
we can target selectors such as <code>[data-expanded]</code> or <code>[data-disabled]</code>.
</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item2">
<AccordionHeader
headerProps={{ className: 'custom-accordion-header' }}
buttonProps={{ className: 'custom-accordion-button' }}
>
Accordion Item 2
</AccordionHeader>
<AccordionPanel className="custom-accordion-panel">
<p>
This accordion item is styled by passing in strings for <code>className</code> and
CSS that targets the supplied classes and the state exposed by React ARIA Widgets.
</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item3">
<AccordionHeader
headerProps={{ style: { color: 'hsl(217, 71%, 45%)' } }}
buttonProps={{ style: { color: 'inherit' } }}
>
Accordion Item 3
</AccordionHeader>
<AccordionPanel style={{ color: 'hsl(217, 71%, 45%)' }}>
<p className="mb-4">
This accordion item is styled by passing in objects for <code>style</code>.
</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item4">
<AccordionHeader
headerProps={{ className: ({ isExpanded }) => `another-custom-header ${isExpanded ? 'expanded' : 'collapsed'}` }}
buttonProps={{ className: ({ isExpanded }) => `another-custom-button ${isExpanded ? 'expanded' : 'collapsed'}` }}
>
Accordion Item 4
</AccordionHeader>
<AccordionPanel className={ ({ isExpanded }) => `another-custom-panel ${isExpanded ? 'expanded' : 'collapsed'}` }>
<p>
This accordion item is styled by passing in functions for <code>className</code>. These functions
have access to the accordion's state, allowing you to dynamically apply classes.
</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item5">
<AccordionHeader
headerProps={{ style: ({ isExpanded }) => isExpanded ? { color: 'hsl(0, 0%, 100%)' } : {} }}
buttonProps={{ style: ({ isExpanded }) => isExpanded ? { color: 'inherit', backgroundColor: 'hsl(217, 71%, 53%' } : {} }}
>
Accordion Item 5
</AccordionHeader>
<AccordionPanel style={ ({ isExpanded }) => isExpanded ? {} : { display: 'none' } }>
<p className="mb-4">
This accordion item is styled by passing in functions for <code>style</code>. As before, these
functions allow you to dynamically apply styles based on the accordion's state.
</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="item6">
<AccordionHeader>
{ ({ id, getIsExpanded }) => (
<span
className={ getIsExpanded(id) ? 'expanded' : 'collapsed' }
style={ getIsExpanded(id) ? { color: 'hsl(217, 71%, 45%)' } : {} }
>
Accordion Item 6
</span>
) }
</AccordionHeader>
<AccordionPanel>
{ ({ id, getIsExpanded }) => (
<p
className={ getIsExpanded(id) ? 'expanded' : 'collapsed' }
style={ getIsExpanded(id) ? { color: 'hsl(217, 71%, 45%)' } : {} }
>
The content for this accordion item is rendered with a render function. Since these render
functions have access to the accordion's state, you can dynamically style your content.
</p>
) }
</AccordionPanel>
</AccordionItem>
</Accordion>
);
}
.react-aria-widgets-accordion-panel[data-expanded=false] {
display: none;
}
.custom-accordion-panel[data-expanded=false] {
display: none;
}
.another-custom-panel.collapsed {
display: none;
}
Further Customization
React ARIA Widgets exposes all of the hooks, contexts, components, etc. that it uses, allowing you to create your own accordion implementations.
Creating a Custom <Accordion>
As mentioned in Controlling State, one can simply combine useAccordion
and <ControlledAccordion>
to create a new version of <Accordion>
.
import { useAccordion, ControlledAccordion } from 'react-aria-widgets/accordion';
function CustomAccordion({
children = null,
...rest
}) {
const contextValue = useAccordion(rest);
return (
<ControlledAccordion contextValue={ contextValue }>
{ children }
</ControlledAccordion>
);
}
Creating Custom Headers and Panels
You can use the hook useAccordionContext
to read and modify the accordion state that gets sent down from <ControlledAccordion>
. We'll be using it to create our own accordion headers and panels.
We won't be using them in this example, but for the sake of convenience, React ARIA Widgets also provides the components <BaseAccordionHeader>
and <BaseAccordionPanel>
to simplify building your own accordions. They're essentially just thin wrappers over HTML, but they use TypeScript and PropTypes to help remind you what HTML attributes are needed to fulfill the APG.
import { useAccordionContext } from 'react-aria-widgets/accordion';
function CustomAccordionHeader({ children = null, id }) {
const {
headerLevel,
getIsExpanded,
getIsDisabled,
toggleExpanded,
pushItemRef,
focusPrevItem,
focusNextItem,
focusFirstItem,
focusLastItem,
} = useAccordionContext();
const HeaderElement = `h${headerLevel}`;
const isExpanded = getIsExpanded(id);
const isDisabled = getIsDisabled(id);
const refCallback = (ref) => {
pushItemRef(ref, id);
};
const onClick = () => {
toggleExpanded(id);
};
const onKeyDown = (event) => {
const { key } = event;
if(key === 'ArrowUp') {
event.preventDefault();
focusPrevItem(id);
}
else if(key === 'ArrowDown') {
event.preventDefault();
focusNextItem(id);
}
else if(key === 'Home') {
event.preventDefault();
focusFirstItem();
}
else if(key === 'End') {
event.preventDefault();
focusLastItem();
}
};
return (
<HeaderElement className="my-accordion-header">
<button
type="button"
className="button is-primary my-accordion-button is-flex is-align-items-baseline has-text-right"
id={ id }
onClick={ onClick }
onKeyDown={ onKeyDown }
aria-controls={ `${id}-panel` }
aria-expanded={ isExpanded }
aria-disabled={ isDisabled }
ref={ refCallback }
>
{ children }
<i
className={ `fa-solid fa-chevron-${isExpanded ? 'down' : 'right'} is-flex-grow-1` }
aria-hidden="true"
/>
</button>
</HeaderElement>
);
}
import { useAccordionContext } from 'react-aria-widgets/accordion';
function CustomAccordionPanel({ children = null, id }) {
const { getIsExpanded } = useAccordionContext();
const isExpanded = getIsExpanded(id);
return (
<section
id={ `${id}-panel` }
aria-labelledby={ id }
className={ `my-accordion-panel ${isExpanded ? 'expanded' : 'collapsed'} content` }
>
{ children }
</section>
);
}
.my-accordion-header {
margin-bottom: 1rem;
}
.my-accordion-button {
width: 100%;
}
.my-accordion-panel.collapsed {
display: none;
}
Putting It All Together
You'll notice that we didn't create another version of <AccordionItem>
. Its main job is to make sure that the header and panel both have the same ID, and we won't need that in this example.
Here's the completed accordion:
Why don't scientists trust atoms? Because they make up everything!
import CustomAccordion from './CustomAccordion';
import CustomAccordionHeader from './CustomAccordionHeader';
import CustomAccordionPanel from './CustomAccordionPanel';
function MyAccordion(props) {
return (
<CustomAccordion { ...props }>
<CustomAccordionHeader id="item1">
Joke #1
</CustomAccordionHeader>
<CustomAccordionPanel id="item1">
<p>Why don't scientists trust atoms? Because they make up everything!</p>
</CustomAccordionPanel>
<CustomAccordionHeader id="item2">
Joke #2
</CustomAccordionHeader>
<CustomAccordionPanel id="item2">
Why did the bicycle fall over? Because it was two tired!
</CustomAccordionPanel>
<CustomAccordionHeader id="item3">
Joke #3
</CustomAccordionHeader>
<CustomAccordionPanel id="item3">
What do you call fake spaghetti? An "impasta"!
</CustomAccordionPanel>
</CustomAccordion>
);
}
API
Components
<Accordion>
Provides accordion state and functionality to its constituent components. It passes this data down to child components via the context API, which can be read with the hook useAccordionContext
.
Props
The type definition for this component's props are exported as AccordionProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
children | React.ReactNode | null | React nodes that represent the accordion's constituent headers and panels. Does not have to be the components provided by React ARIA Widgets. | |
allowMultiple | boolean | true | Determines whether or not multiple accordion items can be expanded at the same time. | |
allowCollapseLast | boolean | true | Determines whether or not the last expanded panel can be collapsed. | |
headerLevel | 1 | 2 | 3 | 4 | 5 | 6 | Yes | Determines the HTML heading element (e.g. <h1> ) of each accordion header. | |
initialExpanded | string[] | [] | Determines which accordion items (identified by their ID) should be expanded on the initial mount. If allowMultiple is off, the first element in the array is picked naively. | |
initialDisabled | string[] | [] | Determines which accordion items (identified by their ID) should be prevented from expanding or collapsing on the initial mount. | |
onToggleExpanded | (expandedItems: Set<string>) => void; | undefined | Callback to be fired after an item is expanded or collapsed. Receives the currently-expanded item IDs as an argument. | |
onToggleDisabled | (disabledItems: Set<string>) => void; | undefined | Callback to be fired after an item is enabled/disabled. Receives the currently-disabled item IDs as an argument. | |
onFocusItem | (args: {
elem: HTMLButtonElement | HTMLElement | null;
index: number;
id: string;
}) => void; | undefined | Callback to be fired after an item receives focus. Note that this only runs when using one of the focus methods provided by useAccordion . |
<AccordionItem>
Represents a header/panel pair. Helps ensure that they both have the same ID and generates HTML IDs for attributes like id
and aria-labelledby
. Passes information down to child components via the context API, which can be read with the hook useAccordionItemContext
.
Props
The type definition for this component's props are exported as AccordionItemProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
children | React.ReactNode | null | Technically allows for anything renderable by React, but you should pass in components that represent the accordion's header and panel (e.g. <AccordionHeader> and <AccordionPanel> ). | |
id | string | Yes | A string that uniquely identifies this header/panel pair. IDs do not have to be unique globally, but they do have to be unique amongst its siblings. |
<ControlledAccordion>
Acts similarly to <Accordion>
in that its role is to act as a context provider for the accordion's fields and methods. However, unlike <Accordion>
, it doesn't use the useAccordion
hook, giving you the freedom to choose where to use it.
Props
The type definition for this component's props are exported as ControlledAccordionProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
contextValue | AccordionMembers | Yes | The object returned by useAccordion (i.e. the accordion's fields and methods). |
<AccordionHeader>
Represents the header of an accordion. Receives the fields and methods from the accordion contexts by using the useAccordionContext
and useAccordionItemContext
hooks. Implements event handlers to manage focus and expand/collapse its panel's visibility. Also sets the HTML/ARIA attributes needed to fulfill the APG.
Props
The type definition for this component's props are exported as AccordionHeaderProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
children | React.ReactNode | AccordionRenderFunction | null | The content to be rendered. This can either be a string, component, etc., or a render function. If you provide a render function, it will receive all of the fields and methods provided by Note that because the content is placed inside of a | |
headerProps | AccordionHeaderElementProps | {} | An object that is spread onto the underlying HTML heading element, allowing you to pass props and attributes to it. You can supply a string or If no | |
buttonProps | AccordionButtonElementProps | {} | An object that is spread onto the underlying HTML button element, allowing you to pass props and attributes to it. You can supply a string or If no |
Data Attributes
HTML Element | Attribute | Values |
---|---|---|
<h1> to <h6> | [data-expanded] | true | false |
[data-disabled] | true | false | |
<button> | [aria-expanded] | true | false |
[aria-disabled] | true | false |
<AccordionPanel>
Represents the body of content for an accordion item. Receives the fields and methods from the accordion contexts by using the useAccordionContext
and useAccordionItemContext
hooks. Sets the HTML/ARIA attributes needed to fulfill the APG.
Props
Note that if you pass any props other than those listed below, they will be spread onto the underlying element (indicated by the as
prop).
The type definitions for this component's props are exported as AccordionPanelProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
children | React.ReactNode | AccordionRenderFunction | null | The content to be rendered. This can either be a string, component, etc., or a render function. If you provide a render function, it will receive all of the fields and methods provided by | |
className | string | AccordionRenderClass | react-aria-widgets-accordion-panel | A string or function that determines the CSS class. If you supply a function, it will receive state information (see AccordionRenderStyleData ) that will allow you to dynamically set the class. | |
style | React.CSSProperties | AccordionRenderStyle | {} | An object or function that determines the style attribute. If you supply a function, it will receive state information (see AccordionRenderStyleData ) that will allow you to dynamically apply styles. | |
as | React.ElementType | 'section' | Determines the element that will be rendered. Note that the default element Avoid using the |
Data Attributes
Attribute | Values |
---|---|
[data-expanded] | true | false |
[data-disabled] | true | false |
<BaseAccordionHeader>
A stateless component that represents an accordion header. Exists mainly to provide guardrails to help ensure adherence to the APG, namely:
- The heading element contains only a button
- The content lives in the button
- Uses TypeScript and PropTypes to remind developers which HTML/ARIA attributes need to be set
Props
The type definition for this component's props are exported as BaseAccordionHeaderProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
children | React.ReactNode | null | A string, React component, etc., to be rendered. Note that because the content is placed inside of a | |
id | string | undefined | (See description) | The HTML ID for the button element. Note that if the HTML element representing the corresponding accordion panel has the Accordion panels in general are not required to have the |
headerLevel | 1 | 2 | 3 | 4 | 5 | 6 | Yes | Determines the HTML heading element (e.g. <h1> ). | |
onClick | React.MouseEventHandler<HTMLButtonElement> | Yes | A click event handler for the button. Should handle expanding/collapsing the panel. | |
onKeyDown | React.KeyboardEventHandler<HTMLButtonElement> | undefined | A keydown event handler for the button. Can be used to provide focus management. | |
aria-controls | string | Yes | A unique identifier that points to the accordion panel's HTML ID. | |
aria-expanded | boolean | Yes | Informs assistive technologies whether or not the panel is expanded. Note that this attribute does not affect the visibility of the panel. | |
aria-disabled | boolean | Yes | Informs assistive technologies if the button cannot be interacted with. A common use-case would be if the associated panel is currently expanded, and the accordion does not allow it to be collapsed (e.g. Note that unlike the | |
headerProps | BaseAccordionHeaderElementProps | {} | An object that is spread onto the underlying heading element, allowing you to pass props and attributes to it. | |
buttonProps | BaseAccordionButtonElementProps | {} | An object that is spread onto the underlying button element, allowing you to pass props and attributes to it. |
<BaseAccordionPanel>
A stateless component that represents an accordion panel. Exists mainly to provide guardrails to help ensure adherence to the APG, namely by using TypeScript and PropTypes to remind developers which HTML/ARIA attributes need to be set.
Props
Note that if you pass any props other than those listed below, they will be spread onto the underlying element (indicated by the as
prop).
The type definition for this component's props are exported as BaseAccordionPanelProps
.
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
children | React.ReactNode | null | The content to be rendered. | |
as | React.ElementType | 'section' | Determines the element that will be rendered. Note that the default element, First, elements with the Second, there are times where the Avoid using the | |
id | string | Yes | The HTML ID for the panel. Note that the corresponding accordion header button should also have an aria-controls attribute that points to this panel. | |
aria-labelledby | string | undefined | Yes by default, see description for details. | A string that points to the accordion header button's HTML ID. If the HTML element representing the accordion panel has the |
Hooks
useAccordion
useAccordion
is the hook that provides the state and functionality for the accordion. It accepts a number of arguments that help determine the behavior of the accordion, and returns fields and methods that get or set the state.
Arguments
This hook accepts an object of type UseAccordionOptions
that contains the following properties:
Name | Type | Default Value | Required | Description |
---|---|---|---|---|
allowMultiple | boolean | true | Controls whether or not multiple panels can be expanded at the same time. | |
allowCollapseLast | boolean | true | Controls whether or not the last expanded panel can be collapsed. | |
headerLevel | 1 | 2 | 3 | 4 | 5 | 6 | Yes | Determines the HTML heading element (e.g. <h1> ) of each accordion header. | |
initialExpanded | string[] | [] | Determines which accordion items (identified by their ID) should be expanded on the initial mount. If allowMultiple is off, the hook naively picks the first element in the array. | |
initialDisabled | string[] | [] | Determines which accordion items (identified by their ID) should be prevented from expanding or collapsing on the initial mount. | |
onToggleExpanded | (expandedItems: Set<string>) => void; | undefined | Callback to be fired after an item is expanded or collapsed. Receives the currently-expanded item IDs as an argument. | |
onToggleDisabled | (disabledItems: Set<string>) => void; | undefined | Callback to be fired after an item is enabled/disabled. Receives the currently-disabled item IDs as an argument. | |
onFocusItem | (args: {
elem: HTMLButtonElement | HTMLElement | null;
index: number;
id: string;
}) => void; | undefined | Callback to be fired after an item receives focus. Note that this only runs when using one of the focus methods provided by this hook. |
Return Value
The type of the returned object is exported as AccordionMembers
.
Name | Type | Description |
---|---|---|
allowMultiple | boolean | Informs downstream components whether or not multiple panels can be expanded at the same time. |
allowCollapselast | boolean | Informs downstream components whether or not the last expanded panel can be collapsed. |
headerLevel | 1 | 2 | 3 | 4 | 5 | 6 | Determines the HTML heading element (e.g. <h1> ) of each accordion header. |
getIsExpanded | (id: string) => boolean | Returns whether an accordion item is currently expanded. |
getIsDisabled | (id: string) => boolean | Returns whether an accordion item is currently prevented from being expanded/collapsed. |
toggleExpanded | (id: string) => void | Expands/collapses an accordion item. |
toggleDisabled | (id: string) => void | Prevents/allows an accordion item from being expanded/collapsed. |
pushItemRef | (elem: HTMLButtonElement | HTMLElement | null, id: string) => void; | Registers an accordion item to the hook. The hook must be aware of each header button in the accordion to manage focus. |
focusItemIndex | (index: number) => void | Focuses an accordion item based on its index. |
focusItemId | (id: string) => void | Focuses an accordion item based on its ID. |
focusPrevItem | (id: string) => void | Focuses the previous accordion item (relative to the supplied ID). |
focusNextItem | (id: string) => void | Focuses the next accordion item (relative to the supplied ID). |
focusFirstItem | () => void | Focuses the first accordion item. |
focusLastItem | () => void | Focuses the last accordion item. |
useAccordionContext
<Accordion>
and <ControlledAccordion>
pass down the fields and methods from useAccordion
via the context API. This hook can be used to read from that context provider.
Arguments
This hook doesn't accept any arguments.
Return Value
This hook has the same return value as useAccordion
's return value, an object of type AccordionMembers
.
useAccordionItemContext
<AccordionItem>
passes down the IDs for its header and panel via the context API. This hook can be used to read from that context provider.
Arguments
This hook doesn't accept any arguments.
Return Value
This hook returns an object of type AccordionItemContextType
that contains the following properties:
Name | Type | Description |
---|---|---|
id | string | The accordion item's identifier. Should be unique amongst its sibling items. |
headerHTMLId | string | A string ID used to identify the accordion header button via attributes like id and aria-labelledby . |
panelHTMLId | string | A string ID used to identify the accordion panel via attributes like id and aria-controls . |
Contexts
AccordionContext
This context provides the fields and methods from useAccordion
to consumers like useAccordionContext
. Chances are, you'll be using <Accordion>
or <ControlledAccordion>
instead of importing this directly, but React ARIA Widgets exports it for those who wish to use it.
AccordionContext.Provder
is also exported as AccordionProvider
for those who prefer it for aesthetic or other reasons.
AccordionItemContext
This context provides the header and panel IDs from <AccordionItem>
to consumers like useAccordionItemContext
. Chances are, you'll be using <AccordionItem>
instead, but React ARIA Widgets exports it for those who wish to use it.
AccordionItemContext.Provider
is also exported as AccordionItemProvider
for those who prefer it for aesthetic or other reasons.
Types
Name | Definition |
---|---|
AccordionProps |
|
AccordionItemProps |
|
ControlledAccordionProps |
|
AccordionHeaderProps |
|
AccordionHeaderElementProps |
|
AccordionButtonElementProps |
|
AccordionPanelProps |
|
BaseAccordionHeaderProps |
|
BaseAccordionHeaderElementProps |
|
BaseAccordionButtonElementProps |
|
BaseAccordionPanelProps |
|
UseAccordionOptions |
|
AccordionMembers |
|
AccordionItemContextType |
|
AccordionRenderFunction |
|
AccordionRenderClass |
|
AccordionRenderStyle |
|
AccordionRenderStyleData |
|