HextaUI

Accordion

Stacked headings that each reveal a panel, with height motion you can reverse mid-way and panels that stay searchable while closed.

A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionDemo() {
  return (
    <Accordion defaultValue={["what"]} className="w-full max-w-md">
      {items.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/accordion.json

Adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"
<Accordion defaultValue={["item-1"]}>
  <AccordionItem value="item-1">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>
      Yes. It follows the WAI-ARIA accordion pattern.
    </AccordionContent>
  </AccordionItem>
</Accordion>
Accordion
└── AccordionItem
    ├── AccordionTrigger
    └── AccordionContent

Outline

Items share one bordered surface, with the outer corners rounded.

A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionOutline() {
  return (
    <Accordion
      variant="outline"
      defaultValue={["what"]}
      className="w-full max-w-md"
    >
      {items.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}

Separated

Each item is its own card.

A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionSeparated() {
  return (
    <Accordion
      variant="separated"
      defaultValue={["what"]}
      className="w-full max-w-md"
    >
      {items.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}

Ghost

No borders. Triggers get a soft fill on hover and when open.

A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionGhost() {
  return (
    <Accordion
      variant="ghost"
      defaultValue={["what"]}
      className="w-full max-w-md"
    >
      {items.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}

Multiple

Set multiple to let more than one item stay open.

A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.

Copy the source into your project, then edit it like any other file you own.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionMultiple() {
  return (
    <Accordion
      variant="outline"
      multiple
      defaultValue={["what", "install"]}
      className="w-full max-w-md"
    >
      {items.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}

Disabled

Disable a single item with disabled on <AccordionItem />, or the whole accordion on the root.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionDisabled() {
  return (
    <Accordion variant="outline" className="w-full max-w-md">
      {items.map((item) => (
        <AccordionItem
          key={item.value}
          value={item.value}
          disabled={item.value === "install"}
        >
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}

Controlled

Pass value and onValueChange to keep the open items in your own state.

A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.

"use client"

import * as React from "react"

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionControlled() {
  const [value, setValue] = React.useState<string[]>(["what"])

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <div className="flex gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => setValue(items.map((item) => item.value))}
        >
          Open all
        </Button>
        <Button variant="outline" size="sm" onClick={() => setValue([])}>
          Close all
        </Button>
      </div>
      <Accordion
        variant="outline"
        multiple
        value={value}
        onValueChange={setValue}
      >
        {items.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger>{item.question}</AccordionTrigger>
            <AccordionContent>{item.answer}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Custom icon

Pass any element to icon. Style its open state with group-data-panel-open/accordion-trigger.

import { IconPlus } from "@tabler/icons-react"

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionCustomIcon() {
  return (
    <Accordion variant="ghost" className="w-full max-w-md">
      {items.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionTrigger
            icon={
              <IconPlus className="transition-transform duration-200 ease-out-cubic group-data-panel-open/accordion-trigger:rotate-45 motion-reduce:transition-none" />
            }
          >
            {item.question}
          </AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  )
}

Leading icons

Icons before the label are sized and aligned for you. icon={null} removes the chevron.

import { IconLock, IconUser } from "@tabler/icons-react"

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

export function AccordionLeadingIcons() {
  return (
    <Accordion variant="outline" className="w-full max-w-md">
      <AccordionItem value="account">
        <AccordionTrigger icon={null}>
          <IconUser />
          Account
        </AccordionTrigger>
        <AccordionContent>Name, email and avatar.</AccordionContent>
      </AccordionItem>
      <AccordionItem value="security">
        <AccordionTrigger icon={null}>
          <IconLock />
          Security
        </AccordionTrigger>
        <AccordionContent>Password and two-factor settings.</AccordionContent>
      </AccordionItem>
    </Accordion>
  )
}

Long content

Long titles wrap while the icon stays on the first line. Closed panels stay searchable, so Cmd/Ctrl+F for “retention” opens this one.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

export function AccordionLongContent() {
  return (
    <Accordion className="w-full max-w-md">
      <AccordionItem value="long">
        <AccordionTrigger>
          What happens to my data if I cancel my subscription halfway through a
          billing period and later decide to come back?
        </AccordionTrigger>
        <AccordionContent>
          <p>
            Your workspace is kept for 30 days after cancellation. During that
            time you can reactivate and everything is restored exactly as you
            left it.
          </p>
          <p>
            After the retention window, data is permanently deleted. Read the{" "}
            <a href="#">data policy</a> for details.
          </p>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  )
}

Nested

An inner accordion keeps its own variant, focus styles and arrow-key navigation.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "What is HextaUI?",
    answer:
      "A collection of components built on top of shadcn/ui, with careful attention to structure and micro-interactions.",
  },
  {
    value: "install",
    question: "How do I install a component?",
    answer:
      "Copy the source into your project, then edit it like any other file you own.",
  },
  {
    value: "license",
    question: "Can I use it in commercial projects?",
    answer:
      "Yes. Every component is free and open source, for personal and commercial work.",
  },
]

export function AccordionNested() {
  return (
    <Accordion variant="separated" className="w-full max-w-md">
      <AccordionItem value="billing">
        <AccordionTrigger>Billing</AccordionTrigger>
        <AccordionContent>
          <Accordion variant="ghost">
            {items.map((item) => (
              <AccordionItem key={item.value} value={item.value}>
                <AccordionTrigger>{item.question}</AccordionTrigger>
                <AccordionContent>{item.answer}</AccordionContent>
              </AccordionItem>
            ))}
          </Accordion>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="shipping">
        <AccordionTrigger>Shipping</AccordionTrigger>
        <AccordionContent>Ships within 2 business days.</AccordionContent>
      </AccordionItem>
    </Accordion>
  )
}

Right to left

Content, icons and the open motion follow the reading direction.

مجموعة مكونات مبنية على shadcn/ui مع اهتمام بالتفاصيل والحركة.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"

const items = [
  {
    value: "what",
    question: "ما هي HextaUI؟",
    answer: "مجموعة مكونات مبنية على shadcn/ui مع اهتمام بالتفاصيل والحركة.",
  },
  {
    value: "install",
    question: "كيف أثبّت مكوّنًا؟",
    answer: "انسخ الشيفرة إلى مشروعك وعدّلها كأي ملف تملكه.",
  },
  {
    value: "license",
    question: "هل يمكنني استخدامها في مشاريع تجارية؟",
    answer: "نعم، جميع المكونات مجانية ومفتوحة المصدر.",
  },
]

export function AccordionRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md">
      <Accordion variant="outline" defaultValue={["what"]}>
        {items.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger>{item.question}</AccordionTrigger>
            <AccordionContent>{item.answer}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}
KeyAction
EnterSpaceOpens or closes the focused item.
↓Moves focus to the next trigger.
↑Moves focus to the previous trigger.
HomeMoves focus to the first trigger.
EndMoves focus to the last trigger.
TabMoves focus into the open panel, then on to the next trigger.

Built on the Base UI accordion. Every part accepts the props of the primitive it wraps.

PropTypeDefault
variant
"default" | "outline" | "separated" | "ghost""default"
multipleAllow more than one item to be open.
booleanfalse
value
Value[]–
defaultValue
Value[]–
onValueChange
(value: Value[], details) => void–
disabled
booleanfalse
hiddenUntilFoundKeep closed panels findable with the browser’s page search.
booleantrue
keepMountedIgnored while hiddenUntilFound is on.
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="accordion"Target the root in CSS.
data-variantThe current variant.
data-disabledPresent when the accordion is disabled.
PropTypeDefault
valueGenerated automatically when omitted.
Value–
disabled
booleanfalse
onOpenChange
(open: boolean, details) => void–
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="accordion-item"Target items in CSS.
data-openPresent when the item is open.
data-disabledPresent when the item is disabled.
data-indexThe item’s position, starting at 0.
PropTypeDefault
iconnull removes the icon.
ReactNode | null<IconChevronDown />
render
ReactElement | (props, state) => ReactElement<button>
AttributeDescription
data-slot="accordion-trigger"Target triggers in CSS.
data-panel-openPresent when its panel is open. Style the icon with group-data-panel-open/accordion-trigger.
data-disabledPresent when the item is disabled.
PropTypeDefault
classNameApplied to the inner wrapper, so padding never fights the height animation.
string–
keepMounted
booleanfalse
hiddenUntilFound
boolean–
AttributeDescription
data-slot="accordion-content"Target panels in CSS.
data-openPresent when the panel is open.
data-starting-stylePresent while the panel animates in.
data-ending-stylePresent while the panel animates out.
data-settledPresent once the panel is fully open. It stops clipping then, so focus rings inside a nested accordion show in full.
--accordion-panel-heightThe panel’s measured height, used for the height animation.