- Docs
- Components
- Accordion
Accordion
A vertically stacked set of interactive headings that each reveal a section of content.
Preview
Code
Yes. It adheres to the WAI-ARIA design pattern.
Yes. It comes with default styles that matches the other components' aesthetic.
Yes. It's animated by default, but you can disable it if you prefer.
Installation
CLI
Manual
Copy and paste the following code into your project.
"use client";
import {
Button,
type ButtonProps,
Disclosure,
DisclosureGroup,
DisclosurePanel,
} from "react-aria-components";
import { AccordionStyles } from "./styles";
const Icons = {
ChevronDown: (props) => (
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width={32}
height={32}
viewBox="0 0 24 24"
{...props}
>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 9l6 6 6-6"
/>
</svg>
),
} as const satisfies Record<
string,
(props: React.ComponentProps<"svg">) => React.JSX.Element
>;
export interface AccordionRootProps
extends React.ComponentProps<typeof DisclosureGroup> {}
export const AccordionRoot = DisclosureGroup;
export interface AccordionItemProps
extends React.ComponentProps<typeof Disclosure> {}
export function AccordionItem({ className, ...props }: AccordionItemProps) {
return (
<Disclosure
{...props}
className={(values) =>
AccordionStyles.Item({
className:
typeof className === "function" ? className(values) : className,
})
}
/>
);
}
export interface AccordionTriggerProps extends Omit<ButtonProps, "slot"> {}
export function AccordionTrigger({
className,
children,
...props
}: AccordionTriggerProps) {
return (
<Button
{...props}
className={(values) =>
AccordionStyles.Trigger({
className:
typeof className === "function" ? className(values) : className,
})
}
slot="trigger"
>
{(values) => (
<>
{typeof children === "function" ? children(values) : children}
<Icons.ChevronDown className={AccordionStyles.TriggerIcon()} />
</>
)}
</Button>
);
}
export interface AccordionContentProps
extends React.ComponentProps<typeof DisclosurePanel> {}
export function AccordionContent({
className,
children,
...props
}: AccordionContentProps) {
return (
<DisclosurePanel
{...props}
className={(values) =>
AccordionStyles.Content({
className:
typeof className === "function" ? className(values) : className,
})
}
>
<div className="pt-0 pb-4">{children}</div>
</DisclosurePanel>
);
}
export * from "./accordion";
export * as Accordion from "./namespace";
export * from "./styles";
export {
AccordionRoot as Root,
AccordionItem as Item,
AccordionTrigger as Trigger,
AccordionContent as Content,
} from "./accordion";
import { cva } from "~/lib/cva";
export const AccordionStyles = {
Item: cva({
base: ["group border-b"],
}),
Trigger: cva({
base: [
"flex w-full flex-1 items-center justify-between py-4 text-left font-medium text-sm outline-hidden transition-all hover:underline",
],
}),
TriggerIcon: cva({
base: [
"size-4 shrink-0 text-muted-fg transition-transform duration-200 group-expanded:rotate-180",
],
}),
Content: cva({
base: [
"overflow-hidden text-sm transition-all transition-discrete [interpolate-size:allow-keywords] group-expanded:h-auto aria-hidden:h-0",
],
}),
};
Update the import paths to match your project setup.
Usage
Single import
import { Accordion } from "~/components/ui/accordion";
<Accordion.Root defaultExpandedKeys={["item-1"]}>
<Accordion.Item value="item-1">
<Accordion.Trigger>Is it accessible?</Accordion.Trigger>
<Accordion.Content>
Yes. It adheres to the WAI-ARIA design pattern.
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
Multiple imports
import {
AccordionContent,
AccordionItem,
AccordionRoot,
AccordionTrigger,
} from "~/components/ui/accordion";
<AccordionRoot defaultExpandedKeys={["item-1"]}>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
</AccordionRoot>
Animation
The Accordion component uses the interpolate-size: allow-keywords
CSS
property to animate the height of the content. This property is not
supported in all browsers. See the browser
compatibility
table for more information.