Pushes to main and release/* need one approving review and a green pipeline.
Previews are torn down 48 hours after the pull request closes.
Secrets are encrypted at rest and injected at build time only.
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).
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.
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/svelte-accordion.
Yes — every component exposes a passThrough prop for custom styles and HTML attributes on each slot.
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/svelte-accordion.
Yes — every component exposes a passThrough prop for custom styles and HTML attributes on each slot.
This item can be expanded and collapsed normally.
This item is not accessible when disabled.
This item is also available for interaction.
---
// GENERATED from packages/demos/accordion/variants.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Single</h3>
<Accordion type="single" collapsible>
<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/svelte-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>
<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/svelte-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>
<div>
<h3>Disabled Item</h3>
<Accordion type="single" collapsible>
<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>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 size uses tighter padding and a smaller font — suitable for dense layouts or secondary content areas.
Inherits size="sm" from the root — no need to repeat on each item.
Medium size is the default — balanced padding and font size for most use cases.
Inherits size="md" from the root.
Large size uses generous padding and a larger font — ideal for prominent content or marketing pages.
Inherits size="lg" from the root.
This item uses the root's size="sm".
This item sets its own size="lg", which overrides the root's sm.
Back to inheriting from the root.
---
// GENERATED from packages/demos/accordion/size.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Small</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Medium (default)</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Large</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Per-item override</h3>
<Accordion type="single" collapsible 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>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.
Rendered from a named snippet matching the item's value. Inherits size="md" and feedback="primary" from the root.
Same defaults as the first.
Also inherits from the root.
Uses root defaults.
Item sets size: "lg", which wins over the root's sm.
Item sets feedback: "danger", which wins over the root's neutral.
Set per item.
Configure sign-in options.
Manage database connectivity.
Configure object storage and CDN.
---
// GENERATED from packages/demos/accordion/items.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Items array with cascaded defaults</h3>
<Accordion
type="single"
collapsible
size="md"
feedback="primary"
items={[
{ value: "first", title: "First", open: true },
{ value: "second", title: "Second" },
{ value: "third", title: "Third" },
]}
>
<p slot="first">
Rendered from a named snippet matching the item's <code>value</code>.
Inherits <code>size="md"</code> and <code>feedback="primary"</code> from
the root.
</p>
<p slot="second">Same defaults as the first.</p>
<p slot="third">Also inherits from the root.</p>
</Accordion>
</div>
<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 },
]}
>
<p slot="default">Uses root defaults.</p>
<p slot="larger">
Item sets <code>size: "lg"</code>, which wins over the root's{" "}
<code>sm</code>.
</p>
<p slot="danger">
Item sets <code>feedback: "danger"</code>, which wins over the root's{" "}
<code>neutral</code>.
</p>
<p slot="disabled">Set per item.</p>
</Accordion>
</div>
<div>
<h3>With subtitles</h3>
<Accordion
type="single"
collapsible
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",
},
]}
>
<p slot="auth">Configure sign-in options.</p>
<p slot="db">Manage database connectivity.</p>
<p slot="storage">Configure object storage and CDN.</p>
</Accordion>
</div>
</div>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 is the default feedback color — use it for standard content without any special semantic meaning.
Each item inherits the feedback color and border from the root.
Primary feedback applies the brand color palette — use it to highlight important content or onboarding steps.
Each item inherits the feedback color and border from the root.
Success feedback conveys positive outcomes — use it for completed steps, confirmation flows, or resolved states.
Each item inherits the feedback color and border from the root.
Warning feedback draws attention to caution states — use it for deprecation notices, rate limits, or soft restrictions.
Each item inherits the feedback color and border from the root.
Danger feedback signals critical or destructive information — use it for breaking changes, error states, or irreversible actions.
Each item inherits the feedback color and border from the root.
---
// GENERATED from packages/demos/accordion/feedback.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Neutral</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Primary</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Success</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Warning</h3>
<Accordion type="single" collapsible 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>
<div>
<h3>Danger</h3>
<Accordion type="single" collapsible 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 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 emphasis fills each item with the feedback's accent surface and uses contrast text. Use it for the most prominent accordion sections.
The trigger and indicator read against the bold surface.
Secondary emphasis paints each item with the feedback's soft surface. This is the default — most accordions use it.
Trigger text picks up the feedback's accent color.
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.
The trigger and content read against neutral surface.
---
// GENERATED from packages/demos/accordion/emphasis.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<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>
<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>
<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>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.
Add a subtitle to provide secondary context below the title without losing the clickable trigger area.
Configure your database connection and migration settings here.
Set up file storage providers and CDN delivery options.
Use headingTrailing to place a badge, count, or status indicator inline with the title text.
Active pull requests waiting for review.
Recent deployment activity and logs.
Use headingLeading to prepend a Stamp icon directly beside the title text.
Notification preferences and delivery settings.
Two-factor authentication and session management.
General application preferences and defaults.
Use trailing to place content at the far right of the trigger row — outside the title/subtitle, before the expand indicator.
Current build cache status and invalidation controls.
---
// GENERATED from packages/demos/accordion/rich-trigger.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import Stamp from "@pindoba/astro-stamp";
import {
KeyRound,
Database,
HardDrive,
User,
Bell,
Shield,
Settings,
} 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",
})}
>
<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 size={16} />
</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 size={16} />
</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 size={16} />
</Stamp>
<p>Set up file storage providers and CDN delivery options.</p>
</AccordionItem>
</Accordion>
</div>
<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 <code>headingTrailing</code> 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>
<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 size={12} />
</Stamp>
<p>
Use <code>headingLeading</code> 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 size={12} />
</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 size={12} />
</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 size={12} />
</Stamp>
<p>General application preferences and defaults.</p>
</AccordionItem>
</Accordion>
</div>
<div>
<h3>Trailing</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 <code>trailing</code> 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>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:
leading, trailing, headingLeading, headingTrailing, subheadingLeading, subheadingTrailing): rendered inside the trigger button. Icons, badges, counts. No interactive children.actionsLeading, actionsTrailing): rendered next to the trigger. Buttons, menu triggers, drag handles.Use actionsTrailing for interactive controls that should sit next to the header without toggling the accordion when clicked.
Click the icon buttons — they don't toggle the accordion.
An ellipsis menu placed alongside the trigger.
Use actionsLeading for a drag handle (or any leading control). Activating it doesn't toggle the panel.
Layer settings for the background plate.
Layer settings for the foreground content.
Layer settings for the top overlay.
Three independent focus stops: handle, trigger, edit.
---
// GENERATED from packages/demos/accordion/header-actions.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import Button from "@pindoba/astro-button";
import { Pencil, Trash, Ellipsis, GripVertical } 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" })}>
<div>
<h3>Trailing actions</h3>
<p>
Use <code>actionsTrailing</code> for interactive controls that should sit
next to the header without toggling the accordion when clicked.
</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 profile"
>
<Pencil size={14} />
</Button>
<Button
emphasis="ghost"
size="sm"
shape="square"
feedback="danger"
aria-label="Delete profile"
>
<Trash size={14} />
</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 size={14} />
</Button>
<p>An ellipsis menu placed alongside the trigger.</p>
</AccordionItem>
</Accordion>
</div>
<div>
<h3>Leading drag handle</h3>
<p>
Use <code>actionsLeading</code> 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 size={16} />
</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 size={16} />
</button>
<Button
slot="actions-trailing"
emphasis="ghost"
size="sm"
shape="square"
aria-label="Edit Foreground"
>
<Pencil size={14} />
</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 size={16} />
</button>
<p>Layer settings for the top overlay.</p>
</AccordionItem>
</Accordion>
</div>
<div>
<h3>Leading + trailing together</h3>
<Accordion type="single" collapsible>
<AccordionItem
value="task-1"
title="Set up CI"
subtitle="GitHub Actions and Turbo cache"
>
<button
slot="actions-leading"
type="button"
class={handle}
aria-label="Reorder task"
>
<GripVertical size={16} />
</button>
<Button
slot="actions-trailing"
emphasis="ghost"
size="sm"
shape="square"
aria-label="Edit"
>
<Pencil size={14} />
</Button>
<p>Three independent focus stops: handle, trigger, edit.</p>
</AccordionItem>
</Accordion>
</div>
</div>Pass a card object to frame the whole group in a Card: the group heading, subheading, eyebrow, leading and trailing actions go in the card header, an optional footer sits under the items, and the items become the card’s content. Items never become cards themselves — each keeps its disclosure pattern (<hN> → <button aria-expanded aria-controls> + role="region" panel).
Inside the card the items dissolve into flush rows: no item corners, ring or fill (the card’s surface shows through), a hairline between rows, and the open body reads as part of its row. The content is edge to edge (content.padding defaults to "none"), and a flush card lines its header text up with the row titles. Add contentBackground (or content.background) to put the rows on an inset panel over a shell — the rows then clip to the panel’s radii.inner.* corner.
In card mode the accordion-level size, background, border, radius and feedback become the card’s defaults (a card with only a heading looks like one big item panel); anything set on card wins. passThrough.card reaches the Card (its root, header, content, footer…); the card’s own passThrough wins per slot. Rich header/footer content is a snippet (Svelte), a node (React/Vue) or a card-* slot (Astro — card-trailing, card-footer-trailing, …).
---
// GENERATED from packages/demos/accordion/card.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
const releaseSteps = [
{ value: "freeze", title: "Freeze the release branch", open: true },
{ value: "notes", title: "Draft the release notes" },
{ value: "rollout", title: "Roll out to 10% of regions" },
];
---
<div class={stack({ gap: "xl", direction: "column" })}>
<Accordion
itemHeadingLevel={4}
card={{
heading: "Deployment settings",
subheading: "Applies to every environment in this project",
footer: {
leading: "Last changed by the platform team",
},
}}
>
<Button slot="card-trailing" size="sm" emphasis="secondary">
New rule
</Button>
<Button slot="card-footer-trailing" size="sm" emphasis="ghost">
Read the docs
</Button>
<AccordionItem
value="branches"
title="Protected branches"
open
headingLevel={4}
>
<p>
Pushes to <code>main</code> and <code>release/*</code> need one
approving review and a green pipeline.
</p>
</AccordionItem>
<AccordionItem
value="previews"
title="Preview deployments"
subtitle="Every pull request"
headingLevel={4}
>
<p>Previews are torn down 48 hours after the pull request closes.</p>
</AccordionItem>
<AccordionItem value="secrets" title="Environment secrets" headingLevel={4}>
<p>Secrets are encrypted at rest and injected at build time only.</p>
</AccordionItem>
</Accordion>
<Accordion
items={releaseSteps}
itemHeadingLevel={4}
card={{
heading: "Release checklist",
subheading: "v4.2 · 3 steps",
background: "surface.base",
border: "none",
contentBackground: "surface.peak",
footer: {},
}}
>
<p slot="freeze">
Cut <code>release/4.2</code> from main and lock merges to it.
</p>
<p slot="notes">
Group merged pull requests by label; call out breaking changes.
</p>
<p slot="rollout">
Watch error rates for an hour before widening the rollout.
</p>
<Button slot="card-footer-trailing" size="sm" emphasis="primary">
Start release
</Button>
</Accordion>
</div>The card heading renders as an <h3>, and so do the item headings by default. Set the two levels together so the group heading sits where it belongs in your page outline and the items nest one level below it: card={{ heading: "Billing", header: { headingLevel: 2 } }} with itemHeadingLevel={3} (any level from 1 to 6 works for the group heading and 2 to 6 for the items; headingLevel: "none" renders the group heading as a plain <span>). An item’s own headingLevel wins. In Astro, slot-composed <AccordionItem>s can’t read the root’s props — set headingLevel on each item (the items array does honour itemHeadingLevel).
Without card (or with card={false}) the accordion renders exactly as before — separate item panels, no Card. Row styling only applies in card mode: per-item surface props (background, border, radius) are for the bare stack; inside a card, style the group through card. Avoid emphasis="primary" items and a primary card inside a card-framed accordion — the rows take the card’s surface, so a filled item’s contrast text would sit on the wrong fill.
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>.
Use passThrough.trigger.style on an item to restyle its trigger — here applying a monospace font for a file-tree appearance.
The style is applied via Panda CSS css.raw(), giving you full access to design tokens and responsive utilities.
Use passThrough.content.style on an item to customize its content area — here adding a bold top border using the current feedback color palette.
Each item's content slot gets the same custom styles.
Use passThrough.root.props on the accordion to forward HTML attributes to the root element — here adding role="region" and an aria-label for screen reader context.
passThrough is the escape hatch for any customization not covered by component props. It keeps the public API clean while remaining fully flexible.
---
// GENERATED from packages/demos/accordion/custom.demo.tsx — edit the source, not this file.
import Accordion from "@pindoba/astro-accordion";
import { AccordionItem } from "@pindoba/astro-accordion";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";
---
<div
class={stack({
gap: "xl",
direction: "column",
})}
>
<div>
<h3>Custom Trigger Style</h3>
<Accordion type="single" collapsible>
<AccordionItem
value="item-1"
title="src/index.ts"
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.svelte"
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>
<div>
<h3>Custom Content Style</h3>
<Accordion type="single" collapsible feedback="primary">
<AccordionItem
value="item-1"
title="Separated content area"
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>
<div>
<h3>ARIA Attributes</h3>
<Accordion
type="single"
collapsible
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>