Aesthe UI

Accordion

A vertically stacked list of interactive headings with smooth animations and customizable content

collapsibleanimatedinteractivefaqaccessible

Installation

Install dependencies

npm i @radix-ui/react-accordion clsx tailwind-merge lucide-react

Add util file

lib/utils.ts

import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Add animation to tailwind.config

tailwind.config.js

module.exports = {
  theme: {
    extend: {
      keyframes: {
        "accordion-down": {
          from: { height: "0" },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
}

Copy the source code

components/aesthe-ui/accordion/accordion.tsx

"use client"

import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}

function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", 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(
  "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
  className
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}

function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
)
}

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

Interactive Image Accordion

Aesthe-UI provides a set of pre-built, accessible, and responsive components.


Customization

Accordion Types

Control how many items can be open at once:

// Single - only one item open at a time
<Accordion type="single" collapsible>
  {/* items */}
</Accordion>

// Multiple - multiple items can be open
<Accordion type="multiple">
  {/* items */}
</Accordion>

Default Open State

Set which items are open by default:

// Single type
<Accordion type="single" defaultValue="item-1">
  {/* items */}
</Accordion>

// Multiple type
<Accordion type="multiple" defaultValue={["item-1", "item-2"]}>
  {/* items */}
</Accordion>

Custom Styling

Customize the appearance with className props:

<AccordionItem className="border-none bg-secondary/50 rounded-lg px-4">
  <AccordionTrigger className="hover:text-primary text-lg hover:no-underline">
    Custom styled trigger
  </AccordionTrigger>
  <AccordionContent className="text-muted-foreground">
    Custom styled content
  </AccordionContent>
</AccordionItem>

Controlled State

Manage the accordion state externally:

const [value, setValue] = useState("item-1");

<Accordion 
  type="single" 
  value={value} 
  onValueChange={setValue}
>
  {/* items */}
</Accordion>

Props

Accordion

Prop nameTypeDescription
type"single" | "multiple"Determines if one or multiple items can be open
collapsiblebooleanWhether items can be collapsed (single type only)
defaultValuestring | string[]Default open item(s)
valuestring | string[]Controlled open item(s)
onValueChange(value) => voidCallback when open items change
classNamestringAdditional CSS classes
disabledbooleanDisables all items

AccordionItem

Prop nameTypeDescription
valuestringUnique identifier for the item (required)
classNamestringAdditional CSS classes
disabledbooleanDisables this specific item

AccordionTrigger

Prop nameTypeDescription
classNamestringAdditional CSS classes for the trigger
childrenReact.ReactNodeContent of the trigger (usually the question/title)

AccordionContent

Prop nameTypeDescription
classNamestringAdditional CSS classes for the content wrapper
childrenReact.ReactNodeContent to show when expanded

Accessibility

The accordion component is built on top of Radix UI and includes:

  • Keyboard navigation: Use Tab to focus, Space or Enter to toggle, arrow keys to navigate
  • ARIA attributes: Proper aria-expanded, aria-controls, and aria-labelledby attributes
  • Focus management: Visible focus indicators and proper focus order
  • Screen reader support: Announces state changes and content

Use Cases

  • FAQs: Perfect for frequently asked questions sections
  • Navigation menus: Organize nested navigation items
  • Feature lists: Display product features with detailed descriptions
  • Settings panels: Group related settings in collapsible sections
  • Step-by-step guides: Progressive disclosure of instructions