Accordion
Collapsible content panels — supports single or multiple open items, with animated chevron indicators and smooth expand/collapse transitions.
Preview
Basic accordion
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/registry/ui-kit/tailwind-accordion/react'
function Example() {
return (
<Accordion defaultIndex={0}>
<AccordionItem>
<AccordionTrigger>What is Tailwind CSS?</AccordionTrigger>
<AccordionContent>A utility-first CSS framework for rapid UI development.</AccordionContent>
</AccordionItem>
<AccordionItem>
<AccordionTrigger>Is it free?</AccordionTrigger>
<AccordionContent>Yes, Tailwind CSS is open-source and MIT licensed.</AccordionContent>
</AccordionItem>
</Accordion>
)
}Multiple open
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/registry/ui-kit/tailwind-accordion/react'
function Example() {
return (
<Accordion multiple defaultIndex={[0, 1]}>
<AccordionItem>
<AccordionTrigger>First item</AccordionTrigger>
<AccordionContent>Content for the first item.</AccordionContent>
</AccordionItem>
<AccordionItem>
<AccordionTrigger>Second item</AccordionTrigger>
<AccordionContent>Content for the second item.</AccordionContent>
</AccordionItem>
</Accordion>
)
}Component API
| Prop | Default | Description |
|---|---|---|
multiple | false | Allow multiple items to be open at the same time. |
defaultIndex | — | Index or indices of items to open by default. |
className | "" | Additional CSS classes for the accordion wrapper. |
AccordionItem | — | Wrapper for each collapsible panel. |
AccordionTrigger | — | Clickable button that toggles the panel open/closed. |
AccordionContent | — | Collapsible content area revealed when the item is open. |
Source Code
"use client";
import { createContext, useContext, useState, useCallback } from "react";
/* ── Context ── */
interface AccordionContextValue {
openIndices: number[];
toggle: (index: number) => void;
}
const AccordionContext = createContext<AccordionContextValue>({
openIndices: [],
toggle: () => {},
});
interface AccordionItemContextValue {
index: number;
isOpen: boolean;
toggle: () => void;
}
const AccordionItemContext = createContext<AccordionItemContextValue>({
index: 0,
isOpen: false,
toggle: () => {},
});
/* ── Accordion ── */
interface AccordionProps {
multiple?: boolean;
defaultIndex?: number | number[];
className?: string;
children: React.ReactNode;
}
function Accordion({ multiple = false, defaultIndex, className = "", children }: AccordionProps) {
const [openIndices, setOpenIndices] = useState<number[]>(() => {
if (defaultIndex === undefined) return [];
return Array.isArray(defaultIndex) ? defaultIndex : [defaultIndex];
});
const toggle = useCallback(
(index: number) => {
setOpenIndices((prev) => {
if (prev.includes(index)) {
return prev.filter((i) => i !== index);
}
return multiple ? [...prev, index] : [index];
});
},
[multiple]
);
return (
<AccordionContext.Provider value={{ openIndices, toggle }}>
<div
className={`divide-y divide-zinc-200 rounded-lg border border-zinc-200 dark:divide-zinc-800 dark:border-zinc-800 ${className}`}
>
{Array.isArray(children)
? children.map((child, i) => (
<AccordionItemContext.Provider
key={i}
value={{ index: i, isOpen: openIndices.includes(i), toggle: () => toggle(i) }}
>
{child}
</AccordionItemContext.Provider>
))
: children}
</div>
</AccordionContext.Provider>
);
}
/* ── AccordionItem ── */
interface AccordionItemProps {
className?: string;
children: React.ReactNode;
}
function AccordionItem({ className = "", children }: AccordionItemProps) {
return <div className={className}>{children}</div>;
}
/* ── AccordionTrigger ── */
interface AccordionTriggerProps {
className?: string;
children: React.ReactNode;
}
function AccordionTrigger({ className = "", children }: AccordionTriggerProps) {
const { isOpen, toggle } = useContext(AccordionItemContext);
return (
<button
type="button"
onClick={toggle}
className={`flex w-full items-center justify-between px-4 py-3.5 text-left text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-50 dark:text-zinc-100 dark:hover:bg-zinc-800/50 ${className}`}
aria-expanded={isOpen}
>
{children}
<svg
className={`h-4 w-4 shrink-0 text-zinc-500 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>
</button>
);
}
/* ── AccordionContent ── */
interface AccordionContentProps {
className?: string;
children: React.ReactNode;
}
function AccordionContent({ className = "", children }: AccordionContentProps) {
const { isOpen } = useContext(AccordionItemContext);
return (
<div
className={`overflow-hidden transition-all duration-200 ${
isOpen ? "max-h-96 opacity-100" : "max-h-0 opacity-0"
} ${className}`}
>
<div className="px-4 pb-3.5 text-sm text-zinc-600 dark:text-zinc-400">{children}</div>
</div>
);
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
export default Accordion;