component

Accordion

A collapsible disclosure component following the W3C ARIA accordion pattern. Each item has an <h3>-wrapped trigger <button> paired with a role="region" panel. Adjacent interactive controls (action buttons, drag handles) live as siblings of the trigger inside the header row — independently focusable, never nested inside a button.

Find-in-page (Ctrl/⌘+F) still reveals text in collapsed panels: the panel carries hidden="until-found" when closed, and the browser’s beforematch event opens the matching item (Chromium; Firefox/Safari skip the auto-reveal, matching their native <details> behavior).

Expansion Behavior

The type prop controls how many items can be open at once. single (default) collapses the previously open item when another opens — keeping the view focused. multiple lets any number of items be open simultaneously.

Use collapsible (default true) on single accordions to allow the open item to be collapsed back to nothing.

Single

Pindoba is a design system providing framework-agnostic UI components for Astro, React, and Svelte.

Multiple

Pindoba is a design system providing framework-agnostic UI components for Astro, React, and Svelte.

Install individual packages via pnpm, for example:pnpm add @pindoba/astro-accordion.

Disabled Item

This item can be expanded and collapsed normally.

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <!-- Single mode (default) -->
  <div>
    <h3>Single</h3>
    <Accordion type="single" data-collapsible="true">
      <AccordionItem value="item-1" title="What is Pindoba?" open>
        <p>
          Pindoba is a design system providing framework-agnostic UI components
          for Astro, React, and Svelte.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="How do I install it?">
        <p>
          Install individual packages via pnpm, for example:
          <code>pnpm add @pindoba/astro-accordion</code>.
        </p>
      </AccordionItem>
      <AccordionItem value="item-3" title="Is it customizable?">
        <p>
          Yes — every component exposes a <code>passThrough</code> prop for custom
          styles and HTML attributes on each slot.
        </p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Multiple mode -->
  <div>
    <h3>Multiple</h3>
    <Accordion type="multiple">
      <AccordionItem value="item-1" title="What is Pindoba?" open>
        <p>
          Pindoba is a design system providing framework-agnostic UI components
          for Astro, React, and Svelte.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="How do I install it?" open>
        <p>
          Install individual packages via pnpm, for example:
          <code>pnpm add @pindoba/astro-accordion</code>.
        </p>
      </AccordionItem>
      <AccordionItem value="item-3" title="Is it customizable?">
        <p>
          Yes — every component exposes a <code>passThrough</code> prop for custom
          styles and HTML attributes on each slot.
        </p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Disabled item -->
  <div>
    <h3>Disabled Item</h3>
    <Accordion type="single">
      <AccordionItem value="item-1" title="Available Section" open>
        <p>This item can be expanded and collapsed normally.</p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Disabled Section" disabled>
        <p>This item is not accessible when disabled.</p>
      </AccordionItem>
      <AccordionItem value="item-3" title="Another Available Section">
        <p>This item is also available for interaction.</p>
      </AccordionItem>
    </Accordion>
  </div>
</div>

Size

Three sizes scale the trigger padding, content padding, and font size together. The size is set once on the root <Accordion> and inherited by every item.

Small

Small size uses tighter padding and a smaller font — suitable for dense layouts or secondary content areas.

Medium (default)

Medium size is the default — balanced padding and font size for most use cases.

Large

Large size uses generous padding and a larger font — ideal for prominent content or marketing pages.

Per-item override

This item uses the root's size="sm".

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <!-- Small — size on the root cascades to items via CSS variables -->
  <div>
    <h3>Small</h3>
    <Accordion type="single" size="sm">
      <AccordionItem value="item-1" title="Compact section" open>
        <p>
          Small size uses tighter padding and a smaller font — suitable for
          dense layouts or secondary content areas.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Another compact section">
        <p>
          Inherits <code>size="sm"</code> from the root — no need to repeat on each
          item.
        </p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Medium (default) -->
  <div>
    <h3>Medium (default)</h3>
    <Accordion type="single" size="md">
      <AccordionItem value="item-1" title="Standard section" open>
        <p>
          Medium size is the default — balanced padding and font size for most
          use cases.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Another standard section">
        <p>Inherits <code>size="md"</code> from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Large -->
  <div>
    <h3>Large</h3>
    <Accordion type="single" size="lg">
      <AccordionItem value="item-1" title="Spacious section" open>
        <p>
          Large size uses generous padding and a larger font — ideal for
          prominent content or marketing pages.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Another spacious section">
        <p>Inherits <code>size="lg"</code> from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Per-item override -->
  <div>
    <h3>Per-item override</h3>
    <Accordion type="single" size="sm">
      <AccordionItem value="item-1" title="Small (inherited)" open>
        <p>This item uses the root's <code>size="sm"</code>.</p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Large (override)" size="lg">
        <p>
          This item sets its own <code>size="lg"</code>, which overrides the
          root's <code>sm</code>.
        </p>
      </AccordionItem>
      <AccordionItem value="item-3" title="Small again (inherited)">
        <p>Back to inheriting from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>
</div>

Items Array

For data-driven usage, pass an items array to <Accordion> and it renders one <AccordionItem> per entry. Root-level props (size, feedback, background, …) act as defaults; each entry can override via its own fields. Body content is provided per framework: named slots in Astro (<Fragment slot="<value>">), named snippets or the snippets map in Svelte (keyed by each item’s value). For rich custom trigger content, fall back to slot composition with <AccordionItem> directly.

Items array with cascaded defaults

Rendered from a named slot matching the item's value. Inherits size="md" and feedback="primary" from the root.

Per-item overrides

Uses root defaults.

Item sets size: "lg", which wins over the root's sm.

With subtitles

---
import Accordion from "../Accordion.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <!-- Basic items array with named slots -->
  <div>
    <h3>Items array with cascaded defaults</h3>
    <Accordion
      type="single"
      size="md"
      feedback="primary"
      items={[
        { value: "first", title: "First", open: true },
        { value: "second", title: "Second" },
        { value: "third", title: "Third" },
      ]}
    >
      <Fragment slot="first">
        <p>
          Rendered from a named slot matching the item's <code>value</code>.
          Inherits <code>size="md"</code> and <code>feedback="primary"</code> from
          the root.
        </p>
      </Fragment>
      <Fragment slot="second">
        <p>Same defaults as the first.</p>
      </Fragment>
      <Fragment slot="third">
        <p>Also inherits from the root.</p>
      </Fragment>
    </Accordion>
  </div>

  <!-- Per-item overrides -->
  <div>
    <h3>Per-item overrides</h3>
    <Accordion
      type="multiple"
      size="sm"
      feedback="neutral"
      items={[
        {
          value: "default",
          title: "Default (size=sm, feedback=neutral)",
          open: true,
        },
        { value: "larger", title: "Overrides size", size: "lg", open: true },
        { value: "danger", title: "Overrides feedback", feedback: "danger" },
        { value: "disabled", title: "Disabled", disabled: true },
      ]}
    >
      <Fragment slot="default">
        <p>Uses root defaults.</p>
      </Fragment>
      <Fragment slot="larger">
        <p>
          Item sets <code>size: "lg"</code>, which wins over the root's <code
            >sm</code
          >.
        </p>
      </Fragment>
      <Fragment slot="danger">
        <p>
          Item sets <code>feedback: "danger"</code>, which wins over the root's
          <code>neutral</code>.
        </p>
      </Fragment>
      <Fragment slot="disabled">
        <p>Set per item.</p>
      </Fragment>
    </Accordion>
  </div>

  <!-- Subtitles -->
  <div>
    <h3>With subtitles</h3>
    <Accordion
      type="single"
      size="md"
      items={[
        {
          value: "auth",
          title: "Authentication",
          subtitle: "OAuth providers and session settings",
        },
        {
          value: "db",
          title: "Database",
          subtitle: "Connection strings, pooling, migrations",
        },
        {
          value: "storage",
          title: "Storage",
          subtitle: "File uploads and CDN",
        },
      ]}
    >
      <Fragment slot="auth">
        <p>Configure sign-in options.</p>
      </Fragment>
      <Fragment slot="db">
        <p>Manage database connectivity.</p>
      </Fragment>
      <Fragment slot="storage">
        <p>Configure object storage and CDN.</p>
      </Fragment>
    </Accordion>
  </div>
</div>

Feedback Colors

Accordion items use Panel under the hood — pass feedback and border on the root to color all items consistently. Use feedback for semantic meaning: primary for key content, success for completed flows, warning for notices, and danger for critical information.

Neutral

Neutral is the default feedback color — use it for standard content without any special semantic meaning.

Primary

Primary feedback applies the brand color palette — use it to highlight important content or onboarding steps.

Success

Success feedback conveys positive outcomes — use it for completed steps, confirmation flows, or resolved states.

Warning

Warning feedback draws attention to caution states — use it for deprecation notices, rate limits, or soft restrictions.

Danger

Danger feedback signals critical or destructive information — use it for breaking changes, error states, or irreversible actions.

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <!-- Neutral -->
  <div>
    <h3>Neutral</h3>
    <Accordion type="single" feedback="neutral">
      <AccordionItem value="item-1" title="General information" open>
        <p>
          Neutral is the default feedback color — use it for standard content
          without any special semantic meaning.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Additional details">
        <p>Each item inherits the feedback color and border from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Primary -->
  <div>
    <h3>Primary</h3>
    <Accordion type="single" feedback="primary">
      <AccordionItem value="item-1" title="Getting started" open>
        <p>
          Primary feedback applies the brand color palette — use it to highlight
          important content or onboarding steps.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Next steps">
        <p>Each item inherits the feedback color and border from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Success -->
  <div>
    <h3>Success</h3>
    <Accordion type="single" feedback="success">
      <AccordionItem value="item-1" title="Completed tasks" open>
        <p>
          Success feedback conveys positive outcomes — use it for completed
          steps, confirmation flows, or resolved states.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="What's next">
        <p>Each item inherits the feedback color and border from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Warning -->
  <div>
    <h3>Warning</h3>
    <Accordion type="single" feedback="warning">
      <AccordionItem value="item-1" title="Deprecation notice" open>
        <p>
          Warning feedback draws attention to caution states — use it for
          deprecation notices, rate limits, or soft restrictions.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Migration guide">
        <p>Each item inherits the feedback color and border from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Danger -->
  <div>
    <h3>Danger</h3>
    <Accordion type="single" feedback="danger">
      <AccordionItem value="item-1" title="Breaking changes" open>
        <p>
          Danger feedback signals critical or destructive information — use it
          for breaking changes, error states, or irreversible actions.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="How to resolve">
        <p>Each item inherits the feedback color and border from the root.</p>
      </AccordionItem>
    </Accordion>
  </div>
</div>

Emphasis

emphasis follows panel’s three-tier model. primary fills each item with the feedback accent surface and contrast text. secondary (default) uses the feedback-tinted soft surface — the standard accordion look. tertiary keeps the surface neutral but tints the trigger text and border with the feedback color, useful when a tinted background would compete with the surrounding page.

Set emphasis per <AccordionItem> for slot-composed accordions (each child carries its own panel surface). When you use the items array on <Accordion>, the root-level emphasis acts as a default for every item — same shape as background, border, and padding.

Primary

Primary emphasis fills each item with the feedback's accent surface and uses contrast text. Use it for the most prominent accordion sections.

Secondary (default)

Secondary emphasis paints each item with the feedback's soft surface. This is the default — most accordions use it.

Tertiary

Tertiary emphasis keeps the surface neutral but tints the text and border with the feedback color. Useful when a tinted background would compete with surrounding content.

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <!-- Primary — filled feedback accent surface -->
  <div>
    <h3>Primary</h3>
    <Accordion type="single" feedback="primary">
      <AccordionItem
        value="item-1"
        title="Filled accent surface"
        emphasis="primary"
        open
      >
        <p>
          Primary emphasis fills each item with the feedback's accent surface
          and uses contrast text. Use it for the most prominent accordion
          sections.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="More detail" emphasis="primary">
        <p>The trigger and indicator read against the bold surface.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Secondary (default) — feedback-tinted soft surface -->
  <div>
    <h3>Secondary (default)</h3>
    <Accordion type="single" feedback="primary">
      <AccordionItem value="item-1" title="Feedback-tinted soft surface" open>
        <p>
          Secondary emphasis paints each item with the feedback's soft surface.
          This is the default — most accordions use it.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="More detail">
        <p>Trigger text picks up the feedback's accent color.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Tertiary — neutral surface, feedback-tinted text -->
  <div>
    <h3>Tertiary</h3>
    <Accordion type="single" feedback="primary">
      <AccordionItem
        value="item-1"
        title="Neutral surface, feedback text"
        emphasis="tertiary"
        open
      >
        <p>
          Tertiary emphasis keeps the surface neutral but tints the text and
          border with the feedback color. Useful when a tinted background would
          compete with surrounding content.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="More detail" emphasis="tertiary">
        <p>The trigger and content read against neutral surface.</p>
      </AccordionItem>
    </Accordion>
  </div>
</div>

Rich Triggers

Accordion items use Banner internally for their trigger area. This means you can add a subtitle, inline icons via headingLeading / headingTrailing, and a trailing element at the far right of the trigger row — all without replacing the whole trigger. These slots are decorative — they render inside the trigger button, so don’t put interactive controls (like <button> or links) here. For interactive header controls, see Header Actions below.

Subtitle

Heading Trailing

Leading Icon

Trailing Action

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import Stamp from "@pindoba/astro-stamp";
import {
  Bell,
  Database,
  HardDrive,
  KeyRound,
  Settings,
  Shield,
  User,
} from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const badge = css({
  display: "inline-flex",
  alignItems: "center",
  px: "xs",
  py: "3xs",
  borderRadius: "full",
  fontSize: "xs",
  fontWeight: "semibold",
  background: "colorPalette.surface.valley",
  color: "colorPalette.text",
});
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <!-- Subtitle -->
  <div>
    <h3>Subtitle</h3>
    <Accordion type="single" collapsible>
      <AccordionItem
        value="item-1"
        title="Authentication"
        subtitle="Configure OAuth providers and session settings"
      >
        <Stamp
          slot="leading"
          feedback="neutral"
          emphasis="secondary"
          shape="square"
        >
          <KeyRound />
        </Stamp>
        <p>
          Add a <code>subtitle</code> to provide secondary context below the title
          without losing the clickable trigger area.
        </p>
      </AccordionItem>
      <AccordionItem
        value="item-2"
        title="Database"
        subtitle="Connection strings, pooling, and migrations"
      >
        <Stamp
          slot="leading"
          feedback="neutral"
          emphasis="secondary"
          shape="square"
        >
          <Database />
        </Stamp>
        <p>Configure your database connection and migration settings here.</p>
      </AccordionItem>
      <AccordionItem
        value="item-3"
        title="Storage"
        subtitle="File uploads and CDN configuration"
      >
        <Stamp
          slot="leading"
          feedback="neutral"
          emphasis="secondary"
          shape="square"
        >
          <HardDrive />
        </Stamp>
        <p>Set up file storage providers and CDN delivery options.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Heading trailing — count or status badge -->
  <div>
    <h3>Heading Trailing</h3>
    <Accordion type="multiple">
      <AccordionItem value="item-1" title="Open Issues">
        <span slot="heading-trailing" class={badge}>12</span>
        <p>
          Use the <code>heading-trailing</code> slot to place a badge, count, or status
          indicator inline with the title text.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Pull Requests">
        <span slot="heading-trailing" class={badge}>3</span>
        <p>Active pull requests waiting for review.</p>
      </AccordionItem>
      <AccordionItem value="item-3" title="Deployments">
        <span slot="heading-trailing" class={badge}>1</span>
        <p>Recent deployment activity and logs.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Leading icon -->
  <div>
    <h3>Leading Icon</h3>
    <Accordion type="single" collapsible>
      <AccordionItem value="item-1" title="Profile">
        <Stamp
          slot="heading-leading"
          feedback="primary"
          emphasis="secondary"
          shape="circle"
          size="xs"
        >
          <User />
        </Stamp>
        <p>
          Use the <code>heading-leading</code> slot to prepend a Stamp icon directly
          beside the title text.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Notifications">
        <Stamp
          slot="heading-leading"
          feedback="warning"
          emphasis="secondary"
          shape="circle"
          size="xs"
        >
          <Bell />
        </Stamp>
        <p>Notification preferences and delivery settings.</p>
      </AccordionItem>
      <AccordionItem value="item-3" title="Security">
        <Stamp
          slot="heading-leading"
          feedback="success"
          emphasis="secondary"
          shape="circle"
          size="xs"
        >
          <Shield />
        </Stamp>
        <p>Two-factor authentication and session management.</p>
      </AccordionItem>
      <AccordionItem value="item-4" title="Preferences">
        <Stamp
          slot="heading-leading"
          feedback="neutral"
          emphasis="secondary"
          shape="circle"
          size="xs"
        >
          <Settings />
        </Stamp>
        <p>General application preferences and defaults.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Trailing row action -->
  <div>
    <h3>Trailing Action</h3>
    <Accordion type="single" collapsible>
      <AccordionItem value="item-1" title="Environment Variables">
        <span
          slot="trailing"
          class={css({
            fontSize: "xs",
            color: "neutral.text.muted",
            fontWeight: "normal",
            flexShrink: 0,
          })}
        >
          12 variables
        </span>
        <p>
          Use the <code>trailing</code> slot to place content at the far right of
          the trigger row — outside the title/subtitle, before the expand indicator.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Build Cache">
        <span
          slot="trailing"
          class={css({
            fontSize: "xs",
            color: "success.text",
            fontWeight: "semibold",
            flexShrink: 0,
          })}
        >
          Active
        </span>
        <p>Current build cache status and invalidation controls.</p>
      </AccordionItem>
    </Accordion>
  </div>
</div>

Header Actions

When an item needs interactive controls in its header — a delete button, an overflow menu, a drag handle — use the adjacent slots: actionsLeading (left of the trigger) and actionsTrailing (right of the trigger). They render as siblings of the trigger inside the header row, so activating them doesn’t toggle the accordion and they’re independently focusable.

There are two tiers of header slots — pick the right one for the job:

  • In-trigger, decorative (leading, trailing, headingLeading, headingTrailing, subheadingLeading, subheadingTrailing): rendered inside the trigger button. Icons, badges, counts. No interactive children.
  • Adjacent, interactive (actionsLeading, actionsTrailing): rendered next to the trigger. Buttons, menu triggers, drag handles.

Trailing actions

Use the actions-trailing named slot for interactive controls that sit next to the trigger without toggling it.

Leading drag handle

Use the actions-leading named slot for a drag handle (or any leading control). Activating it doesn't toggle the panel.

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import Button from "@pindoba/astro-button";
import { Ellipsis, GripVertical, Pencil, Trash2 } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const handle = css({
  display: "inline-flex",
  alignItems: "center",
  justifyContent: "center",
  p: "2xs",
  color: "neutral.text.muted",
  cursor: "grab",
  borderRadius: "xs",
  _hover: { background: "neutral.surface.hill", color: "neutral.text" },
  _focusVisible: { pindobaOutline: true },
  _active: { cursor: "grabbing" },
});
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <!-- Trailing actions -->
  <div>
    <h3>Trailing actions</h3>
    <p>
      Use the <code>actions-trailing</code> named slot for interactive controls that
      sit next to the trigger without toggling it.
    </p>
    <Accordion type="single" collapsible>
      <AccordionItem
        value="profile"
        title="Profile"
        subtitle="Name, avatar, and bio"
      >
        <Fragment slot="actions-trailing">
          <Button emphasis="ghost" size="sm" shape="square" aria-label="Edit">
            <Pencil />
          </Button>
          <Button
            emphasis="ghost"
            size="sm"
            shape="square"
            feedback="danger"
            aria-label="Delete"
          >
            <Trash2 />
          </Button>
        </Fragment>
        <p>Click the icon buttons — they don't toggle the accordion.</p>
      </AccordionItem>
      <AccordionItem
        value="billing"
        title="Billing"
        subtitle="Plan and invoices"
      >
        <Button
          slot="actions-trailing"
          emphasis="ghost"
          size="sm"
          shape="square"
          aria-label="More"
        >
          <Ellipsis />
        </Button>
        <p>An ellipsis menu placed alongside the trigger.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Leading drag handle -->
  <div>
    <h3>Leading drag handle</h3>
    <p>
      Use the <code>actions-leading</code> named slot for a drag handle (or any leading
      control). Activating it doesn't toggle the panel.
    </p>
    <Accordion type="multiple">
      <AccordionItem value="layer-1" title="Background">
        <button
          slot="actions-leading"
          type="button"
          class={handle}
          aria-label="Reorder Background"
        >
          <GripVertical />
        </button>
        <p>Layer settings for the background plate.</p>
      </AccordionItem>
      <AccordionItem value="layer-2" title="Foreground">
        <button
          slot="actions-leading"
          type="button"
          class={handle}
          aria-label="Reorder Foreground"
        >
          <GripVertical />
        </button>
        <Button
          slot="actions-trailing"
          emphasis="ghost"
          size="sm"
          shape="square"
          aria-label="Edit Foreground"
        >
          <Pencil />
        </Button>
        <p>Layer settings for the foreground content.</p>
      </AccordionItem>
      <AccordionItem value="layer-3" title="Overlay">
        <button
          slot="actions-leading"
          type="button"
          class={handle}
          aria-label="Reorder Overlay"
        >
          <GripVertical />
        </button>
        <p>Layer settings for the top overlay.</p>
      </AccordionItem>
    </Accordion>
  </div>
</div>

Custom Styling

The passThrough prop on AccordionItem reaches into any slot — item, header, heading, trigger, indicator, actionsLeading, actionsTrailing, content, contentInner. Use style for Panda CSS overrides and props for extra HTML attributes. On the root Accordion, passThrough.root applies to the wrapper <div>.

Custom Trigger Style

Use passThrough.trigger.style on an item to restyle its trigger — here applying a monospace font for a file-tree appearance.

Custom Content Style

Use passThrough.content.style on an item to customize its content area — here adding a bold top border using the current feedback color palette.

ARIA Attributes

Use passThrough.root.props on the accordion to forward HTML attributes to the root element — here addingrole="region" and an aria-label for screen reader context.

---
import Accordion from "../Accordion.astro";
import AccordionItem from "../AccordionItem.astro";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---

<div
  class={stack({
    gap: "xl",
    direction: "column",
  })}
>
  <!-- Custom trigger style -->
  <div>
    <h3>Custom Trigger Style</h3>
    <Accordion type="single">
      <AccordionItem
        value="item-1"
        title="src/index.ts"
        open
        passThrough={{
          trigger: {
            style: css.raw({
              fontFamily: "mono",
              fontSize: "sm",
              letterSpacing: "0.04em",
            }),
          },
        }}
      >
        <p>
          Use <code>passThrough.trigger.style</code> on an item to restyle its trigger
          — here applying a monospace font for a file-tree appearance.
        </p>
      </AccordionItem>
      <AccordionItem
        value="item-2"
        title="src/components/Button.astro"
        passThrough={{
          trigger: {
            style: css.raw({
              fontFamily: "mono",
              fontSize: "sm",
              letterSpacing: "0.04em",
            }),
          },
        }}
      >
        <p>
          The style is applied via Panda CSS <code>css.raw()</code>, giving you
          full access to design tokens and responsive utilities.
        </p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- Custom content style -->
  <div>
    <h3>Custom Content Style</h3>
    <Accordion type="single" feedback="primary">
      <AccordionItem
        value="item-1"
        title="Separated content area"
        open
        passThrough={{
          content: {
            style: css.raw({
              borderTop: "2px solid",
              borderColor: "colorPalette.border.bold",
            }),
          },
        }}
      >
        <p>
          Use <code>passThrough.content.style</code> on an item to customize its content
          area — here adding a bold top border using the current feedback color palette.
        </p>
      </AccordionItem>
      <AccordionItem
        value="item-2"
        title="Another section"
        passThrough={{
          content: {
            style: css.raw({
              borderTop: "2px solid",
              borderColor: "colorPalette.border.bold",
            }),
          },
        }}
      >
        <p>Each item's content slot gets the same custom styles.</p>
      </AccordionItem>
    </Accordion>
  </div>

  <!-- ARIA attributes via passThrough props -->
  <div>
    <h3>ARIA Attributes</h3>
    <Accordion
      type="single"
      passThrough={{
        root: {
          props: {
            role: "region",
            "aria-label": "Frequently asked questions",
          },
        },
      }}
    >
      <AccordionItem value="item-1" title="What does this do?" open>
        <p>
          Use <code>passThrough.root.props</code> on the accordion to forward HTML
          attributes to the root element — here adding
          <code>role="region"</code> and an <code>aria-label</code> for screen reader
          context.
        </p>
      </AccordionItem>
      <AccordionItem value="item-2" title="Why use passThrough?">
        <p>
          <code>passThrough</code> is the escape hatch for any customization not covered
          by component props. It keeps the public API clean while remaining fully
          flexible.
        </p>
      </AccordionItem>
    </Accordion>
  </div>
</div>
props · 23 shown · 23 total
Accordion
background
"surface.peak""surface.hill""surface.base""surface.valley""surface.ground""transparent"

Surface level for each item's Panel background. Inherited by all items unless overridden per-item.

border
"none""bold""default""muted""accent"

Box-shadow ring border applied to each item. Uses box-shadow so it is layout-neutral.

collapsible
boolean
default true

In `single` mode, allow the open item to be collapsed back to nothing. Has no effect in `multiple` mode.

defaultValue
AccordionItemValue[]

Uncontrolled initial list of expanded item values.

disabled
boolean
default false

Disable the entire accordion and all its items.

emphasis
"primary""secondary""tertiary"

Visual emphasis applied to each item's Panel. `primary` is a solid feedback accent surface with contrast text; `secondary` is a feedback-tinted soft surface (default); `tertiary` is a neutral surface with bold feedback-tinted text. Inherited by all items unless overridden per-item.

feedback
"primary""neutral""success""warning""danger""inherit"

Semantic feedback color applied to each item's Panel background. Inherited by all items unless overridden per-item.

items
AccordionItemData[]

When provided, `<Accordion>` renders one `<AccordionItem>` per entry, applying root-level style props as defaults and letting each entry override them. Mutually exclusive with slot children (items wins, slot is ignored with a console warning in dev).

onValueChange
(value: AccordionItemValue[]) => void

Callback fired whenever the expanded set changes.

padding
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""7xl""8xl""4xs""3xs""2xs""9xl""10xl""11xl"

Internal padding for each item's Panel. Inherited by all items unless overridden per-item.

passThrough
AccordionPassThrough< RootElementAttributes, ItemElementAttributes, TriggerElementAttributes, IndicatorElementAttributes, ContentElementAttributes, HeaderElementAttributes, HeadingElementAttributes, ActionsElementAttributes, ContentInnerElementAttributes, ContentBodyElementAttributes >

Per-slot style and attribute override bag. Applies to all items.

radius
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for each item's Panel. Inherited by all items unless overridden per-item.

radiusBottom
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for the bottom corners of each item's Panel.

radiusLeft
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for the left corners of each item's Panel.

radiusRight
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for the right corners of each item's Panel.

radiusTop
"sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner""inherit"

Border radius for the top corners of each item's Panel.

size
"sm""md""lg"
default "md"

Controls trigger padding, content padding, and font size for all items.

type
"single""multiple"
default "single"

Expansion behavior. `single` allows one item open at a time and collapses siblings; `multiple` allows any number of items to be open simultaneously.

value
AccordionItemValue[]

Controlled list of expanded item values. Bind this prop to track or set expansion state externally.

AccordionItem
disabled
boolean
default false

Disable this individual item, preventing interaction.

open
boolean
default false

Whether the item is open. Drives `aria-expanded`, `data-state`, and the collapsed-panel `hidden="until-found"` (which keeps the body findable by the browser's find-in-page). In Svelte this is controlled via the root accordion's `value` prop.

passThrough
{ item?: AccordionSlotPassThrough<ItemElementAttributes>; header?: AccordionSlotPassThrough<HeaderElementAttributes>; heading?: AccordionSlotPassThrough<HeadingElementAttributes>; trigger?: AccordionSlotPassThrough<TriggerElementAttributes>; indicator?: AccordionSlotPassThrough<IndicatorElementAttributes>; actionsLeading?: AccordionSlotPassThrough<ActionsElementAttributes>; actionsTrailing?: AccordionSlotPassThrough<ActionsElementAttributes>; content?: AccordionSlotPassThrough<ContentElementAttributes>; contentInner?: AccordionSlotPassThrough<ContentInnerElementAttributes>; contentBody?: AccordionSlotPassThrough<ContentBodyElementAttributes>; }

Per-slot style and attribute override bag for this item. Takes precedence over the root accordion's `passThrough`.

value required
AccordionItemValue

Unique identifier for this item within the accordion. Used to track expansion state.

Type

  • Components
  • Blocks