HextaUI

Accordion

Gestapelte Überschriften, die jeweils ein Panel aufklappen, mit einer Höhenbewegung, die sich mittendrin umkehren lässt, und Panels, die auch geschlossen durchsuchbar bleiben.

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

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

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

Die Einträge teilen sich eine umrandete Fläche mit abgerundeten äußeren Ecken.

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>
  )
}

Getrennt

Jeder Eintrag ist eine eigene Karte.

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

Keine Rahmen. Trigger erhalten beim Hovern und im geöffneten Zustand eine weiche Füllung.

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>
  )
}

Mehrfach

Setze multiple, damit mehr als ein Eintrag geöffnet bleiben kann.

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>
  )
}

Deaktiviert

Deaktiviere einen einzelnen Eintrag mit disabled auf <AccordionItem /> oder das ganze Accordion an der 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>
  )
}

Kontrolliert

Übergib value und onValueChange, um die geöffneten Einträge in deinem eigenen State zu halten.

"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>
  )
}

Eigenes Icon

Übergib ein beliebiges Element an icon. Style den geöffneten Zustand mit 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>
  )
}

Vorangestellte Icons

Icons vor dem Label werden automatisch in Größe und Ausrichtung angepasst. icon={null} entfernt den 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>
  )
}

Langer Inhalt

Lange Titel umbrechen, während das Icon in der ersten Zeile bleibt. Geschlossene Panels bleiben durchsuchbar, daher öffnet Cmd/Strg+F nach „retention“ dieses hier.

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>
  )
}

Verschachtelt

Ein verschachteltes Accordion behält seine eigene Variante, Fokus-Stile und Pfeiltasten-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>
  )
}

Rechts nach links

Inhalt, Icons und die Öffnen-Bewegung folgen der Leserichtung.

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>
  )
}
TasteAktion
EnterSpaceÖffnet oder schließt den fokussierten Eintrag.
↓Setzt den Fokus auf den nächsten Trigger.
↑Setzt den Fokus auf den vorherigen Trigger.
HomeSetzt den Fokus auf den ersten Trigger.
EndSetzt den Fokus auf den letzten Trigger.
TabSetzt den Fokus in das geöffnete Panel und danach weiter zum nächsten Trigger.

Basiert auf dem Base UI Accordion. Jeder Teil akzeptiert die Props der Primitive, die er umschließt.

PropTypStandard
variant
"default" | "outline" | "separated" | "ghost""default"
multipleErlaubt, dass mehr als ein Eintrag geöffnet ist.
booleanfalse
value
Value[]–
defaultValue
Value[]–
onValueChange
(value: Value[], details) => void–
disabled
booleanfalse
hiddenUntilFoundGeschlossene Panels bleiben über die Seitensuche des Browsers auffindbar.
booleantrue
keepMountedWird ignoriert, solange hiddenUntilFound aktiv ist.
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="accordion"Das Root in CSS ansprechen.
data-variantDie aktuelle Variante.
data-disabledVorhanden, wenn das Accordion deaktiviert ist.
PropTypStandard
valueWird automatisch erzeugt, wenn nicht angegeben.
Value–
disabled
booleanfalse
onOpenChange
(open: boolean, details) => void–
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="accordion-item"Items in CSS ansprechen.
data-openVorhanden, wenn der Eintrag geöffnet ist.
data-disabledVorhanden, wenn das Item deaktiviert ist.
data-indexDie Position des Eintrags, beginnend bei 0.
PropTypStandard
iconnull entfernt das Icon.
ReactNode | null<IconChevronDown />
render
ReactElement | (props, state) => ReactElement<button>
AttributBeschreibung
data-slot="accordion-trigger"Trigger in CSS ansprechen.
data-panel-openVorhanden, wenn das Panel geöffnet ist. Style das Icon mit group-data-panel-open/accordion-trigger.
data-disabledVorhanden, wenn das Item deaktiviert ist.
PropTypStandard
classNameWird auf den inneren Wrapper angewendet, damit Padding nie mit der Höhenanimation kollidiert.
string–
keepMounted
booleanfalse
hiddenUntilFound
boolean–
AttributBeschreibung
data-slot="accordion-content"Panels in CSS ansprechen.
data-openVorhanden, wenn das Panel geöffnet ist.
data-starting-styleVorhanden, während das Panel eingeblendet wird.
data-ending-styleVorhanden, während das Panel ausgeblendet wird.
data-settledVorhanden, sobald das Panel vollständig geöffnet ist. Dann wird nicht mehr beschnitten, sodass Fokusringe in einem verschachtelten Accordion vollständig sichtbar sind.
--accordion-panel-heightDie gemessene Höhe des Panels, die für die Höhenanimation verwendet wird.