Radix homepageRadix Homepage
PrimitivesBeta

Accordion

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)`,
},
'&[data-state="closed"]': {
animation: `${slideUp} 300ms cubic-bezier(0.87, 0, 0.13, 1)`,
},
});
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-ARAI 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;

Features

  • Full keyboard navigation.
  • Can expand one or multiple items.
  • Can be controlled or uncontrolled.

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.

PropTypeDefault
asChildbooleanfalse
type*enumNo default value
valuestringNo default value
defaultValuestringNo default value
onValueChangefunctionNo default value
valuestring[][]
defaultValuestring[][]
onValueChangefunctionNo default value
disabledbooleanfalse

Contains all the parts of a collapsible section.

PropTypeDefault
asChildbooleanfalse
disabledbooleanfalse
value*stringNo default value

Wraps an Accordion.Trigger. Use the asChild prop to update it to the appropriate heading level for your page.

PropTypeDefault
asChildbooleanfalse

Toggles the collapsed state of its associated item. It should be nested inside of an Accordion.Header.

PropTypeDefault
asChildbooleanfalse

Contains the collapsible content for an item.

PropTypeDefault
asChildbooleanfalse
forceMountbooleanNo default value

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 AccordionContent = styled(Accordion.Content, {
'&[data-state="open"]': { animation: `${open} 300ms ease-out` },
'&[data-state="closed"]': { animation: `${close} 300ms ease-out` },
});
export default () => (
<Accordion.Root type="single">
<Accordion.Item value="item-1">
<Accordion.Header></Accordion.Header>
<AccordionContent></AccordionContent>
</Accordion.Item>
</Accordion.Root>
);

Adheres to the Accordion WAI-ARIA design pattern.

KeyDescription
SpaceWhen focus is on an Accordion.Trigger of a collapsed section, expands the section.
EnterWhen focus is on an Accordion.Trigger of a collapsed section, expands the section.
TabMoves focus to the next focusable element.
Shift + TabMoves focus to the previous focusable element.
ArrowDownMoves focus to the next Accordion.Trigger.
ArrowUpMoves focus to the previous Accordion.Trigger.
HomeWhen focus is on an Accordion.Trigger, moves focus to the first Accordion.Trigger.
EndWhen focus is on an Accordion.Trigger, moves focus to the last Accordion.Trigger.