Accordion
Disclosure sections that open first and let their lines follow through.
Same-day delivery in Guatemala City for orders before noon.
Two to four business days anywhere else in the country.
Free over Q500.
Installation
$ pnpm dlx shadcn@latest add @ssntl/accordionUsage
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
<Accordion type="single" collapsible>
<AccordionItem value="shipping">
<AccordionTrigger>Shipping</AccordionTrigger>
<AccordionContent><p>Same-day delivery…</p></AccordionContent>
</AccordionItem>
</Accordion>Rules
What this component expects from you, as it ships in components/ui/accordion.md next to the code. The full file adds when to use it, keyboard, the UI foundations it applies and every shared rule.
## Rules for Accordion
- **Follow through & overlap.** The panel opens first (220ms), then each line lifts in 40ms after the last.
- Closing is faster and all at once, because leaving needs less attention than arriving.
- Headers are real buttons inside headings; arrow keys move between them (Radix).
- The chevron turns on `--ease-in-out`; with reduced motion nothing moves and the content just appears.Code
"use client"
import * as React from "react"
import { ChevronDown } from "lucide-react"
import { Accordion as AccordionPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
// Disclosure sections (Radix Accordion): arrow keys move between headers,
// Enter and Space open them, and each header is a real button in a heading.
// Follow through & overlap: the panel opens first (collapsible), then its rows
// lift in 40ms apart (follow-through), so the motion settles; closing is
// faster and all at once. Both turn off under reduced motion.
const Accordion = AccordionPrimitive.Root
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("not-first:border-t not-first:border-border", className)}
{...props}
/>
)
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"group flex min-h-14 flex-1 cursor-pointer items-center justify-between gap-4 rounded-xs py-3 text-start text-label",
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
className
)}
{...props}
>
{children}
<ChevronDown
aria-hidden
strokeWidth={1.6}
className="size-5 shrink-0 opacity-80 transition-[rotate] duration-(--duration-menu) ease-in-out group-data-[state=open]:rotate-180 motion-reduce:transition-none"
/>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="collapsible"
{...props}
>
<div
className={cn(
"grid follow-through gap-2 pb-4 text-body text-muted-foreground",
className
)}
>
{children}
</div>
</AccordionPrimitive.Content>
)
}
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }