Accordion
A vertically stacked set of interactive headings that each reveal an associated section of content.
Made by imskyleenPowered by
Installation
Base UI migration — collapsible is accepted but ignored. With type="single", Base UI always allows the open
item to close again; Radix's collapsible={false} (one item always open) cannot be faithfully reproduced and is
ignored at runtime for source compatibility. See MIGRATION.md.
Usage
Basic Example
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/redpanda-ui/accordion'
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>What is Redpanda?</AccordionTrigger>
<AccordionContent>
Redpanda is a streaming data platform that is Kafka-compatible but simpler to operate and 10x faster.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>How does it work?</AccordionTrigger>
<AccordionContent>
Redpanda is built from the ground up in C++ and designed to be simple, powerful, and cost-efficient.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Is it production ready?</AccordionTrigger>
<AccordionContent>
Yes, Redpanda is production-ready and used by hundreds of companies worldwide.
</AccordionContent>
</AccordionItem>
</Accordion>Multiple Items Open
<Accordion type="multiple">
<AccordionItem value="features">
<AccordionTrigger>Features</AccordionTrigger>
<AccordionContent>
<ul className="list-disc pl-6 space-y-1">
<li>Kafka API compatible</li>
<li>10x faster performance</li>
<li>Built-in schema registry</li>
<li>Tiered storage support</li>
</ul>
</AccordionContent>
</AccordionItem>
<AccordionItem value="pricing">
<AccordionTrigger>Pricing</AccordionTrigger>
<AccordionContent>
Redpanda offers both open source and cloud options with flexible pricing models.
</AccordionContent>
</AccordionItem>
</Accordion>Custom Animation
<Accordion type="single" collapsible>
<AccordionItem value="animated">
<AccordionTrigger
transition={{ type: 'spring', stiffness: 200, damping: 25 }}
>
Custom Animation
</AccordionTrigger>
<AccordionContent
transition={{ type: 'spring', stiffness: 180, damping: 20 }}
>
This accordion uses custom spring animation settings for smoother transitions.
</AccordionContent>
</AccordionItem>
</Accordion>Without Chevron
<Accordion type="single" collapsible>
<AccordionItem value="no-chevron">
<AccordionTrigger chevron={false}>
No Chevron Icon
</AccordionTrigger>
<AccordionContent>
This accordion item doesn't display the chevron indicator.
</AccordionContent>
</AccordionItem>
</Accordion>Contained Variant
The contained variant provides a more visually distinct card-like appearance with rounded borders and background colors:
<Accordion type="single" defaultValue="item-1" collapsible variant="contained" className="max-w-[500px] w-full">
<AccordionItem value="item-1">
<AccordionTrigger>What is Redpanda UI?</AccordionTrigger>
<AccordionContent>
Redpanda UI is an open-source distribution of React components built with TypeScript, Tailwind CSS, and Motion.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>How is it different from other libraries?</AccordionTrigger>
<AccordionContent>
Instead of installing via NPM, you copy and paste the components directly. This gives you full control to modify or customize them as needed.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Is Redpanda UI free to use?</AccordionTrigger>
<AccordionContent>
Absolutely! Redpanda UI is fully open-source. You can use, modify, and adapt it to fit your needs.
</AccordionContent>
</AccordionItem>
</Accordion>Controlled with Expand / Collapse All
Disabled Item
Contained with Custom Elements
The contained variant supports custom start and end elements for more complex layouts:
import { Circle, FileText, Settings } from 'lucide-react';
import { Badge } from '@/components/badge';
<Accordion type="single" defaultValue="item-1" collapsible variant="contained" className="max-w-[500px] w-full">
<AccordionItem value="item-1">
<AccordionTrigger
start={<Circle className="size-4 text-muted-foreground" />}
end={<Badge variant="success">Available</Badge>}
>
Accordion heading
</AccordionTrigger>
<AccordionContent>
This is the accordion content that provides additional details when the accordion is expanded.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger
start={<FileText className="size-4 text-muted-foreground" />}
end={<Badge variant="simple">In preview</Badge>}
>
Accordion heading
</AccordionTrigger>
<AccordionContent>
This is the accordion content that provides additional details when the accordion is expanded.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger
start={<Settings className="size-4 text-muted-foreground" />}
end={<Badge variant="success">Available</Badge>}
>
Accordion heading
</AccordionTrigger>
<AccordionContent>
This is the accordion content that provides additional details when the accordion is expanded.
</AccordionContent>
</AccordionItem>
</Accordion>When to use
Use an accordion when you need to display a large amount of content in a compact, organized way. This component is ideal for:
Use Accordion when:
- You have FAQ sections or help documentation
- Content can be grouped into clear, discrete sections
- Screen space is limited and you need to show/hide content
- Users typically need to focus on one section at a time
- You want to provide an overview with expandable details
Don't use Accordion when:
- Users need to compare content across sections simultaneously
- The content sections are very short (use a simple list instead)
- Navigation between sections is the primary goal (use Tabs instead)
- The content doesn't have a natural hierarchical structure
Anatomy
The Accordion component consists of several parts working together:
Accordion Container
┌─────────────────────────────────────────────────┐
│ AccordionItem (value="item-1") │
│ ┌─────────────────────────────────────────────┐ │
│ │ AccordionHeader │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ AccordionTrigger (clickable) │ │ │
│ │ │ [Text Content] [ChevronDown Icon] ────┐ │ │ │
│ │ └─────────────────────────────────────────┘ │ │ │
│ └─────────────────────────────────────────────┘ │ │
│ ┌─────────────────────────────────────────────┐ │ │
│ │ AccordionContent (animated) │ │ │
│ │ [Expandable content with mask animation] │ │ │
│ └─────────────────────────────────────────────┘ │ │
├─────────────────────────────────────────────────┤ │
│ AccordionItem (value="item-2") │ │
│ [Similar structure repeats...] │ │
└─────────────────────────────────────────────────┘ │
│
Context Flow: │
AccordionItemContext ──────────────────────────────┘
├── isOpen: boolean
└── setIsOpen: (open: boolean) => voidComponent Hierarchy:
- Accordion (Root): Manages overall state and behavior (single vs multiple)
- AccordionItem: Individual accordion section with unique value
- AccordionHeader: Contains the clickable trigger (automatically included)
- AccordionTrigger: Clickable element that toggles the content visibility
- AccordionContent: Expandable content area with smooth animations
Key Interactions:
- State Management: Each AccordionItem tracks its open/closed state via context
- Animation: Motion components provide smooth height and opacity transitions
- Accessibility: Built on Base UI primitives for keyboard navigation and screen readers
- Chevron Animation: Icon rotates 180° when content expands using Motion
Props
Keyboard interactions
| Key | Action |
|---|---|
Tab to trigger | Focus moves to the next accordion trigger. |
Enter / Space | Toggles the focused item. |
ArrowDown | Moves focus to the next trigger. Wraps at the end. |
ArrowUp | Moves focus to the previous trigger. Wraps at the start. |
Home / End | Jumps to first / last trigger. |
Accessibility
- Each
AccordionTriggerrenders inside anAccordionHeader(an<h3>by default). Override the tag viarenderonAccordionHeaderif the heading level doesn't match your document outline. - Triggers expose
aria-expanded,aria-controls, and the Radix-compatdata-state="open" | "closed"attribute — CSS selectors likedata-[state=open]:rotate-180keep working. - In
type="single"mode Base UI always allows re-closing the open item. Radix'scollapsible={false}behavior cannot be faithfully reproduced. See MIGRATION.md.
Animate UI Props
Accordion
Prop
Type
AccordionTrigger
Prop
Type
AccordionContent
Prop
Type
Don't delete from the DOM
The choice made (also Base UI's default) is to remove the element from the DOM for accessibility and performance reasons. However, this may pose a problem for SEO. If you want your Accordion content to be taken into account by Google, replace the AccordionContent component with:
function AccordionContent({
className,
children,
transition = { type: 'spring', stiffness: 150, damping: 22 },
...props
}: AccordionContentProps) {
const { isOpen } = useAccordionItem();
return (
<AccordionPrimitive.Content forceMount {...props}>
<motion.div
key="accordion-content"
initial={{ height: 0, opacity: 0, '--mask-stop': '0%' }}
animate={
isOpen
? { height: 'auto', opacity: 1, '--mask-stop': '100%' }
: { height: 0, opacity: 0, '--mask-stop': '0%' }
}
transition={transition}
style={{
maskImage:
'linear-gradient(black var(--mask-stop), transparent var(--mask-stop))',
WebkitMaskImage:
'linear-gradient(black var(--mask-stop), transparent var(--mask-stop))',
}}
className="overflow-hidden"
ref={ref}
>
<div className={cn('pb-4 pt-0 text-sm', className)}>{children}</div>
</motion.div>
</AccordionPrimitive.Content>
);
}Credits
- We use Base UI for the accordion component.
- We take our inspiration from Shadcn UI for the accordion style.
- We use Animate UI from imskyleen for all the animations.
Recent changes
- patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested b…#133
- minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
- minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
- majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
- patchv0.3.1**Accordion** - `AccordionTrigger` now shows `cursor-pointer` on hover.#112