Skip to content
Praesepe UI

disclosure

Accordion

Compound disclosure sections with single or multiple open items, ARIA expanded/controls pairing, and motion-safe height transitions.

Copied!

Examples

Single type

Only one panel open at a time.

Copied!

Ships within 2 business days across Indonesia.

Start a return within 14 days of delivery.

Email support replies within one business day.
<x-ui.accordion type="single" default="shipping">
    <x-ui.accordion.item id="shipping" title="Shipping">
        Ships within 2 business days across Indonesia.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="returns" title="Returns">
        Start a return within 14 days of delivery.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="support" title="Support">
        Email support replies within one business day.
    </x-ui.accordion.item>
</x-ui.accordion>
<div class="divide-y divide-border rounded-card border border-border bg-surface-raised" x-data="{ openId: 'shipping', toggle(id) { this.openId = this.openId === id ? null : id }, isOpen(id) { return this.openId === id } }">
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex w-full items-center justify-between py-4 text-left text-sm font-medium text-fg" x-on:click="toggle('shipping')" x-bind:aria-expanded="isOpen('shipping').toString()" aria-controls="shipping-panel" id="shipping-header">
                <span>Shipping</span>
            </button>
        </h3>
        <div id="shipping-panel" role="region" aria-labelledby="shipping-header" class="grid transition-[grid-template-rows] duration-200" x-bind:class="isOpen('shipping') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted">Ships within 2 business days across Indonesia.</p>
            </div>
        </div>
    </div>
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex w-full items-center justify-between py-4 text-left text-sm font-medium text-fg" x-on:click="toggle('returns')" x-bind:aria-expanded="isOpen('returns').toString()" aria-controls="returns-panel" id="returns-header">
                <span>Returns</span>
            </button>
        </h3>
        <div id="returns-panel" role="region" aria-labelledby="returns-header" class="grid transition-[grid-template-rows] duration-200" x-bind:class="isOpen('returns') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted">Start a return within 14 days of delivery.</p>
            </div>
        </div>
    </div>
</div>

Multiple type

Several panels can stay open together.

Copied!

Generate personal access tokens from Settings.

Subscribe to deploy and billing events.

Free workspaces share a 60 request per minute budget.
<x-ui.accordion type="multiple" :default="['api', 'webhooks']">
    <x-ui.accordion.item id="api" title="API access">
        Generate personal access tokens from Settings.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="webhooks" title="Webhooks">
        Subscribe to deploy and billing events.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="limits" title="Rate limits">
        Free workspaces share a 60 request per minute budget.
    </x-ui.accordion.item>
</x-ui.accordion>
<div class="divide-y divide-border rounded-card border border-border bg-surface-raised" x-data="{ openIds: ['api', 'webhooks'], toggle(id) { this.openIds = this.openIds.includes(id) ? this.openIds.filter(v => v !== id) : [...this.openIds, id] }, isOpen(id) { return this.openIds.includes(id) } }">
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" id="api-header" class="flex w-full items-center justify-between py-4 text-left text-sm font-medium text-fg" x-on:click="toggle('api')" x-bind:aria-expanded="isOpen('api').toString()" aria-controls="api-panel">
                <span>API access</span>
            </button>
        </h3>
        <div id="api-panel" role="region" aria-labelledby="api-header" class="grid transition-[grid-template-rows] duration-200" x-bind:class="isOpen('api') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted">Generate personal access tokens from Settings.</p>
            </div>
        </div>
    </div>
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" id="webhooks-header" class="flex w-full items-center justify-between py-4 text-left text-sm font-medium text-fg" x-on:click="toggle('webhooks')" x-bind:aria-expanded="isOpen('webhooks').toString()" aria-controls="webhooks-panel">
                <span>Webhooks</span>
            </button>
        </h3>
        <div id="webhooks-panel" role="region" aria-labelledby="webhooks-header" class="grid transition-[grid-template-rows] duration-200" x-bind:class="isOpen('webhooks') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted">Subscribe to deploy and billing events.</p>
            </div>
        </div>
    </div>
</div>

Props API

Component props
Prop Type Default Description
type single|multiple single single keeps one panel open. multiple allows several panels open together.
default string|array|null null Initially open item id, or list of ids when type is multiple.
item.id string required Stable id for the accordion item.
item.title string required Header button label.

Accessibility notes

Architected with WCAG 2.2 AA patterns in mind. These notes document the exact keyboard and ARIA behavior for this component.

  • Header buttons expose x-bind:aria-expanded and aria-controls pointing at the panel id.
  • Panels use role="region" and aria-labelledby pointing back at the header button.
  • Keyboard: Tab moves between headers. Enter or Space toggles the focused header (native button behavior).
  • Height and opacity transitions respect prefers-reduced-motion via motion-reduce utilities and global reduced-motion overrides.

Built by Praesepe

Architecture & custom software partner

Praesepe uses these exact UI primitives to ship SME dashboards, inventory systems, invoicing desks, and internal tools faster, without rebuilding a design system for every client.

  • Same Blade + Tailwind 4 + Alpine 3 stack you see in this docs site
  • Opinionated for Indonesian SME ops: invoices, stock, roles, approvals
  • Architected with WCAG 2.2 AA patterns in mind for keyboard and ARIA

Talk to the studio

WhatsApp: TODO Email the studio Visit Praesepe