HextaUI

Accordion

Des titres empilés qui révèlent chacun un panneau, avec un mouvement de hauteur réversible en cours de route et des panneaux qui restent consultables par la recherche une fois fermés.

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

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

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

Les éléments partagent une même surface bordée, avec les coins extérieurs arrondis.

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

Séparé

Chaque élément est sa propre carte.

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

Sans bordures. Les déclencheurs reçoivent un léger fond au survol et à l’ouverture.

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

Définissez multiple pour laisser plusieurs éléments ouverts.

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

Désactivé

Désactivez un seul élément avec disabled sur <AccordionItem />, ou tout l’accordéon sur la racine.

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

Contrôlé

Passez value et onValueChange pour conserver les éléments ouverts dans votre propre état.

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

Icône personnalisée

Passez n’importe quel élément à icon. Stylez son état ouvert avec 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>
  )
}

Icônes en début de ligne

Les icônes placées avant le libellé sont dimensionnées et alignées pour vous. icon={null} supprime le 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>
  )
}

Contenu long

Les titres longs passent à la ligne tandis que l’icône reste sur la première ligne. Les panneaux fermés restent recherchables : Cmd/Ctrl+F sur « retention » ouvre donc celui-ci.

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

Imbriqué

Un accordéon imbriqué conserve sa propre variante, ses styles de focus et sa navigation aux flèches.

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

De droite à gauche

Le contenu, les icônes et l’animation d’ouverture suivent le sens de lecture.

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>
  )
}
ToucheAction
EnterSpaceOuvre ou ferme l’élément ayant le focus.
↓Déplace le focus vers le déclencheur suivant.
↑Déplace le focus vers le déclencheur précédent.
HomeDéplace le focus vers le premier déclencheur.
EndDéplace le focus vers le dernier déclencheur.
TabDéplace le focus dans le panneau ouvert, puis vers le déclencheur suivant.

Construit sur l’accordéon de Base UI. Chaque partie accepte les props de la primitive qu’elle enveloppe.

PropTypePar défaut
variant
"default" | "outline" | "separated" | "ghost""default"
multiplePermet d’ouvrir plusieurs éléments à la fois.
booleanfalse
value
Value[]–
defaultValue
Value[]–
onValueChange
(value: Value[], details) => void–
disabled
booleanfalse
hiddenUntilFoundGarde les panneaux fermés détectables par la recherche de page du navigateur.
booleantrue
keepMountedIgnoré lorsque hiddenUntilFound est activé.
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="accordion"Ciblez la racine en CSS.
data-variantLa variante actuelle.
data-disabledPrésent lorsque l’accordéon est désactivé.
PropTypePar défaut
valueGénéré automatiquement si omis.
Value–
disabled
booleanfalse
onOpenChange
(open: boolean, details) => void–
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="accordion-item"Ciblez les items en CSS.
data-openPrésent lorsque l’élément est ouvert.
data-disabledPrésent lorsque l’item est désactivé.
data-indexLa position de l’élément, à partir de 0.
PropTypePar défaut
iconnull supprime l’icône.
ReactNode | null<IconChevronDown />
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="accordion-trigger"Ciblez les déclencheurs en CSS.
data-panel-openPrésent lorsque son panneau est ouvert. Stylez l’icône avec group-data-panel-open/accordion-trigger.
data-disabledPrésent lorsque l’item est désactivé.
PropTypePar défaut
classNameAppliqué au conteneur interne, afin que le padding n’entre jamais en conflit avec l’animation de hauteur.
string–
keepMounted
booleanfalse
hiddenUntilFound
boolean–
AttributDescription
data-slot="accordion-content"Ciblez les panneaux en CSS.
data-openPrésent lorsque le panneau est ouvert.
data-starting-stylePrésent pendant l’animation d’ouverture du panneau.
data-ending-stylePrésent pendant l’animation de fermeture du panneau.
data-settledPrésent une fois le panneau entièrement ouvert. Il cesse alors de rogner son contenu, si bien que les anneaux de focus d’un accordéon imbriqué s’affichent en entier.
--accordion-panel-heightLa hauteur mesurée du panneau, utilisée pour l’animation de hauteur.