Accordion
A vertically stacked set of interactive headings that reveal or hide associated content.
Contenu de la section 1
Installation
Install the component Accordion in your project using the CLI.
Accordion.tsx
pnpm dlx behsseui@latest add AccordionInstall the component manually.
Create a ui folder at the root of the project, then a component folder inside it, and finally a Accordion.tsx file in that folder.
Copy and paste the following code into your project.
ui/components/Accordion.tsx
1"use client"23import type { ReactNode } from "react"4import { createContext, useContext, useState, useEffect } from "react"5import { cva, type VariantProps } from "class-variance-authority"6import { cn } from "@/lib/utils"78const accordionVariants = cva(9 "border rounded-lg overflow-hidden",10 {11 variants: {12 variant: {13 default: "border-border",14 ghost: "border-transparent",15 },16 },17 defaultVariants: {18 variant: "default",19 }20 }21)2223const accordionItemVariants = cva(24 "border-b last:border-b-0",25 {26 variants: {27 variant: {28 default: "border-border",29 ghost: "border-transparent",30 },31 },32 defaultVariants: {33 variant: "default",34 }35 }36)3738const accordionTriggerVariants = cva(39 "flex w-full items-center justify-between py-4 px-4 text-sm font-medium transition-all hover:underline cursor-pointer [&[data-state=open]>svg]:rotate-180",40 {41 variants: {42 variant: {43 default: "",44 ghost: "",45 },46 },47 defaultVariants: {48 variant: "default",49 }50 }51)5253const accordionContentVariants = cva(54 "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",55 {56 variants: {57 variant: {58 default: "",59 ghost: "",60 },61 },62 defaultVariants: {63 variant: "default",64 }65 }66)6768type AccordionContextType = {69 openItems: string[]70 toggleItem: (value: string) => void71 variant?: "default" | "ghost" | null72 type?: "single" | "multiple"73}7475const AccordionContext = createContext<AccordionContextType | undefined>(undefined)7677function useAccordion() {78 const context = useContext(AccordionContext)79 if (!context) {80 throw new Error("Accordion components must be used within an Accordion")81 }82 return context83}8485type AccordionItemContextType = {86 value: string87}8889const AccordionItemContext = createContext<AccordionItemContextType | undefined>(undefined)9091function useAccordionItem() {92 const context = useContext(AccordionItemContext)93 if (!context) {94 throw new Error("AccordionTrigger and AccordionContent must be used within an AccordionItem")95 }96 return context97}9899type AccordionProps = {100 children: ReactNode101 className?: string102 type?: "single" | "multiple"103 defaultValue?: string | string[]104 value?: string | string[]105 onValueChange?: (value: string | string[]) => void106} & VariantProps<typeof accordionVariants>107108export function Accordion({109 children,110 className,111 variant = "default",112 type = "single",113 defaultValue,114 value,115 onValueChange,116}: AccordionProps) {117 const [internalOpenItems, setInternalOpenItems] = useState<string[]>(() => {118 if (defaultValue) {119 return Array.isArray(defaultValue) ? defaultValue : [defaultValue]120 }121 return []122 })123124 const isControlled = value !== undefined125 const openItems = isControlled126 ? Array.isArray(value)127 ? value128 : [value]129 : internalOpenItems130131 const toggleItem = (itemValue: string) => {132 let newOpenItems: string[]133134 if (type === "single") {135 newOpenItems = openItems.includes(itemValue) ? [] : [itemValue]136 } else {137 newOpenItems = openItems.includes(itemValue)138 ? openItems.filter((v) => v !== itemValue)139 : [...openItems, itemValue]140 }141142 if (!isControlled) {143 setInternalOpenItems(newOpenItems)144 }145146 if (onValueChange) {147 onValueChange(type === "single" ? newOpenItems[0] || "" : newOpenItems)148 }149 }150151 return (152 <AccordionContext.Provider value={{ openItems, toggleItem, variant, type }}>153 <div className={cn(accordionVariants({ variant, className }))}>154 {children}155 </div>156 </AccordionContext.Provider>157 )158}159160type AccordionItemProps = {161 children: ReactNode162 value: string163 className?: string164 defaultOpen?: boolean165}166167export function AccordionItem({ children, value, className, defaultOpen }: AccordionItemProps) {168 const { variant, openItems, toggleItem } = useAccordion()169170 useEffect(() => {171 if (defaultOpen && !openItems.includes(value)) {172 toggleItem(value)173 }174 }, []) // eslint-disable-line react-hooks/exhaustive-deps175176 return (177 <AccordionItemContext.Provider value={{ value }}>178 <div className={cn(accordionItemVariants({ variant, className }))}>179 {children}180 </div>181 </AccordionItemContext.Provider>182 )183}184185type AccordionTriggerProps = {186 children: ReactNode187 className?: string188}189190export function AccordionTrigger({ children, className }: AccordionTriggerProps) {191 const { openItems, toggleItem, variant } = useAccordion()192 const { value } = useAccordionItem()193194 const isOpen = openItems.includes(value)195196 return (197 <button198 type="button"199 className={cn(accordionTriggerVariants({ variant, className }))}200 data-state={isOpen ? "open" : "closed"}201 onClick={() => toggleItem(value)}202 >203 {children}204 <svg205 xmlns="http://www.w3.org/2000/svg"206 width="16"207 height="16"208 viewBox="0 0 24 24"209 fill="none"210 stroke="currentColor"211 strokeWidth="2"212 strokeLinecap="round"213 strokeLinejoin="round"214 className="shrink-0 transition-transform duration-200"215 >216 <path d="m6 9 6 6 6-6" />217 </svg>218 </button>219 )220}221222type AccordionContentProps = {223 children: ReactNode224 className?: string225}226227export function AccordionContent({ children, className }: AccordionContentProps) {228 const { openItems, variant } = useAccordion()229 const { value } = useAccordionItem()230231 const isOpen = openItems.includes(value)232233 return (234 <div235 className={cn(accordionContentVariants({ variant, className }))}236 data-state={isOpen ? "open" : "closed"}237 >238 {isOpen && (239 <div className="px-4 pb-4 pt-0">240 {children}241 </div>242 )}243 </div>244 )245}246Usages
Different variants and use cases for the Accordion component.
Default
A single accordion that allows only one item open at a time.
Content for section 1
Default.tsx
<Accordion type="single" defaultValue="item-1">
<AccordionItem value="item-1">
<AccordionTrigger>Section 1</AccordionTrigger>
<AccordionContent>Content for section 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Section 2</AccordionTrigger>
<AccordionContent>Content for section 2</AccordionContent>
</AccordionItem>
</Accordion>Multiple
An accordion that allows multiple items to be open simultaneously.
Multiple.tsx
<Accordion type="multiple">
<AccordionItem value="item-1">
<AccordionTrigger>Section 1</AccordionTrigger>
<AccordionContent>Content for section 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Section 2</AccordionTrigger>
<AccordionContent>Content for section 2</AccordionContent>
</AccordionItem>
</Accordion>Ghost
An accordion without visible borders.
Ghost.tsx
<Accordion type="single" variant="ghost">
<AccordionItem value="item-1">
<AccordionTrigger>Section 1</AccordionTrigger>
<AccordionContent>Content for section 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Section 2</AccordionTrigger>
<AccordionContent>Content for section 2</AccordionContent>
</AccordionItem>
</Accordion>