A vertically stacked set of interactive headings that each reveal an associated section of content.
import React from 'react';
import { styled, keyframes } from '@stitches/react';
import { violet, blackA, mauve } from '@radix-ui/colors';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
const slideDown = keyframes({
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' },
});
const slideUp = keyframes({
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 },
});
const StyledAccordion = styled(AccordionPrimitive.Root, {
borderRadius: 6,
width: 300,
backgroundColor: mauve.mauve6,
boxShadow: `0 2px 10px ${blackA.blackA4}`,
});
const StyledItem = styled(AccordionPrimitive.Item, {
overflow: 'hidden',
marginTop: 1,
'&:first-child': {
marginTop: 0,
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
},
'&:last-child': {
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
},
'&:focus-within': {
position: 'relative',
zIndex: 1,
boxShadow: `0 0 0 2px ${mauve.mauve12}`,
},
});
const StyledHeader = styled(AccordionPrimitive.Header, {
all: 'unset',
display: 'flex',
});
const StyledTrigger = styled(AccordionPrimitive.Trigger, {
all: 'unset',
fontFamily: 'inherit',
backgroundColor: 'transparent',
padding: '0 20px',
height: 45,
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
fontSize: 15,
lineHeight: 1,
color: violet.violet11,
boxShadow: `0 1px 0 ${mauve.mauve6}`,
'&[data-state="closed"]': { backgroundColor: 'white' },
'&[data-state="open"]': { backgroundColor: 'white' },
'&:hover': { backgroundColor: mauve.mauve2 },
});
const StyledContent = styled(AccordionPrimitive.Content, {
overflow: 'hidden',
fontSize: 15,
color: mauve.mauve11,
backgroundColor: mauve.mauve2,
'&[data-state="open"]': {
animation: `${slideDown} 300ms cubic-bezier(0.87, 0, 0.13, 1) forwards`,
},
'&[data-state="closed"]': {
animation: `${slideUp} 300ms cubic-bezier(0.87, 0, 0.13, 1) forwards`,
},
});
const StyledContentText = styled('div', {
padding: '15px 20px',
});
const StyledChevron = styled(ChevronDownIcon, {
color: violet.violet10,
transition: 'transform 300ms cubic-bezier(0.87, 0, 0.13, 1)',
'[data-state=open] &': { transform: 'rotate(180deg)' },
});
// Exports
export const Accordion = StyledAccordion;
export const AccordionItem = StyledItem;
export const AccordionTrigger = React.forwardRef(({ children, ...props }, forwardedRef) => (
<StyledHeader>
<StyledTrigger {...props} ref={forwardedRef}>
{children}
<StyledChevron aria-hidden />
</StyledTrigger>
</StyledHeader>
));
export const AccordionContent = React.forwardRef(({ children, ...props }, forwardedRef) => (
<StyledContent {...props} ref={forwardedRef}>
<StyledContentText>{children}</StyledContentText>
</StyledContent>
));
// Your app...
export const AccordionDemo = () => (
<Accordion type="single" defaultValue="item-1" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Is it unstyled?</AccordionTrigger>
<AccordionContent>
Yes. It's unstyled by default, giving you freedom over the look and feel.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Can it be animated?</AccordionTrigger>
<AccordionContent>
Yes! You can animate the Accordion with CSS or JavaScript.
</AccordionContent>
</AccordionItem>
</Accordion>
);
export default AccordionDemo;
Install the component from your command line.
npm install @radix-ui/react-accordion
Import all parts and piece them together.
import * as Accordion from '@radix-ui/react-accordion';
() => (
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger />
</Accordion.Header>
<Accordion.Content />
</Accordion.Item>
</Accordion.Root>
);
Contains all the parts of an accordion.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
type* | enum | |
value | string | |
defaultValue | string | |
onValueChange | function | |
value | string[] | [] |
defaultValue | string[] | [] |
onValueChange | function | |
collapsible | boolean | false |
disabled | boolean | false |
Contains all the parts of a collapsible section.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
disabled | boolean | false |
value* | string |
Wraps an Accordion.Trigger
. Use the asChild
prop to update it to the appropriate heading level for your page.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Toggles the collapsed state of its associated item. It should be nested inside of an Accordion.Header
.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Contains the collapsible content for an item.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
forceMount | boolean |
Use the defaultValue
prop to define the open item by default.
<Accordion.Root type="single" defaultValue="item-2">
<Accordion.Item value="item-1">…</Accordion.Item>
<Accordion.Item value="item-2">…</Accordion.Item>
</Accordion.Root>
Use the collapsible
prop to allow all items to close.
<Accordion.Root type="single" collapsible>
<Accordion.Item value="item-1">…</Accordion.Item>
<Accordion.Item value="item-2">…</Accordion.Item>
</Accordion.Root>
Set the type
prop to multiple
to enable opening multiple items at once.
<Accordion.Root type="multiple">
<Accordion.Item value="item-1">…</Accordion.Item>
<Accordion.Item value="item-2">…</Accordion.Item>
</Accordion.Root>
You can add extra decorative elements, such as chevrons, and rotate it when the item is open.
import { styled } from '@stitches/react';
import * as Accordion from '@radix-ui/react-accordion';
import { ChevronDownIcon } from '@radix-ui/react-icons';
const AccordionChevron = styled(ChevronDownIcon, {
transition: 'transform 300ms',
'[data-state=open] &': { transform: 'rotate(180deg)' },
});
export default () => (
<Accordion.Root type="single">
<Accordion.Item value="item-1">
<Accordion.Header>
<Accordion.Trigger>
<span>Trigger text</span>
<AccordionChevron aria-hidden />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>…</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
Use the --radix-accordion-content-width
and/or --radix-accordion-content-height
CSS variables to animate the size of the content when it opens/closes. Here's a demo using Stitches:
import { styled, keyframes } from '@stitches/react';
import * as Accordion from '@radix-ui/react-accordion';
const open = keyframes({
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' },
});
const close = keyframes({
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 },
});
const AccordionHeader = styled(Accordion.Header, {
margin: 0,
});
const AccordionContent = styled(Accordion.Content, {
overflow: 'hidden',
'&[data-state="open"]': { animation: `${open} 300ms ease-out forwards` },
'&[data-state="closed"]': { animation: `${close} 300ms ease-out forwards` },
});
export default () => (
<Accordion.Root type="single">
<Accordion.Item value="item-1">
<AccordionHeader>…</AccordionHeader>
<AccordionContent>…</AccordionContent>
</Accordion.Item>
</Accordion.Root>
);
Adheres to the Accordion WAI-ARIA design pattern.
Key | Description |
---|---|
Space | When focus is on an Accordion.Trigger of a collapsed section, expands the section. |
Enter | When focus is on an Accordion.Trigger of a collapsed section, expands the section. |
Tab | Moves focus to the next focusable element. |
Shift + Tab | Moves focus to the previous focusable element. |
ArrowDown | Moves focus to the next Accordion.Trigger . |
ArrowUp | Moves focus to the previous Accordion.Trigger . |
Home | When focus is on an Accordion.Trigger , moves focus to the first Accordion.Trigger . |
End | When focus is on an Accordion.Trigger , moves focus to the last Accordion.Trigger . |