Accordion
zyeon/accordionFreeComponent
A zero-dependency accessible disclosure list — single or multiple expansion, grid-rows animation, and full APG keyboard support.
Install it
npx shadcn@latest add https://ui.zyeon.ai/r/accordion.jsonSource
1"use client"23import * as React from "react"4import { ChevronDown } from "lucide-react"5import { cn } from "@/lib/utils"67export interface AccordionItem {8 id: string9 title: React.ReactNode10 content: React.ReactNode11}1213export interface AccordionProps {14 items: AccordionItem[]15 /** "single" (default) closes the previously open item when a new one opens; "multiple" toggles items independently */16 type?: "single" | "multiple"17 /** item ids expanded on mount */18 defaultOpen?: string[]19 className?: string20}2122export function Accordion({ items, type = "single", defaultOpen, className }: AccordionProps) {23 const uid = React.useId()24 const [open, setOpen] = React.useState<string[]>(defaultOpen ?? [])25 const triggerRefs = React.useRef<(HTMLButtonElement | null)[]>([])2627 const toggle = (id: string) => {28 setOpen(prev => {29 if (prev.includes(id)) return prev.filter(v => v !== id)30 return type === "single" ? [id] : [...prev, id]31 })32 }3334 // APG accordion pattern: headers stay in the normal Tab sequence; arrow keys35 // move focus between them and Home/End jump to the first/last header.36 const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>, index: number) => {37 const triggers = triggerRefs.current38 let next: number | null = null39 if (e.key === "ArrowDown") next = (index + 1) % triggers.length40 else if (e.key === "ArrowUp") next = (index - 1 + triggers.length) % triggers.length41 else if (e.key === "Home") next = 042 else if (e.key === "End") next = triggers.length - 143 if (next !== null) {44 e.preventDefault()45 triggers[next]?.focus()46 }47 }4849 return (50 <div className={cn("flex w-full flex-col", className)}>51 {items.map((item, index) => {52 const isOpen = open.includes(item.id)53 const triggerId = `${uid}-trigger-${item.id}`54 const panelId = `${uid}-panel-${item.id}`55 return (56 <div className="border-b" key={item.id}>57 <h3 className="flex">58 <button59 aria-controls={panelId}60 aria-expanded={isOpen}61 className={cn(62 "flex w-full flex-1 cursor-pointer items-center justify-between gap-4 rounded-md py-4 text-left text-sm font-medium",63 "hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",64 )}65 id={triggerId}66 onClick={() => toggle(item.id)}67// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from zyeon
All 893 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| A/B Test Resultab-test-result | zyeon | Components | Paid | 2 deps · 2 files | |
| Activity Feedactivity-feed | zyeon | Components | Free | 2 deps · 2 files | |
| Activity Heatmapactivity-heatmap | zyeon | Components | Paid | 1 dep · 2 files | |
| Address Formaddress-form | zyeon | Components | Paid | 1 dep | |
| Agent Graphagent-graph | zyeon | Components | Free | 2 deps · 2 files | |
| Agent Handoffagent-handoff | zyeon | Components | Free | 1 dep | |
| Agent Inboxagent-inbox | zyeon | Components | Free | 2 deps · 2 files | |
| Agent Permissionsagent-permissions | zyeon | Components | Free | 2 deps · 2 files |
