Vela UI Docsvenizia.ai

Accordion

A vertically stacked set of interactive headings that each reveal a section of content.

npx shadcn@latest add @velaui/accordion
npx shadcn@latest add @velaui/accordion

Examples

  • Default
  • Outline fill indicator
  • Fill numbered
  • Icon leading
  • Disabled

View code

Accordion · Default

Installation

pnpm dlx velaui add @velaui/accordion
pnpm dlx velaui add @velaui/accordion

Code

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

export default function Example() {
  return (
    <Accordion type="single" collapsible defaultValue="item-1">
      <AccordionItem value="item-1">
        <AccordionTrigger>What is your refund policy?</AccordionTrigger>
        <AccordionContent>
          You can request a full refund within 30 days of purchase.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Do you offer support?</AccordionTrigger>
        <AccordionContent>
          Yes, our team is available around the clock via chat and email.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-3">
        <AccordionTrigger>Can I cancel anytime?</AccordionTrigger>
        <AccordionContent>
          Absolutely — cancel your subscription at any time from settings.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion defaultValue={["item-1"]}>
      <AccordionItem value="item-1">
        <AccordionTrigger>What is your refund policy?</AccordionTrigger>
        <AccordionContent>
          You can request a full refund within 30 days of purchase.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Do you offer support?</AccordionTrigger>
        <AccordionContent>
          Yes, our team is available around the clock via chat and email.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-3">
        <AccordionTrigger>Can I cancel anytime?</AccordionTrigger>
        <AccordionContent>
          Absolutely — cancel your subscription at any time from settings.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

View code

Accordion · Outline fill indicator

Installation

pnpm dlx velaui add @velaui/accordion
pnpm dlx velaui add @velaui/accordion

Code

accordion-outline-fill-indicator.tsx
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="outline" size="lg" type="single" collapsible defaultValue="item-1">
      <AccordionItem value="item-1">
        <AccordionTrigger indicator="fill">How does billing work?</AccordionTrigger>
        <AccordionContent>
          You're billed monthly based on your active plan and usage.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger indicator="fill">Is there a free trial?</AccordionTrigger>
        <AccordionContent>
          Every plan includes a 14-day free trial, no card required.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="outline" size="lg" defaultValue={["item-1"]}>
      <AccordionItem value="item-1">
        <AccordionTrigger indicator="fill">How does billing work?</AccordionTrigger>
        <AccordionContent>
          You're billed monthly based on your active plan and usage.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger indicator="fill">Is there a free trial?</AccordionTrigger>
        <AccordionContent>
          Every plan includes a 14-day free trial, no card required.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

View code

Accordion · Fill numbered

Installation

pnpm dlx velaui add @velaui/accordion
pnpm dlx velaui add @velaui/accordion

Code

accordion-fill-numbered.tsx
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionLeading,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="fill" size="sm" type="single" collapsible defaultValue="item-1">
      <AccordionItem value="item-1">
        <AccordionTrigger>
          <AccordionLeading>01</AccordionLeading>
          Create your account
        </AccordionTrigger>
        <AccordionContent>
          Sign up with your email and choose a workspace name.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>
          <AccordionLeading>02</AccordionLeading>
          Invite your team
        </AccordionTrigger>
        <AccordionContent>
          Add teammates by email — they'll get an invite instantly.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionLeading,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="fill" size="sm" defaultValue={["item-1"]}>
      <AccordionItem value="item-1">
        <AccordionTrigger>
          <AccordionLeading>01</AccordionLeading>
          Create your account
        </AccordionTrigger>
        <AccordionContent>
          Sign up with your email and choose a workspace name.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>
          <AccordionLeading>02</AccordionLeading>
          Invite your team
        </AccordionTrigger>
        <AccordionContent>
          Add teammates by email — they'll get an invite instantly.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

View code

Accordion · Icon leading

Installation

pnpm dlx velaui add @velaui/accordion
pnpm dlx velaui add @velaui/accordion

Code

accordion-icon-leading.tsx
import { Card01 } from "@/components/icons/card-01";
import { Shield01 } from "@/components/icons/shield-01";
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionLeading,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="outline" size="md" type="single" collapsible defaultValue="item-1">
      <AccordionItem value="item-1">
        <AccordionTrigger>
          <AccordionLeading>
            <Card01 />
          </AccordionLeading>
          Payment methods
        </AccordionTrigger>
        <AccordionContent>
          We accept all major credit cards and PayPal.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>
          <AccordionLeading>
            <Shield01 />
          </AccordionLeading>
          Data security
        </AccordionTrigger>
        <AccordionContent>
          Your data is encrypted at rest and in transit.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
import { Card01 } from "@/components/icons/card-01";
import { Shield01 } from "@/components/icons/shield-01";
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionLeading,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="outline" size="md" defaultValue={["item-1"]}>
      <AccordionItem value="item-1">
        <AccordionTrigger>
          <AccordionLeading>
            <Card01 />
          </AccordionLeading>
          Payment methods
        </AccordionTrigger>
        <AccordionContent>
          We accept all major credit cards and PayPal.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>
          <AccordionLeading>
            <Shield01 />
          </AccordionLeading>
          Data security
        </AccordionTrigger>
        <AccordionContent>
          Your data is encrypted at rest and in transit.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

View code

Accordion · Disabled

Installation

pnpm dlx velaui add @velaui/accordion
pnpm dlx velaui add @velaui/accordion

Code

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

export default function Example() {
  return (
    <Accordion variant="underline" size="md" type="single" collapsible>
      <AccordionItem value="item-1" disabled>
        <AccordionTrigger>Enterprise SSO (coming soon)</AccordionTrigger>
        <AccordionContent>
          Single sign-on for enterprise customers is on the roadmap.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>API access</AccordionTrigger>
        <AccordionContent>
          Generate an API key from your account settings to get started.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion variant="underline" size="md">
      <AccordionItem value="item-1" disabled>
        <AccordionTrigger>Enterprise SSO (coming soon)</AccordionTrigger>
        <AccordionContent>
          Single sign-on for enterprise customers is on the roadmap.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>API access</AccordionTrigger>
        <AccordionContent>
          Generate an API key from your account settings to get started.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

Usage

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

export default function Example() {
  return (
    <Accordion type="single" collapsible defaultValue="item-1">
      <AccordionItem value="item-1">
        <AccordionTrigger>What is your refund policy?</AccordionTrigger>
        <AccordionContent>
          You can request a full refund within 30 days of purchase.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Do you offer support?</AccordionTrigger>
        <AccordionContent>
          Yes, our team is available around the clock via chat and email.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-3">
        <AccordionTrigger>Can I cancel anytime?</AccordionTrigger>
        <AccordionContent>
          Absolutely — cancel your subscription at any time from settings.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from "@/components/ui/accordion";

export default function Example() {
  return (
    <Accordion defaultValue={["item-1"]}>
      <AccordionItem value="item-1">
        <AccordionTrigger>What is your refund policy?</AccordionTrigger>
        <AccordionContent>
          You can request a full refund within 30 days of purchase.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Do you offer support?</AccordionTrigger>
        <AccordionContent>
          Yes, our team is available around the clock via chat and email.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-3">
        <AccordionTrigger>Can I cancel anytime?</AccordionTrigger>
        <AccordionContent>
          Absolutely — cancel your subscription at any time from settings.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

Props

Accordion

PropTypeDefault
sizeAccordionSize"md"
variant"fill" | "underline" | "outline""underline"

Takes these and every prop of AccordionProps.

AccordionItem

PropTypeDefault
variant"fill" | "underline" | "outline"—

Takes these and every prop of AccordionItemProps.

AccordionTrigger

PropTypeDefault
indicator"fill" | "default""default"
sizeAccordionSize—

Takes these and every prop of AccordionTriggerProps.

AccordionLeading

Takes every prop of AccordionLeadingProps.

AccordionContent

PropTypeDefault
sizeAccordionSize—

Takes these and every prop of AccordionContentProps.

Accordion

PropTypeDefault
sizeAccordionSize"md"
variant"underline" | "outline" | "fill""underline"

Takes these and every prop of AccordionProps.

AccordionItem

PropTypeDefault
variant"underline" | "outline" | "fill"—

Takes these and every prop of AccordionItemProps.

AccordionTrigger

PropTypeDefault
indicator"fill" | "default""default"
sizeAccordionSize—

Takes these and every prop of AccordionTriggerProps.

AccordionLeading

Takes every prop of AccordionLeadingProps.

AccordionContent

PropTypeDefault
sizeAccordionSize—

Takes these and every prop of AccordionContentProps.

Dependencies

npm
class-variance-authority@0.7.1radix-ui@1.6.0
Installs with it
1 icon
npm
@base-ui/react@1.6.0class-variance-authority@0.7.1
Installs with it
1 icon

Source

components/ui/accordion.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Accordion as AccordionPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronDown } from "@/components/icons/chevron-down";

import { cn } from "@/lib/utils";

type AccordionVariant = "underline" | "outline" | "fill";
type AccordionSize = "sm" | "md" | "lg";

/**
 * Style and Size are properties of the whole accordion in the design — every
 * item in an example frame carries the same pair — so the root publishes them
 * and each part reads them, the way ToggleGroup does upstream.
 */
const AccordionContext = React.createContext<{
  variant: AccordionVariant;
  size: AccordionSize;
}>({ variant: "underline", size: "md" });

const accordionVariants = cva("flex w-full flex-col", {
  variants: {
    variant: {
      // Underline items butt together — the shared hairline IS the divider.
      underline: "gap-0",
      outline: "gap-1",
      fill: "gap-1",
    },
  },
  defaultVariants: {
    variant: "underline",
  },
});

const accordionItemVariants = cva("data-disabled:pointer-events-none", {
  variants: {
    variant: {
      underline:
        "border-b border-b-border-default data-open:border-b-border-active",
      // Outline draws no surface at rest; both open and disabled add one.
      outline:
        "rounded-xl border border-border-default px-4 data-open:bg-bg-surface-1 data-disabled:bg-bg-surface-1",
      fill: "rounded-xl bg-bg-surface-1 px-4",
    },
  },
  defaultVariants: {
    variant: "underline",
  },
});

const accordionTriggerVariants = cva(
  "group/accordion-trigger flex flex-1 items-center gap-2 rounded-lg border border-transparent py-4 text-left font-semibold leading-[1.4] text-text-strong transition-all outline-none hover:underline focus-visible:border-ring-neutral-outline focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow disabled:pointer-events-none disabled:cursor-not-allowed disabled:text-text-disabled **:data-[slot=accordion-trigger-icon]:ml-auto",
  {
    variants: {
      size: {
        sm: "text-sm",
        md: "text-base",
        lg: "text-lg",
      },
    },
    defaultVariants: {
      size: "md",
    },
  },
);

const accordionTriggerIconVariants = cva(
  "pointer-events-none flex shrink-0 items-center justify-center transition-transform duration-200 group-aria-expanded/accordion-trigger:rotate-180",
  {
    variants: {
      indicator: {
        default:
          "text-icon-strong group-disabled/accordion-trigger:text-icon-disabled",
        fill: "rounded-full bg-bg-foreground p-1 text-icon-inverse",
      },
      size: {
        sm: "size-4",
        md: "size-5",
        lg: "size-6",
      },
    },
    defaultVariants: {
      indicator: "default",
      size: "md",
    },
  },
);

const accordionContentVariants = cva(
  "h-(--radix-accordion-content-height) pt-0 pb-4 leading-[1.4] text-text-muted [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-text-strong [&_p:not(:last-child)]:mb-2.5",
  {
    variants: {
      size: {
        sm: "text-sm",
        // The design caps body copy at 16 — lg titles keep md body text.
        md: "text-base",
        lg: "text-base",
      },
    },
    defaultVariants: {
      size: "md",
    },
  },
);

export type AccordionProps = React.ComponentProps<
  typeof AccordionPrimitive.Root
> &
  VariantProps<typeof accordionVariants> & {
    size?: AccordionSize;
  };

function Accordion({
  className,
  variant = "underline",
  size = "md",
  ...props
}: AccordionProps) {
  const context = React.useMemo(
    () => ({ variant: variant ?? "underline", size }),
    [variant, size],
  );

  return (
    <AccordionContext value={context}>
      <AccordionPrimitive.Root
        data-slot="accordion"
        data-variant={variant}
        data-size={size}
        className={cn(accordionVariants({ variant }), className)}
        {...props}
      />
    </AccordionContext>
  );
}

export type AccordionItemProps = React.ComponentProps<
  typeof AccordionPrimitive.Item
> &
  VariantProps<typeof accordionItemVariants>;

function AccordionItem({ className, variant, ...props }: AccordionItemProps) {
  const context = React.useContext(AccordionContext);
  const resolved = variant ?? context.variant;

  return (
    <AccordionPrimitive.Item
      data-slot="accordion-item"
      data-variant={resolved}
      className={cn(accordionItemVariants({ variant: resolved }), className)}
      {...props}
    />
  );
}

export type AccordionTriggerProps = React.ComponentProps<
  typeof AccordionPrimitive.Trigger
> & {
  size?: AccordionSize;
  /** Chevron treatment — bare glyph, or glyph on a filled disc. */
  indicator?: "default" | "fill";
};

function AccordionTrigger({
  className,
  children,
  size,
  indicator = "default",
  ...props
}: AccordionTriggerProps) {
  const context = React.useContext(AccordionContext);
  const resolved = size ?? context.size;

  return (
    <AccordionPrimitive.Header className="flex">
      <AccordionPrimitive.Trigger
        data-slot="accordion-trigger"
        data-size={resolved}
        className={cn(accordionTriggerVariants({ size: resolved }), className)}
        {...props}
      >
        {children}
        <span
          data-slot="accordion-trigger-icon"
          className={cn(
            accordionTriggerIconVariants({ indicator, size: resolved }),
          )}
        >
          <ChevronDown className="size-full" />
        </span>
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  );
}

export type AccordionLeadingProps = React.ComponentProps<"span">;

/**
 * Fixed-width leading slot in the trigger — holds the icon or the ordinal of
 * the design's `icon` / `number` types. The caller supplies the content.
 */
function AccordionLeading({
  className,
  children,
  ...props
}: AccordionLeadingProps) {
  return (
    <span
      data-slot="accordion-leading"
      className={cn(
        "block w-6 shrink-0 text-center text-sm font-medium leading-[1.4] text-text-note group-disabled/accordion-trigger:text-text-disabled",
        className,
      )}
      {...props}
    >
     <span className="inline-flex items-center justify-center align-middle [&_svg]:size-5 [&_svg]:shrink-0 [&_svg]:text-icon-strong group-disabled/accordion-trigger:[&_svg]:text-icon-disabled">
        {children}
      </span>
    </span>
  );
}

export type AccordionContentProps = React.ComponentProps<
  typeof AccordionPrimitive.Content
> & {
  size?: AccordionSize;
};

function AccordionContent({
  className,
  children,
  size,
  ...props
}: AccordionContentProps) {
  const context = React.useContext(AccordionContext);
  const resolved = size ?? context.size;

  return (
    <AccordionPrimitive.Content
      data-slot="accordion-content"
      className="overflow-hidden data-open:animate-accordion-down data-closed:animate-accordion-up"
      {...props}
    >
      <div
        data-size={resolved}
        className={cn(accordionContentVariants({ size: resolved }), className)}
      >
        {children}
      </div>
    </AccordionPrimitive.Content>
  );
}

export {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionLeading,
  AccordionContent,
  accordionVariants,
  accordionItemVariants,
  accordionTriggerVariants,
  accordionContentVariants,
};
components/ui/accordion.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronDown } from "@/components/icons/chevron-down";

import { cn } from "@/lib/utils";

type AccordionVariant = "underline" | "outline" | "fill";
type AccordionSize = "sm" | "md" | "lg";

/**
 * Style and Size are properties of the whole accordion in the design — every
 * item in an example frame carries the same pair — so the root publishes them
 * and each part reads them, the way ToggleGroup does upstream.
 */
const AccordionContext = React.createContext<{
  variant: AccordionVariant;
  size: AccordionSize;
}>({ variant: "underline", size: "md" });

const accordionVariants = cva("flex w-full flex-col", {
  variants: {
    variant: {
      // Underline items butt together — the shared hairline IS the divider.
      underline: "gap-0",
      outline: "gap-1",
      fill: "gap-1",
    },
  },
  defaultVariants: {
    variant: "underline",
  },
});

const accordionItemVariants = cva("data-disabled:pointer-events-none", {
  variants: {
    variant: {
      underline:
        "border-b border-b-border-default data-open:border-b-border-active",
      // Outline draws no surface at rest; both open and disabled add one.
      outline:
        "rounded-xl border border-border-default px-4 data-open:bg-bg-surface-1 data-disabled:bg-bg-surface-1",
      fill: "rounded-xl bg-bg-surface-1 px-4",
    },
  },
  defaultVariants: {
    variant: "underline",
  },
});

const accordionTriggerVariants = cva(
  "group/accordion-trigger flex flex-1 items-center gap-2 rounded-lg border border-transparent py-4 text-left font-semibold leading-[1.4] text-text-strong transition-all outline-none hover:underline focus-visible:border-ring-neutral-outline focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:text-text-disabled **:data-[slot=accordion-trigger-icon]:ml-auto",
  {
    variants: {
      size: {
        sm: "text-sm",
        md: "text-base",
        lg: "text-lg",
      },
    },
    defaultVariants: {
      size: "md",
    },
  },
);

const accordionTriggerIconVariants = cva(
  "pointer-events-none flex shrink-0 items-center justify-center transition-transform duration-200 group-aria-expanded/accordion-trigger:rotate-180",
  {
    variants: {
      indicator: {
        default:
          "text-icon-strong group-data-disabled/accordion-trigger:text-icon-disabled",
        fill: "rounded-full bg-bg-foreground p-1 text-icon-inverse",
      },
      size: {
        sm: "size-4",
        md: "size-5",
        lg: "size-6",
      },
    },
    defaultVariants: {
      indicator: "default",
      size: "md",
    },
  },
);

const accordionContentVariants = cva(
  "pt-0 pb-4 leading-[1.4] text-text-muted [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-text-strong [&_p:not(:last-child)]:mb-2.5",
  {
    variants: {
      size: {
        sm: "text-sm",
        // The design caps body copy at 16 — lg titles keep md body text.
        md: "text-base",
        lg: "text-base",
      },
    },
    defaultVariants: {
      size: "md",
    },
  },
);

export type AccordionProps = React.ComponentProps<
  typeof AccordionPrimitive.Root
> &
  VariantProps<typeof accordionVariants> & {
    size?: AccordionSize;
  };

function Accordion({
  className,
  variant = "underline",
  size = "md",
  ...props
}: AccordionProps) {
  const context = React.useMemo(
    () => ({ variant: variant ?? "underline", size }),
    [variant, size],
  );

  return (
    <AccordionContext value={context}>
      <AccordionPrimitive.Root
        data-slot="accordion"
        data-variant={variant}
        data-size={size}
        className={cn(accordionVariants({ variant }), className)}
        {...props}
      />
    </AccordionContext>
  );
}

export type AccordionItemProps = React.ComponentProps<
  typeof AccordionPrimitive.Item
> &
  VariantProps<typeof accordionItemVariants>;

function AccordionItem({ className, variant, ...props }: AccordionItemProps) {
  const context = React.useContext(AccordionContext);
  const resolved = variant ?? context.variant;

  return (
    <AccordionPrimitive.Item
      data-slot="accordion-item"
      data-variant={resolved}
      className={cn(accordionItemVariants({ variant: resolved }), className)}
      {...props}
    />
  );
}

export type AccordionTriggerProps = React.ComponentProps<
  typeof AccordionPrimitive.Trigger
> & {
  size?: AccordionSize;
  /** Chevron treatment — bare glyph, or glyph on a filled disc. */
  indicator?: "default" | "fill";
};

function AccordionTrigger({
  className,
  children,
  size,
  indicator = "default",
  ...props
}: AccordionTriggerProps) {
  const context = React.useContext(AccordionContext);
  const resolved = size ?? context.size;

  return (
    <AccordionPrimitive.Header className="flex">
      <AccordionPrimitive.Trigger
        data-slot="accordion-trigger"
        data-size={resolved}
        className={cn(accordionTriggerVariants({ size: resolved }), className)}
        {...props}
      >
        {children}
        <span
          data-slot="accordion-trigger-icon"
          className={cn(
            accordionTriggerIconVariants({ indicator, size: resolved }),
          )}
        >
          <ChevronDown className="size-full" />
        </span>
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  );
}

export type AccordionLeadingProps = React.ComponentProps<"span">;

/**
 * Fixed-width leading slot in the trigger — holds the icon or the ordinal of
 * the design's `icon` / `number` types. The caller supplies the content.
 */
function AccordionLeading({
  className,
  children,
  ...props
}: AccordionLeadingProps) {
  return (
    <span
      data-slot="accordion-leading"
      className={cn(
        "block w-6 shrink-0 text-center text-sm font-medium leading-[1.4] text-text-note group-data-disabled/accordion-trigger:text-text-disabled",
        className,
      )}
      {...props}
    >
     <span className="inline-flex items-center justify-center align-middle [&_svg]:size-5 [&_svg]:shrink-0 [&_svg]:text-icon-strong group-data-disabled/accordion-trigger:[&_svg]:text-icon-disabled">
        {children}
      </span>
    </span>
  );
}

export type AccordionContentProps = React.ComponentProps<
  typeof AccordionPrimitive.Panel
> & {
  size?: AccordionSize;
};

function AccordionContent({
  className,
  children,
  size,
  ...props
}: AccordionContentProps) {
  const context = React.useContext(AccordionContext);
  const resolved = size ?? context.size;

  return (
    <AccordionPrimitive.Panel
      data-slot="accordion-content"
      className="h-(--accordion-panel-height) overflow-hidden transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0"
      {...props}
    >
      <div
        data-size={resolved}
        className={cn(accordionContentVariants({ size: resolved }), className)}
      >
        {children}
      </div>
    </AccordionPrimitive.Panel>
  );
}

export {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionLeading,
  AccordionContent,
  accordionVariants,
  accordionItemVariants,
  accordionTriggerVariants,
  accordionContentVariants,
};