Skip to main content
PolyUI/docs

Layout

Accordion

Expandable content panels for FAQs, settings, and similar use cases. Built on Base UI Accordion with single and multiple open modes.

Installation

bash
npx polyui add accordion

Basic

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionBasic() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full">
        {faqItems.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger>{item.title}</AccordionTrigger>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Split

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionSplit() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full space-y-2">
        {faqItems.map((item) => (
          <AccordionItem
            key={item.value}
            value={item.value}
            className="bg-card rounded-md border-b-0 shadow-md data-open:shadow-lg"
          >
            <AccordionTrigger className="px-5">{item.title}</AccordionTrigger>
            <AccordionContent className="text-muted-foreground px-5">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

With Icon

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionWithIcon() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full">
        {iconItems.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger>
              <span className="flex items-center gap-4">
                <item.icon className="text-muted-foreground size-4 shrink-0" />
                <span>{item.title}</span>
              </span>
            </AccordionTrigger>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Plus Icon

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
import { PlusIcon } from "lucide-react"
export function AccordionPlusIcon() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {iconItems.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <div className="flex">
              <AccordionTrigger
                data-slot="accordion-trigger"
                className="focus-visible:border-ring focus-visible:ring-ring/50 group/accordion-trigger flex flex-1 items-start justify-between gap-4 rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-expanded:[&>svg.plus-icon]:rotate-45"
              >
                <span className="flex items-center gap-4">
                  <item.icon className="size-4 shrink-0" />
                  <span>{item.title}</span>
                </span>
                <PlusIcon className="plus-icon text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200" />
              </AccordionTrigger>
            </div>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Icon & Subtitle

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
import { PlusIcon } from "lucide-react"
export function AccordionIconSubtitle() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {iconItems.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <div className="flex">
              <AccordionTrigger
                data-slot="accordion-trigger"
                className="focus-visible:border-ring focus-visible:ring-ring/50 group/accordion-trigger flex flex-1 items-center justify-between gap-4 rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-expanded:[&>svg.plus-icon]:rotate-45"
              >
                <span className="flex items-center gap-4">
                  <span
                    className="flex size-10 shrink-0 items-center justify-center rounded-full border"
                    aria-hidden="true"
                  >
                    <item.icon className="size-4" />
                  </span>
                  <span className="flex flex-col space-y-0.5">
                    <span>{item.title}</span>
                    <span className="text-muted-foreground font-normal">{item.subtitle}</span>
                  </span>
                </span>
                <PlusIcon className="plus-icon text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200" />
              </AccordionTrigger>
            </div>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Active Highlight

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionActiveHighlight() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {faqItems.map((item) => (
          <AccordionItem
            key={item.value}
            value={item.value}
            className="data-open:border-amber-600 not-last:data-open:border-b-2 dark:data-open:border-amber-400"
          >
            <AccordionTrigger className="data-open:text-amber-600 dark:data-open:text-amber-400">
              {item.title}
            </AccordionTrigger>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

With Avatar

Richard Payne is a remarkable individual known for his exceptional skills and expertise in various fields.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
import { Avatar, AvatarImage, AvatarFallback } from "@polyui/react/avatar"
export function AccordionWithAvatar() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {avatarItems.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger className="items-center hover:no-underline">
              <span className="flex items-center gap-4">
                <Avatar className="size-10 rounded-sm">
                  <AvatarImage src={item.src} alt={item.name} className="rounded-sm" />
                  <AvatarFallback className="rounded-sm text-xs">
                    {item.name.split(/\s/).reduce((acc, word) => acc + word.slice(0, 1), "")}
                  </AvatarFallback>
                </Avatar>
                <span className="flex flex-col space-y-0.5">
                  <span>{item.name}</span>
                  <span className="text-muted-foreground font-normal">{item.email}</span>
                </span>
              </span>
            </AccordionTrigger>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Outline Separated

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionOutlineSeparated() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full space-y-2" defaultValue={["item-1"]}>
        {faqItemsFull.map((item) => (
          <AccordionItem key={item.value} value={item.value} className="rounded-md border!">
            <AccordionTrigger className="px-5">{item.title}</AccordionTrigger>
            <AccordionContent className="text-muted-foreground px-5">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Box

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionBox() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full rounded-md border" defaultValue={["item-1"]}>
        {faqItemsFull.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger className="px-5">{item.title}</AccordionTrigger>
            <AccordionContent className="text-muted-foreground px-5">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Tabs

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionTabs() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {faqItemsFull.map((item) => (
          <AccordionItem
            key={item.value}
            value={item.value}
            className="data-open:bg-accent rounded-md border-none px-5 transition-colors duration-200"
          >
            <AccordionTrigger>{item.title}</AccordionTrigger>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Tabs Outline

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
import { PlusIcon } from "lucide-react"
export function AccordionTabsOutline() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {faqItemsFull.map((item) => (
          <AccordionItem
            key={item.value}
            value={item.value}
            className="data-open:border-border rounded-md border border-transparent px-5 transition-colors duration-200 data-open:border data-open:shadow-md"
          >
            <div className="flex">
              <AccordionTrigger
                data-slot="accordion-trigger"
                className="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-center justify-between gap-4 rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-expanded:[&>svg.plus-icon]:rotate-45"
              >
                {item.title}
                <PlusIcon className="plus-icon text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200" />
              </AccordionTrigger>
            </div>
            <AccordionContent className="text-muted-foreground">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

With Media

You'll receive tracking information via email once your order ships.

How do I track my order?

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionWithMedia() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full" defaultValue={["item-1"]}>
        {mediaItems.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionTrigger>
              <span className="flex items-center gap-4">
                <item.icon className="size-4 shrink-0" />
                <span>{item.title}</span>
              </span>
            </AccordionTrigger>
            <AccordionContent className="space-y-4">
              <p className="text-muted-foreground">{item.content}</p>
              <img src={item.media} alt={item.title} className="w-full rounded-md" />
            </AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Filled Header

You can track your order by logging into your account and visiting the "Orders" section. You'll receive tracking information via email once your order ships. For real-time updates, you can also use the tracking number provided in your shipping confirmation email.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
export function AccordionFilledHeader() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full space-y-2" defaultValue={["item-1"]}>
        {faqItemsFull.map((item) => (
          <AccordionItem key={item.value} value={item.value} className="rounded-md border!">
            <AccordionTrigger className="bg-accent px-5 data-open:rounded-b-none">{item.title}</AccordionTrigger>
            <AccordionContent className="text-muted-foreground px-5 pt-4">{item.content}</AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Multilevel

You can track your order by logging into your account and visiting the "Orders" section.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@polyui/react/collapsible"
import { PlusIcon, ChevronDownIcon } from "lucide-react"
export function AccordionMultilevel() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full rounded-md border" defaultValue={["item-1"]}>
        {multilevelItems.map((item) => (
          <AccordionItem
            key={item.value}
            value={item.value}
            className="outline-none first:rounded-t-md last:rounded-b-md"
          >
            <div className="flex">
              <AccordionTrigger
                data-slot="accordion-trigger"
                className="focus-visible:border-ring focus-visible:ring-ring/50 flex w-full flex-1 items-start justify-between gap-4 rounded-lg px-5 py-4 text-left text-sm font-medium transition-all outline-none hover:underline disabled:pointer-events-none disabled:opacity-50 aria-expanded:[&>svg.plus-icon]:rotate-45"
              >
                <span className="flex items-center gap-4">
                  <item.icon className="size-4 shrink-0" />
                  <span>{item.category}</span>
                </span>
                <PlusIcon className="plus-icon text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200" />
              </AccordionTrigger>
            </div>
            <AccordionContent className="pb-0">
              {item.faqs.map((faq, i) => (
                <Collapsible key={i} className="bg-accent border-t px-5" defaultOpen={i === 0}>
                  <CollapsibleTrigger className="focus-visible:ring-ring/50 flex w-full items-center gap-4 rounded-sm py-4 font-medium outline-none focus-visible:z-10 focus-visible:ring-[3px]">
                    <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0" />
                    {faq.title}
                  </CollapsibleTrigger>
                  <CollapsibleContent className="text-muted-foreground overflow-hidden pb-4">
                    {faq.content}
                  </CollapsibleContent>
                </Collapsible>
              ))}
            </AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Multilevel Plus

You can track your order by logging into your account and visiting the "Orders" section.

tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@polyui/react/accordion"
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@polyui/react/collapsible"
import { PlusIcon } from "lucide-react"
export function AccordionMultilevelPlus() {
  return (
    <div className="flex flex-col gap-8 w-full max-w-lg">
      <Accordion className="w-full rounded-md border" defaultValue={["item-1"]}>
        {multilevelItems.map((item) => (
          <AccordionItem
            key={item.value}
            value={item.value}
            className="outline-none first:rounded-t-md last:rounded-b-md"
          >
            <AccordionTrigger className="px-5 outline-none focus-visible:ring-0">{item.category}</AccordionTrigger>
            <AccordionContent className="pb-0">
              {item.faqs.map((faq, i) => (
                <Collapsible key={i} className="bg-muted border-t px-8" defaultOpen={i === 0}>
                  <CollapsibleTrigger className="focus-visible:ring-ring/50 flex w-full items-center gap-4 rounded-sm py-4 font-medium outline-none focus-visible:z-10 focus-visible:ring-[3px] aria-expanded:[&>svg.plus-icon]:rotate-45">
                    <PlusIcon className="plus-icon text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200" />
                    {faq.title}
                  </CollapsibleTrigger>
                  <CollapsibleContent className="text-muted-foreground overflow-hidden pb-4">
                    {faq.content}
                  </CollapsibleContent>
                </Collapsible>
              ))}
            </AccordionContent>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  )
}

Props — Accordion

属性类型默认值说明
openMultiplebooleanfalseWhether multiple panels can be open simultaneously
defaultValuestring | string[]Default open panel value(s) in uncontrolled mode
valuestring | string[]Controlled open panel value(s)
onValueChange(value: string | string[]) => voidCallback when open state changes
classNamestringAdditional CSS class names

Props — AccordionItem

属性类型默认值说明
valuestringUnique identifier for this panel (required)
disabledbooleanfalseWhether this panel is disabled
classNamestringAdditional CSS class names