This accordion component aims to follow the guidelines set out in the WAI-ARIA Authoring Practices 1.1 and uses the render props pattern to be as flexible as possible.
It is purposefully missing the ability for a section to be auto collapsed on expanding another. Read Design decisions for more information.
- Installation
- Example usage
- Using <Header /> correctly
- API
- Design decisions
- Technical decisions
- Contributors
npm install react-aria-accordion
Given the flexible nature of the component it would be best to wrap each component and style them as is appropriate for your use case.
// Accordion/Accordion
import Accordion from 'react-aria-accordion';
const MyAccordion = ({ children, ...rest }) => (
<Accordion>
{() => <div>{children}</div>}
</Accordion>
);
export default MyAccordion;
// Accordion/Section
import { Section } from 'react-aria-accordion';
const MySection = ({ onToggle, defaultExpanded, children }) => (
<Section onToggle={onToggle} defaultExpanded={defaultExpanded}>
{({ getSectionProps }) => <div {...getSectionProps()}>{children}</div>}
</Section>
);
export default MySection;
// Accordion/Header
import { Header } from 'react-aria-accordion';
// Using native html elements
const MyHeader = ({ title }) => (
<Header>
{({ getButtonToggleProps }) => (
<h1>
<button {...getButtonToggleProps()}>{title}</button>
</h1>
)}
</Header>
);
// Using custom html elements
const MyHeader = ({ title }) => (
<Header headingLevel={1}>
{({ getHeadingProps, getElementToggleProps }) => (
<div {...getHeadingProps()}>
<div {...getElementToggleProps()}>{title}</div>
</div>
)}
</Header>
);
export default Header;
// Accordion/Panel
import { Panel } from 'react-aria-accordion';
const MyPanel = ({ children }) => (
<Panel>
{({ getPanelProps }) => (
<div {...getPanelProps()}>
{children}
</div>
)}
</Panel>
);
export default MyPanel;
// Accordion/index
export { default } from './Accordion';
export { default as Section } from './Section';
export { default as Header } from './Header';
export { default as Panel } from './Panel';
This would allow the following:
import Accordion, { Section, Header, Panel } from './Accordion';
<Accordion>
<Section
defaultExpanded
onToggle={
(nextExpanded) => console.log(`First section is ${nextExpanded ? 'expanded' : 'collapsed'}`)
}
>
<Header title="Names" />
<Panel>
{listOfNames}
</Panel>
</Section>
<Section>
<Header title="Contact details" />
<Panel>
{listOfContactDetails}
</Panel>
</Section>
<Section>
<Header title="Other" />
<Panel>
{other}
</Panel>
</Section>
</Accordion>
An additional initialiseForSsr
function is required when using SSR so that the DOM matches when using client hydration:
import { renderToString } from 'react-dom/server';
import { initialiseForSsr } from 'react-aria-accordion';
initialiseForSsr();
renderToString(<App />);
The title for the accordion section must be wrapped in exactly one button and one heading. The following 2 examples offer the same semantics. Where possible use native html elements.
const header = (
<Header>
{({ getButtonToggleProps }) => (
<h2>
<button {...getButtonToggleProps()}>Title</button>
</h2>
)}
</Header>
);
const header = (
<Header headingLevel={2}>
{({ getHeadingProps, getElementToggleProps }) => (
<div {...getHeadingProps()}>
<div {...getElementToggleProps()}>Title</div>
</div>
)}
</Header>
);
If you want to include a menu next to the title, then make sure the menu is not inside the heading element.
const header = (
<Header>
{({ getButtonToggleProps }) => (
<Fragment>
<h2>
<button {...getButtonToggleProps()}>Title</button>
</h2>
<Menu />
</Fragment>
)}
</Header>
);
function({})
| required
This is called with an object.
Property | Type | Description |
---|---|---|
expandAllSections |
function() |
Expands all sections. |
collapseAllSections |
function() |
Collapses all sections. |
boolean
| defaults tofalse
This is the initial expanded
value.
boolean
| control prop
function(nextExpanded: boolean)
| defaults toundefined
function({})
| required
This is called with an object.
Property | Type | Description |
---|---|---|
getSectionProps |
function(props: object) |
Returns the props you should apply to the element you render. |
expanded |
boolean |
The expanded state of the section. |
boolean
| defaults tofalse
number
| defaults to1
function({})
| required
This is called with an object.
Property | Type | Description |
---|---|---|
getHeadingProps |
function(props: object) |
Returns the props you should apply to the heading element you render when not using <h# /> . |
getButtonToggleProps |
function(props: object) |
Returns the props you should apply to the toggle element you render when using <button /> . |
getElementToggleProps |
function(props: object) |
Returns the props you should apply to the toggle element you render when not using <button /> . |
expanded |
boolean |
The expanded state of the section. |
function({})
| required
This is called with an object.
Property | Type | Description |
---|---|---|
getPanelProps |
function(props: object) |
Returns the props you should apply to the element you render. |
expanded |
boolean |
The expanded state of the section. |
function()
See Server Side Rendering (SSR) for more details.
The aim of this component is to be used in projects to help developers feel confident they are making their websites as accessible as possible using relevant aria-* properties.
Automatic collapsing of sections can disorientate some users, particularly if scroll behaviour is also associated. For example, if a user is using magnification software they may not realise that expanding one section has collapsed another.
Data presented within a section may be comparable with another, therefore having more than one section expanded at a time may be a useful feature for your users.
Collapsing and expanding multiple sections manually may make your website difficult to use, so this component provides the functionality to collapse and expand all sections at once. Given this behaviour will need to be triggered by a user there are less usability concerns.
The reason the same render prop is available is so that it's possible to wrap each component as
shown in Example usage without having to pass your own expanded
prop to Header
and Panel
.
You may think that using a lifecycle method like componentWillMount
in <Section />
would
suffice, however it wouldn't cope with the following scenario:
<Accordion>
<Section>A</Section>
{showSection ? <Section>B</Section> : null}
<Section>C</Section>
</Accordion>
If showSection
starts as false
and switches to true
later, then componenetWillMount
will
fire for Section B, leaving the accordion no ability to know the ordering of the sections. Having
to manually specify the order of a <Section />
would be a burden on the developer.
Thanks goes to these wonderful people (emoji key):
Kenneth Gray π» π |
Koji Wakayama π» |
---|
This project follows the all-contributors specification. Contributions of any kind are welcome!