Componentdata1.33 KB gzipZero dependencies
Accordion
Collapsible disclosure items with smooth height transitions.
Preview
Live Preview
An open-source React 19 component library.
MIT licensed — free for personal and commercial applications.
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add accordionOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
<Accordion defaultValue="a">
<AccordionItem value="a">
<AccordionTrigger>Question?</AccordionTrigger>
<AccordionContent>Answer.</AccordionContent>
</AccordionItem>
</Accordion>Usage guidance
Use Accordion when a ticket stub surface needs Collapsible disclosure items with smooth height transitions. Keep props minimal and prefer composition with sibling stubs.
Avoid Accordion for long form flows or dense data grids where a dedicated layout block fits better.
Source code
accordion.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
type AccordionContextValue = { open: string | null; toggle: (id: string) => void };
const AccordionCtx = React.createContext<AccordionContextValue | null>(null);
export type AccordionProps = React.HTMLAttributes<HTMLDivElement> & {
type?: "single";
collapsible?: boolean;
defaultValue?: string;
};
export function Accordion({ type: _type, collapsible: _collapsible, defaultValue, className, children, ...props }: AccordionProps) {
const [open, setOpen] = React.useState<string | null>(defaultValue ?? null);
const toggle = React.useCallback((id: string) => {
setOpen((current) => (current === id ? null : id));
}, []);
return (
<AccordionCtx.Provider value={{ open, toggle }}>
<div className={cn("w-full", className)} {...props}>
{children}
</div>
</AccordionCtx.Provider>
);
}
export type AccordionItemProps = React.HTMLAttributes<HTMLDivElement> & { value: string };
export function AccordionItem({ value, className, children, ...props }: AccordionItemProps) {
const ctx = React.useContext(AccordionCtx);
const isOpen = ctx?.open === value;
const contentId = React.useId();
return (
<div
className={cn("border-b border-dashed border-border", className)}
data-state={isOpen ? "open" : "closed"}
{...props}
>
{React.Children.map(children, (child) =>
React.isValidElement(child)
? React.cloneElement(child as React.ReactElement<{ value?: string; isOpen?: boolean; contentId?: string }>, {
value,
isOpen,
contentId,
})
: child
)}
</div>
);
}
type AccordionTriggerProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
value?: string;
isOpen?: boolean;
contentId?: string;
/** Dual-mode: false renders instantly with no motion. Default true. */
animated?: boolean;
};
export function AccordionTrigger({ value, isOpen, contentId, animated = true, className, children, onClick, ...props }: AccordionTriggerProps) {
const ctx = React.useContext(AccordionCtx);
return (
<h3 className="flex">
<button
type="button"
aria-expanded={isOpen ?? false}
aria-controls={contentId}
onClick={(event) => {
if (value) ctx?.toggle(value);
onClick?.(event);
}}
className={cn(
"flex flex-1 cursor-pointer items-center justify-between gap-4 py-4 text-start text-sm font-medium transition-colors hover:text-muted-foreground",
className
)}
{...props}
>
{children}
<svg
width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden
className={cn("shrink-0 text-muted-foreground", animated ? "transition-transform duration-200 motion-reduce:transition-none" : "transition-none", isOpen && "rotate-45")}
>
<path d="M12 5v14M5 12h14" />
</svg>
</button>
</h3>
);
}
type AccordionContentProps = React.HTMLAttributes<HTMLDivElement> & {
isOpen?: boolean;
contentId?: string;
/** Dual-mode: false renders instantly with no motion. Default true. */
animated?: boolean;
};
export function AccordionContent({ isOpen, contentId, animated = true, className, children, ...props }: AccordionContentProps) {
return (
<div
id={contentId}
role="region"
hidden={!isOpen}
className={cn(
"grid ease-out",
animated ? "transition-[grid-template-rows] duration-200 motion-reduce:transition-none" : "transition-none",
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
)}
{...props}
>
<div className="overflow-hidden">
<div className={cn("pb-4 pt-0 text-sm text-muted-foreground", className)}>{children}</div>
</div>
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| defaultValue | string | Initially open item value. |
| value | string (on AccordionItem) | Unique item identifier. |