Skip to content
EMBOSS
Docs menu

Accordion

Disclosure rows machined into one housing — the open panel recesses into the chassis with a sprung height transition.

@emboss/accordion

Each channel delivers 48V phantom power, switchable per strip.

Installation

pnpm dlx shadcn@latest add @emboss/accordion

Usage

example.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";

export function Example() {
  return (
    <Accordion>
      <AccordionItem value="power">
        <AccordionTrigger>Power</AccordionTrigger>
        <AccordionContent>48V phantom power per channel.</AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

API reference

Accordion

The housing. Pass an array value since multiple panels may open.

PropTypeDefaultDescription
value / defaultValue / onValueChangestring[] / string[] / (value) => voidControlled and uncontrolled open panels.
multiplebooleanfalseAllow several panels open at once.

AccordionItem / AccordionTrigger / AccordionContent

One row: its value, the flush trigger, and the recessed panel.

PropTypeDefaultDescription
valuestringIdentifies the item (AccordionItem).

Keyboard

KeysAction
ArrowDownArrowUpMoves focus between triggers.
EnterToggles the focused panel.
SpaceToggles the focused panel.

Source

View source — accordion.tsx
accordion.tsx
"use client";

import { Accordion as BaseAccordion } from "@base-ui/react/accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";

/**
 * Disclosure rows machined into one housing. Closed items sit flush; the
 * open item's panel recesses into the chassis as if a service hatch opened.
 *
 * @example
 * <Accordion>
 *   <AccordionItem value="power">
 *     <AccordionTrigger>Power</AccordionTrigger>
 *     <AccordionContent>48V phantom power is delivered per channel.</AccordionContent>
 *   </AccordionItem>
 * </Accordion>
 */
function Accordion({
  className,
  ...props
}: React.ComponentProps<typeof BaseAccordion.Root>) {
  return (
    <BaseAccordion.Root
      data-slot="accordion"
      className={cn(
        "w-full divide-y divide-edge-line rounded-md bg-surface-1 shadow-flush",
        className,
      )}
      {...props}
    />
  );
}

function AccordionItem({
  className,
  ...props
}: React.ComponentProps<typeof BaseAccordion.Item>) {
  return (
    <BaseAccordion.Item
      data-slot="accordion-item"
      className={cn("group", className)}
      {...props}
    />
  );
}

function AccordionTrigger({
  className,
  children,
  ...props
}: React.ComponentProps<typeof BaseAccordion.Trigger>) {
  return (
    <BaseAccordion.Header className="flex">
      <BaseAccordion.Trigger
        data-slot="accordion-trigger"
        className={cn(
          "flex flex-1 cursor-pointer items-center justify-between gap-3 px-4 py-3 text-left text-sm font-medium text-ink focus-ring transition-[background-color] hover:bg-surface-2",
          className,
        )}
        {...props}
      >
        {children}
        <ChevronDown
          aria-hidden
          className="size-4 shrink-0 text-ink-faint transition-transform duration-(--duration-settle) ease-(--ease-settle) group-has-data-[panel-open]:rotate-180"
        />
      </BaseAccordion.Trigger>
    </BaseAccordion.Header>
  );
}

function AccordionContent({
  className,
  children,
  ...props
}: React.ComponentProps<typeof BaseAccordion.Panel>) {
  return (
    <BaseAccordion.Panel
      data-slot="accordion-content"
      className={cn(
        "h-(--accordion-panel-height) overflow-hidden bg-well text-sm text-ink-muted shadow-deboss-1 transition-[height] duration-(--duration-settle) ease-(--ease-settle) data-ending-style:h-0 data-starting-style:h-0",
        className,
      )}
      {...props}
    >
      <div className="px-4 py-3">{children}</div>
    </BaseAccordion.Panel>
  );
}

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };