Skip to content
Praesepe UI

Kitchen sink

Dogfood every registered component in one place. Use the theme switcher, dark mode toggle, and each preview canvas control.

Accordion

Docs

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

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>

Alert

Docs

Static inline banner for info, success, warning, and danger messages with optional dismiss control.

Variants

info, success, warning, and danger banners.

Copied!

Heads up

Your trial renews in 3 days.

Saved

Project settings were updated.

Action needed

Verify your domain before go-live.
<div class="space-y-3">
    <x-ui.alert variant="info" title="Heads up">Your trial renews in 3 days.</x-ui.alert>
    <x-ui.alert variant="success" title="Saved">Project settings were updated.</x-ui.alert>
    <x-ui.alert variant="warning" title="Action needed">Verify your domain before go-live.</x-ui.alert>
    <x-ui.alert variant="danger" title="Deploy failed">The build exited with a non-zero status.</x-ui.alert>
</div>
<div class="space-y-3">
    <div class="flex gap-3 rounded-card border border-info/30 bg-info-soft p-4" role="status">
        <span class="text-info" aria-hidden="true">i</span>
        <div>
            <p class="text-sm font-semibold text-fg">Heads up</p>
            <p class="mt-1 text-sm text-fg-muted">Your trial renews in 3 days.</p>
        </div>
    </div>
    <div class="flex gap-3 rounded-card border border-success/30 bg-success-soft p-4" role="status">
        <span class="text-success" aria-hidden="true">✓</span>
        <div>
            <p class="text-sm font-semibold text-fg">Saved</p>
            <p class="mt-1 text-sm text-fg-muted">Project settings were updated.</p>
        </div>
    </div>
    <div class="flex gap-3 rounded-card border border-warning/30 bg-warning-soft p-4" role="status">
        <span class="text-warning" aria-hidden="true">!</span>
        <div>
            <p class="text-sm font-semibold text-fg">Action needed</p>
            <p class="mt-1 text-sm text-fg-muted">Verify your domain before go-live.</p>
        </div>
    </div>
    <div class="flex gap-3 rounded-card border border-danger/30 bg-danger-soft p-4" role="alert">
        <span class="text-danger" aria-hidden="true">!</span>
        <div>
            <p class="text-sm font-semibold text-fg">Deploy failed</p>
            <p class="mt-1 text-sm text-fg-muted">The build exited with a non-zero status.</p>
        </div>
    </div>
</div>

Dismissible

Optional dismiss button with Alpine show state.

Copied!

New docs theme

Try the warm theme from the header switcher.
<x-ui.alert variant="info" title="New docs theme" dismissible>
    Try the warm theme from the header switcher.
</x-ui.alert>
<div
    class="flex gap-3 rounded-card border border-info/30 bg-info-soft p-4"
    role="status"
    x-data="{ show: true }"
    x-show="show"
>
    <div class="min-w-0 flex-1">
        <p class="text-sm font-semibold text-fg">New docs theme</p>
        <p class="mt-1 text-sm text-fg-muted">Try the warm theme from the header switcher.</p>
    </div>
    <button type="button" class="rounded-control p-1 text-fg-muted" x-on:click="show = false" aria-label="Dismiss alert">×</button>
</div>

Avatar

Docs

User and customer avatars with image error fallback to initials or icon, status dots, and overlapping group stacks.

Customer avatars

Initials fallback and status indicators for SME staff.

Copied!
SR BS AL
<div class="flex items-center gap-4">
    <x-ui.avatar name="Siti Rahma" status="online" />
    <x-ui.avatar name="Budi Santoso" status="busy" />
    <x-ui.avatar name="Ayu Lestari" status="offline" />
</div>
<div class="flex items-center gap-4">
    <span class="relative inline-flex">
        <span class="inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium text-fg ring-2 ring-surface" role="img" aria-label="Siti Rahma">SR</span>
        <span class="absolute right-0 bottom-0 size-2.5 rounded-full bg-success ring-2 ring-surface" title="online"></span>
    </span>
    <span class="relative inline-flex">
        <span class="inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium text-fg ring-2 ring-surface" role="img" aria-label="Budi Santoso">BS</span>
        <span class="absolute right-0 bottom-0 size-2.5 rounded-full bg-warning ring-2 ring-surface" title="busy"></span>
    </span>
    <span class="relative inline-flex">
        <span class="inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium text-fg ring-2 ring-surface" role="img" aria-label="Ayu Lestari">AL</span>
        <span class="absolute right-0 bottom-0 size-2.5 rounded-full bg-fg-subtle ring-2 ring-surface" title="offline"></span>
    </span>
</div>

Sizes

xs through xl density scale.

Copied!
XD SD MD LD XD
<div class="flex items-end gap-3">
    <x-ui.avatar size="xs" name="XS Demo" />
    <x-ui.avatar size="sm" name="SM Demo" />
    <x-ui.avatar size="md" name="MD Demo" />
    <x-ui.avatar size="lg" name="LG Demo" />
    <x-ui.avatar size="xl" name="XL Demo" />
</div>
<div class="flex items-end gap-3">
    <span class="inline-flex size-6 items-center justify-center rounded-full bg-surface-sunken text-[10px] font-medium ring-2 ring-surface">XS</span>
    <span class="inline-flex size-8 items-center justify-center rounded-full bg-surface-sunken text-xs font-medium ring-2 ring-surface">SM</span>
    <span class="inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface">MD</span>
    <span class="inline-flex size-12 items-center justify-center rounded-full bg-surface-sunken text-base font-medium ring-2 ring-surface">LG</span>
    <span class="inline-flex size-16 items-center justify-center rounded-full bg-surface-sunken text-lg font-medium ring-2 ring-surface">XL</span>
</div>

Avatar group

Overlapping stack with +N overflow for assigned agents.

Copied!
SR BS AL +3
<x-ui.avatar.group>
    <x-ui.avatar name="Siti Rahma" class="z-[1]" />
    <x-ui.avatar name="Budi Santoso" class="z-[2]" />
    <x-ui.avatar name="Ayu Lestari" class="z-[3]" />
    <x-slot:overflow>+3</x-slot:overflow>
</x-ui.avatar.group>
<div class="flex items-center -space-x-2">
    <span class="relative z-[1] inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface">SR</span>
    <span class="relative z-[2] inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface">BS</span>
    <span class="relative z-[3] inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface">AL</span>
    <span class="relative z-[4] inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-xs font-medium ring-2 ring-surface">+3</span>
</div>

Badge

Docs

Compact status labels with solid, soft, and outline tones, optional status dots, and removable actions.

Invoice statuses

Indonesian SME invoice states using soft badges with dots.

Copied!
Paid · INV-2026-089 Awaiting payment Overdue Draft
<div class="flex flex-wrap gap-2">
    <x-ui.badge variant="soft" color="success" dot>Paid · INV-2026-089</x-ui.badge>
    <x-ui.badge variant="soft" color="warning" dot>Awaiting payment</x-ui.badge>
    <x-ui.badge variant="soft" color="danger" dot>Overdue</x-ui.badge>
    <x-ui.badge variant="soft" color="info" dot>Draft</x-ui.badge>
</div>
<div class="flex flex-wrap gap-2">
    <span class="inline-flex items-center gap-1.5 rounded-pill bg-success-soft px-2 py-0.5 text-xs font-medium tabular-nums text-success shadow-2xs">
        <span class="size-1.5 rounded-full bg-success" aria-hidden="true"></span>
        Paid · INV-2026-089
    </span>
    <span class="inline-flex items-center gap-1.5 rounded-pill bg-warning-soft px-2 py-0.5 text-xs font-medium tabular-nums text-warning shadow-2xs">
        <span class="size-1.5 rounded-full bg-warning" aria-hidden="true"></span>
        Awaiting payment
    </span>
    <span class="inline-flex items-center gap-1.5 rounded-pill bg-danger-soft px-2 py-0.5 text-xs font-medium tabular-nums text-danger shadow-2xs">
        <span class="size-1.5 rounded-full bg-danger" aria-hidden="true"></span>
        Overdue
    </span>
    <span class="inline-flex items-center gap-1.5 rounded-pill bg-info-soft px-2 py-0.5 text-xs font-medium tabular-nums text-info shadow-2xs">
        <span class="size-1.5 rounded-full bg-info" aria-hidden="true"></span>
        Draft
    </span>
</div>

Variants and colors

Solid, soft, and outline across semantic colors.

Copied!
Brand Success Danger Neutral
Small Medium
<div class="space-y-3">
    <div class="flex flex-wrap gap-2">
        <x-ui.badge variant="solid" color="brand">Brand</x-ui.badge>
        <x-ui.badge variant="solid" color="success">Success</x-ui.badge>
        <x-ui.badge variant="outline" color="danger">Danger</x-ui.badge>
        <x-ui.badge variant="soft" color="neutral">Neutral</x-ui.badge>
    </div>
    <div class="flex flex-wrap gap-2">
        <x-ui.badge size="sm" color="info">Small</x-ui.badge>
        <x-ui.badge size="md" color="info">Medium</x-ui.badge>
    </div>
</div>
<div class="space-y-3">
    <div class="flex flex-wrap gap-2">
        <span class="inline-flex rounded-pill bg-brand px-2 py-0.5 text-xs font-medium text-on-brand">Brand</span>
        <span class="inline-flex rounded-pill bg-success px-2 py-0.5 text-xs font-medium text-on-success">Success</span>
        <span class="inline-flex rounded-pill border border-danger bg-transparent px-2 py-0.5 text-xs font-medium text-danger">Danger</span>
    </div>
    <div class="flex flex-wrap gap-2">
        <span class="inline-flex rounded-pill bg-surface-sunken px-1.5 py-0.5 text-[11px] font-medium text-fg">Small</span>
        <span class="inline-flex rounded-pill bg-surface-sunken px-2 py-0.5 text-xs font-medium text-fg">Medium</span>
    </div>
</div>

Removable filter chip

Dismissible badge for active filters.

Copied!
Cabang Bandung
<div x-data="{ show: true }">
    <span x-show="show">
        <x-ui.badge variant="soft" color="brand" removable x-on:click="show = false">
            Cabang Bandung
        </x-ui.badge>
    </span>
</div>
<div x-data="{ show: true }">
    <span
        x-show="show"
        class="inline-flex items-center gap-1.5 rounded-pill bg-brand/10 px-2 py-0.5 text-xs font-medium text-brand"
    >
        Cabang Bandung
        <button type="button" class="rounded-full p-0.5" aria-label="Remove" x-on:click="show = false">×</button>
    </span>
</div>

Breadcrumb

Docs

Accessible breadcrumb navigation with optional home icon, chevron separators, and aria-current on the final page.

App path

Orders section breadcrumb for an SME back office.

Copied!
<x-ui.breadcrumb>
    <x-ui.breadcrumb.item href="/docs">Penjualan</x-ui.breadcrumb.item>
    <x-ui.breadcrumb.item href="/docs/components">Pesanan</x-ui.breadcrumb.item>
    <x-ui.breadcrumb.item current>ORD-2201</x-ui.breadcrumb.item>
</x-ui.breadcrumb>
<nav aria-label="Breadcrumb" class="text-sm">
    <ol class="flex flex-wrap items-center gap-1.5 text-fg-muted">
        <li class="inline-flex items-center gap-1.5">
            <a href="/docs" class="hover:text-fg">Penjualan</a>
            <span aria-hidden="true">/</span>
        </li>
        <li class="inline-flex items-center gap-1.5">
            <a href="/docs/components" class="hover:text-fg">Pesanan</a>
            <span aria-hidden="true">/</span>
        </li>
        <li><span class="font-medium text-fg" aria-current="page">ORD-2201</span></li>
    </ol>
</nav>

With home icon

Leading home control before the trail.

Copied!
<x-ui.breadcrumb home-href="/" home-label="Beranda">
    <x-ui.breadcrumb.item href="/docs">Inventori</x-ui.breadcrumb.item>
    <x-ui.breadcrumb.item current>Stok gudang</x-ui.breadcrumb.item>
</x-ui.breadcrumb>
<nav aria-label="Breadcrumb" class="text-sm">
    <ol class="flex flex-wrap items-center gap-1.5 text-fg-muted">
        <li class="inline-flex items-center gap-1.5">
            <a href="/" class="hover:text-fg"><span class="sr-only">Home</span>⌂</a>
            <span aria-hidden="true">/</span>
        </li>
        <li class="inline-flex items-center gap-1.5">
            <a href="/docs" class="hover:text-fg">Inventori</a>
            <span aria-hidden="true">/</span>
        </li>
        <li><span class="font-medium text-fg" aria-current="page">Stok gudang</span></li>
    </ol>
</nav>

Button

Docs

Polymorphic button and link control with variants, sizes, loading, and disabled states.

Variants

Primary, secondary, outline, ghost, danger, and link styles.

Copied!
<div class="flex flex-wrap items-center gap-3">
    <x-ui.button variant="primary">Save invoice</x-ui.button>
    <x-ui.button variant="secondary">Draft</x-ui.button>
    <x-ui.button variant="outline">Export CSV</x-ui.button>
    <x-ui.button variant="ghost">Cancel</x-ui.button>
    <x-ui.button variant="danger">Void invoice</x-ui.button>
    <x-ui.button variant="link">View ledger</x-ui.button>
</div>
<div class="flex flex-wrap items-center gap-3">
    <button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Save invoice
    </button>
    <button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border bg-surface-sunken px-3.5 text-sm font-medium text-fg shadow-2xs transition duration-150 hover:bg-border/60 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Draft
    </button>
    <button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border-strong bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Export CSV
    </button>
    <button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken [&_svg]:shrink-0">
        Cancel
    </button>
    <button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-danger px-3.5 text-sm font-medium text-on-danger shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Void invoice
    </button>
    <button type="button" class="inline-flex items-center justify-center font-medium text-brand underline-offset-4 hover:underline">
        View ledger
    </button>
</div>

Sizes

Compact through large, plus icon-only sizes.

Copied!
<div class="flex flex-wrap items-center gap-3">
    <x-ui.button size="sm">Small</x-ui.button>
    <x-ui.button size="md">Medium</x-ui.button>
    <x-ui.button size="lg">Large</x-ui.button>
    <x-ui.button size="icon-sm" aria-label="Icon small">
        <x-icons.check class="size-4" />
    </x-ui.button>
</div>
<div class="flex flex-wrap items-center gap-3">
    <button type="button" class="inline-flex h-8 items-center justify-center gap-1.5 rounded-control bg-brand px-2.5 text-xs font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Small
    </button>
    <button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Medium
    </button>
    <button type="button" class="inline-flex h-10 items-center justify-center gap-2 rounded-control bg-brand px-4 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
        Large
    </button>
    <button type="button" aria-label="Mark as paid" class="inline-flex size-9 items-center justify-center rounded-control bg-brand text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px]">
        <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" d="M5 12.5 9.5 17 19 7" />
        </svg>
    </button>
</div>

States

Disabled, loading, and polymorphic link usage.

Copied!
Open docs
<div class="flex flex-wrap items-center gap-3">
    <x-ui.button disabled>Syncing stock</x-ui.button>
    <x-ui.button loading>Approving</x-ui.button>
    <x-ui.button variant="outline" href="/docs">Open docs</x-ui.button>
</div>
<div class="flex flex-wrap items-center gap-3">
    <button type="button" disabled class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand opacity-50 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] [&_svg]:shrink-0">
        Syncing stock
    </button>
    <button type="button" aria-busy="true" disabled class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand opacity-50 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] [&_svg]:shrink-0">
        <svg class="size-4 shrink-0 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" d="M12 3a9 9 0 1 0 9 9" />
        </svg>
        Approving
    </button>
    <a href="/docs" class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border-strong bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken [&_svg]:shrink-0">
        Open docs
    </a>
</div>

Card

Docs

Content container with default, sunken, and interactive variants using a stretched-link pattern for accessible click targets.

Order summary

Default card with header, body, and footer for an SME order.

Copied!

Invoice #INV-2026-089

Toko Sumber Makmur · Bandung

SubtotalRp 1.250.000
ShippingRp 18.000
Total dueRp 1.268.000

ETA: 2 business days · Paid via QRIS

<x-ui.card class="max-w-md">
    <x-slot:header>
        <p class="text-sm font-semibold tracking-tight text-fg">Invoice #INV-2026-089</p>
        <p class="text-xs text-fg-muted leading-relaxed">Toko Sumber Makmur · Bandung</p>
    </x-slot:header>

    <div class="space-y-2 text-sm text-fg">
        <div class="flex justify-between"><span class="text-fg-muted">Subtotal</span><span class="tabular-nums">Rp 1.250.000</span></div>
        <div class="flex justify-between"><span class="text-fg-muted">Shipping</span><span class="tabular-nums">Rp 18.000</span></div>
        <div class="flex justify-between font-medium"><span>Total due</span><span class="tabular-nums">Rp 1.268.000</span></div>
    </div>

    <x-slot:footer>
        <p class="text-sm text-fg-muted">ETA: 2 business days · Paid via QRIS</p>
    </x-slot:footer>
</x-ui.card>
<div class="max-w-md overflow-hidden rounded-card border border-border bg-surface-raised shadow-sm ring-1 ring-black/5">
    <div class="border-b border-border px-4 py-3">
        <p class="text-sm font-semibold tracking-tight text-fg">Invoice #INV-2026-089</p>
        <p class="text-xs text-fg-muted leading-relaxed">Toko Sumber Makmur · Bandung</p>
    </div>
    <div class="space-y-2 px-4 py-4 text-sm text-fg">
        <div class="flex justify-between"><span class="text-fg-muted">Subtotal</span><span class="tabular-nums">Rp 1.250.000</span></div>
        <div class="flex justify-between"><span class="text-fg-muted">Shipping</span><span class="tabular-nums">Rp 18.000</span></div>
        <div class="flex justify-between font-medium"><span>Total due</span><span class="tabular-nums">Rp 1.268.000</span></div>
    </div>
    <div class="border-t border-border px-4 py-3 text-sm text-fg-muted">ETA: 2 business days · Paid via QRIS</div>
</div>

Interactive customer card

Stretched-link card opening a customer profile.

Copied!
CV Berkah Jaya
Active customer · 18 open invoices · IDR
<x-ui.card variant="interactive" href="/docs/components/card" class="max-w-sm">
    <x-slot:title>CV Berkah Jaya</x-slot:title>
    Active customer · 18 open invoices · IDR

    <x-slot:footer>
        <x-ui.button variant="link" size="sm" class="relative z-10">Send payment reminder</x-ui.button>
    </x-slot:footer>
</x-ui.card>
<div class="relative max-w-sm overflow-hidden rounded-card border border-border bg-surface-raised shadow-sm ring-1 ring-black/5 transition-colors duration-150 hover:border-border-strong">
    <div class="px-4 py-4">
        <a href="/docs/components/card" class="font-semibold tracking-tight text-fg after:absolute after:inset-0 after:content-['']">CV Berkah Jaya</a>
        <p class="relative z-10 mt-2 text-sm text-fg-muted leading-relaxed">Active customer · 18 open invoices · IDR</p>
    </div>
    <div class="relative z-10 border-t border-border px-4 py-3">
        <button type="button" class="text-sm font-medium text-brand">Send payment reminder</button>
    </div>
</div>

Sunken metric

Low-emphasis sunken surface for dashboard metrics.

Copied!

Sales today

Rp 8.4M

+12% vs yesterday

<x-ui.card variant="sunken" class="max-w-xs">
    <p class="text-xs font-semibold uppercase tracking-wider text-fg-subtle">Sales today</p>
    <p class="mt-2 text-2xl font-semibold tracking-tight tabular-nums text-fg">Rp 8.4M</p>
    <p class="mt-1 text-sm tabular-nums text-success">+12% vs yesterday</p>
</x-ui.card>
<div class="max-w-xs rounded-card bg-surface-sunken p-4">
    <p class="text-xs font-semibold uppercase tracking-wider text-fg-subtle">Sales today</p>
    <p class="mt-2 text-2xl font-semibold tracking-tight tabular-nums text-fg">Rp 8.4M</p>
    <p class="mt-1 text-sm tabular-nums text-success">+12% vs yesterday</p>
</div>

Checkbox

Docs

Native checkbox with custom check and indeterminate indicators, label, description, error, and attribute forwarding.

Basic checkbox

Label and description with attribute forwarding.

Copied!

Occasional product notes. No spam.

<x-ui.checkbox
    name="marketing"
    value="1"
    label="Email updates"
    description="Occasional product notes. No spam."
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input
                id="marketing-basic"
                type="checkbox"
                name="marketing"
                value="1"
                aria-describedby="marketing-basic-description"
                class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0"
            >
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-[0.3rem] border border-border-strong bg-surface-raised text-on-brand shadow-sm peer-checked:border-brand peer-checked:bg-brand peer-checked:[&_svg]:opacity-100" aria-hidden="true">
                <svg class="size-3 opacity-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12.5 9.5 17 19 7" /></svg>
            </span>
        </div>
        <div>
            <label for="marketing-basic" class="block text-sm font-medium text-fg">Email updates</label>
            <p id="marketing-basic-description" class="mt-0.5 text-sm text-fg-muted">Occasional product notes. No spam.</p>
        </div>
    </div>
</div>

Indeterminate

Alpine x-init sets the native indeterminate flag.

Copied!

Some rows in this page are selected.

<x-ui.checkbox
    name="select_all"
    label="Select all rows"
    description="Some rows in this page are selected."
    indeterminate
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input
                id="select-all"
                type="checkbox"
                name="select_all"
                class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0"
                x-data
                x-init="$el.indeterminate = true"
            >
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-[0.3rem] border border-border-strong bg-surface-raised text-on-brand shadow-sm peer-indeterminate:border-brand peer-indeterminate:bg-brand peer-indeterminate:[&_svg]:opacity-100" aria-hidden="true">
                <svg class="size-3 opacity-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25"><path stroke-linecap="round" d="M6 12h12" /></svg>
            </span>
        </div>
        <div>
            <label for="select-all" class="block text-sm font-medium text-fg">Select all rows</label>
            <p class="mt-0.5 text-sm text-fg-muted">Some rows in this page are selected.</p>
        </div>
    </div>
</div>

Validation error

Error text linked through aria-describedby.

Copied!

You must accept the terms to continue.

<x-ui.checkbox
    name="terms"
    value="1"
    label="Accept terms"
    error="You must accept the terms to continue."
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input
                id="terms-error"
                type="checkbox"
                name="terms"
                value="1"
                aria-invalid="true"
                aria-describedby="terms-error-error"
                class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0"
            >
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-[0.3rem] border border-danger bg-surface-raised text-on-brand shadow-sm peer-checked:border-brand peer-checked:bg-brand peer-checked:[&_svg]:opacity-100" aria-hidden="true">
                <svg class="size-3 opacity-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12.5 9.5 17 19 7" /></svg>
            </span>
        </div>
        <div>
            <label for="terms-error" class="block text-sm font-medium text-fg">Accept terms</label>
        </div>
    </div>
    <p id="terms-error-error" class="mt-1.5 text-xs text-danger">You must accept the terms to continue.</p>
</div>

Dropdown

Docs

Compound menu with keyboard navigation, left/right alignment, danger and disabled items, icons, and shortcut badges.

Basic menu

Default items, separator, danger action, and shortcut badge.

Copied!
<x-ui.dropdown label="Actions">
    <x-ui.dropdown.item>Edit invoice</x-ui.dropdown.item>
    <x-ui.dropdown.item shortcut="⌘D">Duplicate</x-ui.dropdown.item>
    <x-ui.dropdown.separator />
    <x-ui.dropdown.item variant="danger">Void invoice</x-ui.dropdown.item>
</x-ui.dropdown>
<div
    class="relative inline-block text-left"
    x-data="{
        open: false,
        items() {
            return Array.from(this.$refs.menu.querySelectorAll('[role=menuitem]:not([disabled]):not([aria-disabled=true])'));
        },
        show() { this.open = true; this.$nextTick(() => this.items()[0]?.focus()); },
        hide() { this.open = false; this.$refs.trigger.focus(); },
        toggle() { this.open ? this.hide() : this.show(); },
        focusByOffset(offset) {
            const list = this.items();
            const current = list.indexOf(document.activeElement);
            const next = (current + offset + list.length) % list.length;
            list[next]?.focus();
        }
    }"
    x-on:click.outside="if (open) open = false"
    x-on:keydown.escape.window="if (open) hide()"
>
    <button
        type="button"
        x-ref="trigger"
        class="inline-flex h-9 items-center gap-2 rounded-control border border-border bg-surface-raised px-3.5 text-sm font-medium text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong hover:bg-surface-sunken"
        aria-haspopup="menu"
        x-bind:aria-expanded="open.toString()"
        x-on:click="toggle()"
    >
        Actions
        <svg class="size-4 shrink-0 text-fg-subtle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
        </svg>
    </button>
    <div
        x-ref="menu"
        role="menu"
        x-show="open"
        x-cloak
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="opacity-0 scale-95 translate-y-1"
        x-transition:enter-end="opacity-100 scale-100 translate-y-0"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="opacity-100 scale-100 translate-y-0"
        x-transition:leave-end="opacity-0 scale-95 translate-y-1"
        class="absolute left-0 z-40 mt-2 min-w-48 origin-top-left rounded-card border border-border bg-surface-raised p-1 shadow-lg ring-1 ring-black/5"
        x-on:keydown.arrow-down.prevent="focusByOffset(1)"
        x-on:keydown.arrow-up.prevent="focusByOffset(-1)"
    >
        <button type="button" role="menuitem" tabindex="-1" class="flex w-full rounded-control px-3 py-2 text-left text-sm text-fg hover:bg-surface-sunken" x-on:click="hide()">Edit invoice</button>
        <button type="button" role="menuitem" tabindex="-1" class="flex w-full items-center justify-between rounded-control px-3 py-2 text-left text-sm text-fg hover:bg-surface-sunken" x-on:click="hide()">
            <span>Duplicate</span>
            <kbd class="rounded border border-border px-1.5 py-0.5 text-[10px] tabular-nums text-fg-subtle">⌘D</kbd>
        </button>
        <div role="separator" class="my-1 h-px bg-border"></div>
        <button type="button" role="menuitem" tabindex="-1" class="flex w-full rounded-control px-3 py-2 text-left text-sm text-danger hover:bg-danger-soft" x-on:click="hide()">Void invoice</button>
    </div>
</div>

Right aligned

Menu anchored to the right edge of the trigger.

Copied!
<div class="flex justify-end">
    <x-ui.dropdown align="right" label="Account">
        <x-ui.dropdown.item>Profile · Siti Rahma</x-ui.dropdown.item>
        <x-ui.dropdown.item>Billing · IDR</x-ui.dropdown.item>
    </x-ui.dropdown>
</div>
<div class="flex justify-end">
    <div
        class="relative inline-block text-left"
        x-data="{ open: false }"
        x-on:click.outside="open = false"
    >
        <button
            type="button"
            class="inline-flex h-9 items-center gap-2 rounded-control border border-border bg-surface-raised px-3.5 text-sm font-medium text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong hover:bg-surface-sunken"
            aria-haspopup="menu"
            x-bind:aria-expanded="open.toString()"
            x-on:click="open = !open"
        >
            Account
        </button>
        <div
            role="menu"
            x-show="open"
            x-cloak
            x-transition:enter="transition ease-out duration-150"
            x-transition:enter-start="opacity-0 scale-95 translate-y-1"
            x-transition:enter-end="opacity-100 scale-100 translate-y-0"
            x-transition:leave="transition ease-in duration-100"
            x-transition:leave-start="opacity-100 scale-100 translate-y-0"
            x-transition:leave-end="opacity-0 scale-95 translate-y-1"
            class="absolute right-0 z-40 mt-2 min-w-48 origin-top-right rounded-card border border-border bg-surface-raised p-1 shadow-lg ring-1 ring-black/5"
        >
            <button type="button" role="menuitem" tabindex="-1" class="flex w-full rounded-control px-3 py-2 text-left text-sm text-fg hover:bg-surface-sunken" x-on:click="open = false">Profile · Siti Rahma</button>
            <button type="button" role="menuitem" tabindex="-1" class="flex w-full rounded-control px-3 py-2 text-left text-sm text-fg hover:bg-surface-sunken" x-on:click="open = false">Billing · IDR</button>
        </div>
    </div>
</div>

Disabled item

Disabled menuitem is skipped by arrow key navigation.

Copied!
<x-ui.dropdown label="More">
    <x-ui.dropdown.item>Share link</x-ui.dropdown.item>
    <x-ui.dropdown.item variant="disabled">Archive (needs Owner)</x-ui.dropdown.item>
    <x-ui.dropdown.item>Export CSV</x-ui.dropdown.item>
</x-ui.dropdown>
<div
    class="relative inline-block text-left"
    x-data="{ open: false }"
    x-on:click.outside="open = false"
>
    <button
        type="button"
        class="inline-flex h-9 items-center gap-2 rounded-control border border-border bg-surface-raised px-3.5 text-sm font-medium text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong hover:bg-surface-sunken"
        aria-haspopup="menu"
        x-bind:aria-expanded="open.toString()"
        x-on:click="open = !open"
    >
        More
    </button>
    <div
        role="menu"
        x-show="open"
        x-cloak
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="opacity-0 scale-95 translate-y-1"
        x-transition:enter-end="opacity-100 scale-100 translate-y-0"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="opacity-100 scale-100 translate-y-0"
        x-transition:leave-end="opacity-0 scale-95 translate-y-1"
        class="absolute left-0 z-40 mt-2 min-w-48 origin-top-left rounded-card border border-border bg-surface-raised p-1 shadow-lg ring-1 ring-black/5"
    >
        <button type="button" role="menuitem" tabindex="-1" class="flex w-full rounded-control px-3 py-2 text-left text-sm text-fg hover:bg-surface-sunken" x-on:click="open = false">Share link</button>
        <button type="button" role="menuitem" tabindex="-1" disabled aria-disabled="true" class="flex w-full cursor-not-allowed rounded-control px-3 py-2 text-left text-sm text-fg-subtle opacity-60">Archive (needs Owner)</button>
        <button type="button" role="menuitem" tabindex="-1" class="flex w-full rounded-control px-3 py-2 text-left text-sm text-fg hover:bg-surface-sunken" x-on:click="open = false">Export CSV</button>
    </div>
</div>

Input

Docs

Form control archetype with label, hint, error, icons, password toggle, and attribute forwarding to the native input.

Basic field

Label, hint, and placeholder with attribute forwarding.

Copied!

Used for INV-2026-089 and payment receipts.

<x-ui.input
    type="email"
    name="billing_email"
    label="Billing email"
    hint="Used for INV-2026-089 and payment receipts."
    placeholder="finance@tokomaju.id"
    required
    class="max-w-sm"
/>
<div class="w-full max-w-sm">
    <label for="invoice-email" class="mb-1.5 block text-sm font-medium text-fg">Billing email</label>
    <div class="relative">
        <input
            id="invoice-email"
            type="email"
            name="billing_email"
            placeholder="finance@tokomaju.id"
            required
            aria-describedby="invoice-email-hint"
            class="h-9 w-full rounded-control border border-border bg-surface-raised px-3.5 text-sm text-fg shadow-2xs tabular-nums placeholder:text-fg-subtle transition-colors duration-150 hover:border-border-strong focus:border-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        >
    </div>
    <p id="invoice-email-hint" class="mt-1.5 text-xs text-fg-muted leading-relaxed">Used for INV-2026-089 and payment receipts.</p>
</div>

Password with toggle

Accessible show and hide control.

Copied!
<x-ui.input
    type="password"
    name="password"
    label="Owner password"
    placeholder="Enter password"
    required
    class="max-w-sm"
/>
<div class="w-full max-w-sm" x-data="{ show: false }">
    <label for="owner-password" class="mb-1.5 block text-sm font-medium text-fg">Owner password</label>
    <div class="relative">
        <input
            id="owner-password"
            name="password"
            placeholder="Enter password"
            required
            x-bind:type="show ? 'text' : 'password'"
            class="h-9 w-full rounded-control border border-border bg-surface-raised px-3.5 pr-10 text-sm text-fg shadow-2xs placeholder:text-fg-subtle transition-colors duration-150 hover:border-border-strong focus:border-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        >
        <button
            type="button"
            class="absolute inset-y-0 right-0 flex items-center px-3 text-fg-muted hover:text-fg"
            x-on:click="show = !show"
            x-bind:aria-label="show ? 'Hide password' : 'Show password'"
        >
            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
                <path stroke-linecap="round" stroke-linejoin="round" d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" />
                <circle cx="12" cy="12" r="3" />
            </svg>
        </button>
    </div>
</div>

Validation error

Error text linked through aria-describedby.

Copied!

Enter a valid 15-digit NPWP for e-Faktur.

<x-ui.input
    type="text"
    name="npwp"
    label="NPWP"
    value="10.20.30"
    error="Enter a valid 15-digit NPWP for e-Faktur."
    class="max-w-sm"
/>
<div class="w-full max-w-sm">
    <label for="npwp-error" class="mb-1.5 block text-sm font-medium text-fg">NPWP</label>
    <div class="relative">
        <input
            id="npwp-error"
            type="text"
            name="npwp"
            value="10.20.30"
            aria-invalid="true"
            aria-describedby="npwp-error-msg"
            class="h-9 w-full rounded-control border border-danger bg-surface-raised px-3.5 text-sm text-fg shadow-2xs tabular-nums placeholder:text-fg-subtle focus:border-danger focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-danger focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        >
    </div>
    <p id="npwp-error-msg" class="mt-1.5 flex items-start gap-1.5 text-xs text-danger">
        <svg class="mt-0.5 size-3.5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v4m0 4h.01M10.3 4.2 2.8 17.1A2 2 0 0 0 4.5 20h15a2 2 0 0 0 1.7-2.9L13.7 4.2a2 2 0 0 0-3.4 0Z" />
        </svg>
        <span>Enter a valid 15-digit NPWP for e-Faktur.</span>
    </p>
</div>

Modal

Docs

Accessible dialog overlay with focus trap, body teleport, Escape/backdrop dismiss, and confirm variant.

Basic dialog

Inline trigger slot with default actions.

Copied!
<x-ui.modal id="demo-basic" title="Project details" description="Review invoice INV-2026-089 for Toko Maju Jaya before sending.">
    <x-slot:trigger>
        <x-ui.button>Open dialog</x-ui.button>
    </x-slot:trigger>

    <p>Customer: Toko Maju Jaya · Bandung. Amount due: Rp 2.450.000 (IDR).</p>
</x-ui.modal>
<div x-data="{ open: false }">
    <button
        type="button"
        class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px]"
        x-on:click="open = true"
    >
        Open dialog
    </button>

    <template x-teleport="body">
        <div
            x-show="open"
            x-cloak
            class="fixed inset-0 z-50 flex items-end justify-center p-4 sm:items-center"
            role="dialog"
            aria-modal="true"
            aria-labelledby="demo-modal-title"
            x-trap.inert.noscroll="open"
            x-on:keydown.escape.window="if (open) open = false"
        >
            <div
                class="absolute inset-0 bg-overlay backdrop-blur-[2px]"
                x-show="open"
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0"
                x-transition:enter-end="opacity-100"
                x-transition:leave="transition ease-in duration-100"
                x-transition:leave-start="opacity-100"
                x-transition:leave-end="opacity-0"
                x-on:click="open = false"
            ></div>
            <div
                class="relative z-10 w-full max-w-lg rounded-card border border-border bg-surface-raised p-5 shadow-lg ring-1 ring-black/5"
                x-show="open"
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0 scale-95 translate-y-1"
                x-transition:enter-end="opacity-100 scale-100 translate-y-0"
                x-transition:leave="transition ease-in duration-100"
                x-transition:leave-start="opacity-100 scale-100 translate-y-0"
                x-transition:leave-end="opacity-0 scale-95 translate-y-1"
                x-on:click.stop
            >
                <h2 id="demo-modal-title" class="text-lg font-semibold tracking-tight text-fg">Project details</h2>
                <p class="mt-2 text-sm text-fg-muted leading-relaxed">Review invoice INV-2026-089 for Toko Maju Jaya before sending.</p>
                <div class="mt-6 flex justify-end gap-2">
                    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-sunken px-3.5 text-sm shadow-2xs" x-on:click="open = false">Close</button>
                </div>
            </div>
        </div>
    </template>
</div>

Confirm variant

Destructive confirm pattern with danger treatment.

Copied!
<x-ui.modal
    id="demo-confirm"
    variant="confirm"
    title="Approve payout?"
    description="Release Rp 18.750.000 to CV Nusantara for PO-8841. This cannot be undone."
>
    <x-slot:trigger>
        <x-ui.button variant="danger">Approve payout</x-ui.button>
    </x-slot:trigger>

    <x-slot:action>
        <x-ui.button variant="danger" x-on:click="hide()">Approve</x-ui.button>
    </x-slot:action>
</x-ui.modal>
<div x-data="{ open: false }">
    <button
        type="button"
        class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-danger px-3.5 text-sm font-medium text-on-danger shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px]"
        x-on:click="open = true"
    >
        Approve payout
    </button>

    <template x-teleport="body">
        <div
            x-show="open"
            x-cloak
            class="fixed inset-0 z-50 flex items-end justify-center p-4 sm:items-center"
            role="dialog"
            aria-modal="true"
            aria-labelledby="confirm-modal-title"
            x-trap.inert.noscroll="open"
            x-on:keydown.escape.window="if (open) open = false"
        >
            <div
                class="absolute inset-0 bg-overlay backdrop-blur-[2px]"
                x-show="open"
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0"
                x-transition:enter-end="opacity-100"
                x-transition:leave="transition ease-in duration-100"
                x-transition:leave-start="opacity-100"
                x-transition:leave-end="opacity-0"
                x-on:click="open = false"
            ></div>
            <div
                class="relative z-10 w-full max-w-lg rounded-card border border-border bg-surface-raised p-5 shadow-lg ring-1 ring-black/5"
                x-show="open"
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0 scale-95 translate-y-1"
                x-transition:enter-end="opacity-100 scale-100 translate-y-0"
                x-transition:leave="transition ease-in duration-100"
                x-transition:leave-start="opacity-100 scale-100 translate-y-0"
                x-transition:leave-end="opacity-0 scale-95 translate-y-1"
                x-on:click.stop
            >
                <h2 id="confirm-modal-title" class="text-lg font-semibold tracking-tight text-fg">Approve payout?</h2>
                <p class="mt-2 text-sm text-fg-muted leading-relaxed">Release Rp 18.750.000 to CV Nusantara for PO-8841. This cannot be undone.</p>
                <div class="mt-6 flex justify-end gap-2">
                    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-sunken px-3.5 text-sm shadow-2xs" x-on:click="open = false">Cancel</button>
                    <button type="button" class="inline-flex h-9 items-center rounded-control bg-danger px-3.5 text-sm font-medium text-on-danger shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)]" x-on:click="open = false">Approve</button>
                </div>
            </div>
        </div>
    </template>
</div>

Open via window event

Dispatch ui-modal-open with a matching id.

Copied!
<div class="flex flex-wrap items-center gap-3">
    <x-ui.button
        variant="outline"
        x-on:click="window.dispatchEvent(new CustomEvent('ui-modal-open', { detail: { id: 'demo-event' } }))"
    >
        Open via event
    </x-ui.button>

    <x-ui.modal
        id="demo-event"
        title="Approval queue"
        description="Open INV-2026-089 from the finance toolbar without nesting the trigger."
    >
        <p>Listen for the ui-modal-open window event and match detail.id.</p>
    </x-ui.modal>
</div>
<div>
    <button
        type="button"
        class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border-strong bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken"
        x-on:click="window.dispatchEvent(new CustomEvent('ui-modal-open', { detail: { id: 'demo-event' } }))"
    >
        Open via event
    </button>
</div>

Navbar

Docs

Responsive top application bar with brand, desktop links, actions, and an Alpine mobile drawer using focus trap and Escape close.

App shell navbar

SME back-office top bar with sales links and actions.

Copied!
<div class="space-y-8">
    <x-ui.navbar brand-href="/" brand-name="Toko Kita">
        <x-slot:links>
            <x-ui.navbar.link href="#" current>Dashboard</x-ui.navbar.link>
            <x-ui.navbar.link href="#">Pesanan</x-ui.navbar.link>
            <x-ui.navbar.link href="#">Pelanggan</x-ui.navbar.link>
        </x-slot:links>

        <x-slot:actions>
            <x-ui.button variant="outline" size="sm">Akun</x-ui.button>
        </x-slot:actions>
    </x-ui.navbar>

    <x-ui.navbar brand-href="/" brand-name="Toko Kita" mobile-style="floating">
        <x-slot:links>
            <x-ui.navbar.link href="#" current>Dashboard</x-ui.navbar.link>
            <x-ui.navbar.link href="#">Stok</x-ui.navbar.link>
        </x-slot:links>

        <x-slot:actions>
            <x-ui.button size="sm">New PO</x-ui.button>
        </x-slot:actions>
    </x-ui.navbar>
</div>
<div class="space-y-8">
    <header
        class="sticky top-0 z-40 w-full border-b border-border bg-surface/85 backdrop-blur-md"
        x-data="{ mobileOpen: false, close() { this.mobileOpen = false } }"
        x-on:keydown.escape.window="if (mobileOpen) close()"
        x-on:resize.window="if (window.innerWidth >= 768 && mobileOpen) close()"
    >
        <div class="mx-auto flex h-14 max-w-7xl items-center justify-between gap-4 px-4">
            <div class="flex items-center gap-6">
                <a href="/" class="inline-flex items-center gap-2.5 text-base font-semibold tracking-tight text-fg">
                    <span class="inline-flex size-7 items-center justify-center rounded-control bg-brand text-xs font-bold text-on-brand" aria-hidden="true">T</span>
                    <span>Toko Kita</span>
                </a>
                <nav aria-label="Main navigation" class="hidden md:flex md:gap-1">
                    <a href="#" class="inline-flex h-10 items-center rounded-control bg-surface-sunken px-3 text-sm font-medium text-fg" aria-current="page">Dashboard</a>
                    <a href="#" class="inline-flex h-10 items-center rounded-control px-3 text-sm font-medium text-fg-muted hover:bg-surface-sunken hover:text-fg">Pesanan</a>
                    <a href="#" class="inline-flex h-10 items-center rounded-control px-3 text-sm font-medium text-fg-muted hover:bg-surface-sunken hover:text-fg">Pelanggan</a>
                </nav>
            </div>
            <div class="flex items-center gap-2">
                <div class="hidden md:block">
                    <button type="button" class="inline-flex h-8 items-center rounded-control border border-border px-2.5 text-xs font-medium">Akun</button>
                </div>
                <button
                    type="button"
                    class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-fg shadow-2xs hover:bg-surface-sunken md:hidden"
                    x-on:click="mobileOpen = !mobileOpen"
                    x-bind:aria-expanded="mobileOpen.toString()"
                    aria-controls="ui-navbar-drawer-demo"
                    aria-label="Open navigation menu"
                >
                    <svg x-show="!mobileOpen" class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path stroke-linecap="round" d="M4 7h16M4 12h16M4 17h12" /></svg>
                    <svg x-show="mobileOpen" x-cloak class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path stroke-linecap="round" d="M18 6 6 18M6 6l12 12" /></svg>
                </button>
            </div>
        </div>

        <template x-teleport="body">
            <div x-show="mobileOpen" x-cloak id="ui-navbar-drawer-demo" role="dialog" aria-modal="true" aria-label="Mobile navigation" class="fixed inset-0 z-50 md:hidden">
                <div class="fixed inset-0 bg-overlay backdrop-blur-[2px]" x-on:click="close()" x-show="mobileOpen" x-transition:enter="transition-opacity ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition-opacity ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" aria-hidden="true"></div>
                <div
                    class="relative flex h-full w-[min(20rem,calc(100vw-2.5rem))] flex-col border-r border-border bg-surface-raised shadow-xl"
                    x-show="mobileOpen"
                    x-trap.inert.noscroll="mobileOpen"
                    x-transition:enter="transition ease-out duration-200 transform"
                    x-transition:enter-start="-translate-x-full"
                    x-transition:enter-end="translate-x-0"
                    x-transition:leave="transition ease-in duration-150 transform"
                    x-transition:leave-start="translate-x-0"
                    x-transition:leave-end="-translate-x-full"
                >
                    <div class="flex h-14 items-center justify-between border-b border-border px-4">
                        <span class="text-sm font-semibold text-fg">Toko Kita</span>
                        <button type="button" class="inline-flex size-9 items-center justify-center rounded-control text-fg-muted" x-on:click="close()" aria-label="Close navigation menu">×</button>
                    </div>
                    <nav aria-label="Mobile navigation links" class="flex-1 overflow-y-auto overscroll-contain px-3 py-4">
                        <a href="#" class="flex h-10 items-center rounded-control border-l-2 border-brand bg-brand/10 px-3 text-sm font-medium text-brand" aria-current="page">Dashboard</a>
                        <a href="#" class="flex h-10 items-center rounded-control border-l-2 border-transparent px-3 text-sm text-fg-muted hover:bg-surface-sunken">Pesanan</a>
                        <a href="#" class="flex h-10 items-center rounded-control border-l-2 border-transparent px-3 text-sm text-fg-muted hover:bg-surface-sunken">Pelanggan</a>
                    </nav>
                    <div class="border-t border-border bg-surface p-4">
                        <button type="button" class="inline-flex h-9 w-full items-center justify-center rounded-control border border-border text-sm">Akun</button>
                    </div>
                </div>
            </div>
        </template>
    </header>

    <header
        class="relative z-40 w-full border-b border-border bg-surface/85 backdrop-blur-md"
        x-data="{ mobileOpen: false, close() { this.mobileOpen = false } }"
        x-on:keydown.escape.window="if (mobileOpen) close()"
    >
        <div class="flex h-14 items-center justify-between gap-4 px-4">
            <a href="/" class="inline-flex items-center gap-2 font-semibold tracking-tight text-fg">
                <span class="inline-flex size-7 items-center justify-center rounded-control bg-brand text-xs font-bold text-on-brand" aria-hidden="true">T</span>
                Toko Kita
            </a>
            <button
                type="button"
                class="inline-flex size-10 items-center justify-center rounded-control text-fg-muted hover:bg-surface-sunken md:hidden"
                x-on:click="mobileOpen = !mobileOpen"
                x-bind:aria-expanded="mobileOpen.toString()"
                aria-controls="ui-navbar-floating-demo"
                aria-label="Open navigation menu"
            >
                Menu
            </button>
            <nav class="hidden gap-1 md:flex" aria-label="Main navigation">
                <a href="#" class="inline-flex h-10 items-center rounded-control bg-surface-sunken px-3 text-sm font-medium" aria-current="page">Dashboard</a>
                <a href="#" class="inline-flex h-10 items-center rounded-control px-3 text-sm text-fg-muted">Stok</a>
            </nav>
        </div>
        <div
            x-show="mobileOpen"
            x-cloak
            id="ui-navbar-floating-demo"
            role="dialog"
            aria-modal="true"
            aria-label="Mobile navigation"
            class="absolute inset-x-3 top-[calc(100%+0.5rem)] z-50 md:hidden"
            x-trap.inert.noscroll="mobileOpen"
            x-on:click.outside="close()"
        >
            <div class="overflow-hidden rounded-card border border-border bg-surface-raised shadow-xl ring-1 ring-black/5">
                <nav class="flex flex-col gap-1 p-3" aria-label="Mobile navigation links">
                    <a href="#" class="flex h-10 items-center rounded-control bg-brand/10 px-3 text-sm font-medium text-brand" aria-current="page">Dashboard</a>
                    <a href="#" class="flex h-10 items-center rounded-control px-3 text-sm text-fg-muted">Stok</a>
                </nav>
            </div>
        </div>
    </header>
</div>

Pagination

Docs

Numbered pagination with ellipsis windowing and a simple Previous/Next variant with result range summary.

Numbered

Ellipsis window around the current invoice page.

Copied!
<x-ui.pagination
    variant="numbered"
    :current="3"
    :total="5"
    :per-page="10"
    :total-items="42"
/>
<nav class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between" aria-label="Pagination">
    <p class="text-sm text-fg-muted">Showing <span class="font-medium tabular-nums text-fg">21</span> to <span class="font-medium tabular-nums text-fg">30</span> of <span class="font-medium tabular-nums text-fg">42</span> invoices</p>
    <ul class="flex items-center gap-1">
        <li><a href="?page=2" class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs transition-colors duration-150 hover:border-border-strong hover:bg-surface-sunken" aria-label="Previous page">‹</a></li>
        <li><a href="?page=1" class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs">1</a></li>
        <li><span class="inline-flex size-9 items-center justify-center text-sm tabular-nums text-fg-subtle" aria-hidden="true">…</span></li>
        <li><a href="?page=2" class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs">2</a></li>
        <li><a href="?page=3" class="inline-flex size-9 items-center justify-center rounded-control bg-brand text-sm tabular-nums text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)]" aria-current="page">3</a></li>
        <li><a href="?page=4" class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs">4</a></li>
        <li><span class="inline-flex size-9 items-center justify-center text-sm tabular-nums text-fg-subtle" aria-hidden="true">…</span></li>
        <li><a href="?page=5" class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs">5</a></li>
        <li><a href="?page=4" class="inline-flex size-9 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs transition-colors duration-150 hover:border-border-strong hover:bg-surface-sunken" aria-label="Next page">›</a></li>
    </ul>
</nav>

Simple

Previous/Next with Showing X to Y of Z results.

Copied!
<x-ui.pagination
    variant="simple"
    :current="1"
    :total="5"
    :per-page="10"
    :total-items="42"
/>
<nav class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between" aria-label="Pagination">
    <p class="text-sm text-fg-muted">Showing <span class="font-medium tabular-nums text-fg">1</span> to <span class="font-medium tabular-nums text-fg">10</span> of <span class="font-medium tabular-nums text-fg">42</span> invoices</p>
    <div class="flex items-center gap-2">
        <a href="?page=1" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm shadow-2xs opacity-50" aria-disabled="true" tabindex="-1">Previous</a>
        <a href="?page=2" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong hover:bg-surface-sunken">Next</a>
    </div>
</nav>

Radio

Docs

Native radio option with inline default layout or selectable card panels, label, description, and attribute forwarding.

Inline group

Default variant as a vertical radio list.

Copied!
Billing cycle
<fieldset class="max-w-sm space-y-3">
    <legend class="text-sm font-medium text-fg">Billing cycle</legend>
    <x-ui.radio name="cycle" value="monthly" label="Monthly" checked />
    <x-ui.radio name="cycle" value="yearly" label="Yearly" />
</fieldset>
<fieldset class="max-w-sm space-y-3">
    <legend class="text-sm font-medium text-fg">Billing cycle</legend>
    <div class="inline-flex items-start gap-3">
        <div class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input id="cycle-monthly" type="radio" name="cycle" value="monthly" checked class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
                <span class="dot size-2 rounded-full bg-brand opacity-0"></span>
            </span>
        </div>
        <label for="cycle-monthly" class="text-sm font-medium text-fg">Monthly</label>
    </div>
    <div class="inline-flex items-start gap-3">
        <div class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input id="cycle-yearly" type="radio" name="cycle" value="yearly" class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
                <span class="dot size-2 rounded-full bg-brand opacity-0"></span>
            </span>
        </div>
        <label for="cycle-yearly" class="text-sm font-medium text-fg">Yearly</label>
    </div>
</fieldset>

Card variant

Selectable panels highlighted with :has(input:checked).

Copied!
Deployment region
<fieldset class="grid max-w-xl gap-3 sm:grid-cols-2">
    <legend class="mb-1 text-sm font-medium text-fg">Deployment region</legend>
    <x-ui.radio
        variant="card"
        name="region"
        value="sg"
        label="Singapore"
        description="Lowest latency for SEA users."
        checked
    />
    <x-ui.radio
        variant="card"
        name="region"
        value="eu"
        label="Frankfurt"
        description="EU data residency friendly."
    />
</fieldset>
<fieldset class="grid max-w-xl gap-3 sm:grid-cols-2">
    <legend class="mb-1 text-sm font-medium text-fg">Deployment region</legend>
    <label class="flex cursor-pointer items-start gap-3 rounded-card border border-border bg-surface-raised p-4 shadow-sm has-[:checked]:border-brand has-[:checked]:bg-brand/5">
        <span class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input id="region-sg" type="radio" name="region" value="sg" checked class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
                <span class="dot size-2 rounded-full bg-brand opacity-0"></span>
            </span>
        </span>
        <span>
            <span class="block text-sm font-medium text-fg">Singapore</span>
            <span class="mt-0.5 block text-sm text-fg-muted">Lowest latency for SEA users.</span>
        </span>
    </label>
    <label class="flex cursor-pointer items-start gap-3 rounded-card border border-border bg-surface-raised p-4 shadow-sm has-[:checked]:border-brand has-[:checked]:bg-brand/5">
        <span class="relative mt-0.5 inline-flex size-4 shrink-0">
            <input id="region-eu" type="radio" name="region" value="eu" class="peer absolute inset-0 z-10 size-4 cursor-pointer opacity-0">
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm peer-checked:border-brand peer-checked:[&_.dot]:opacity-100" aria-hidden="true">
                <span class="dot size-2 rounded-full bg-brand opacity-0"></span>
            </span>
        </span>
        <span>
            <span class="block text-sm font-medium text-fg">Frankfurt</span>
            <span class="mt-0.5 block text-sm text-fg-muted">EU data residency friendly.</span>
        </span>
    </label>
</fieldset>

Disabled option

Disabled state on a single option.

Copied!

Contact sales to unlock this plan.

<x-ui.radio
    name="plan_demo"
    value="enterprise"
    label="Enterprise"
    description="Contact sales to unlock this plan."
    disabled
/>
<div class="inline-flex items-start gap-3 opacity-50">
    <div class="relative mt-0.5 inline-flex size-4 shrink-0">
        <input id="plan-enterprise" type="radio" name="plan_demo" value="enterprise" disabled class="peer absolute inset-0 z-10 size-4 cursor-not-allowed opacity-0">
        <span class="pointer-events-none flex size-4 items-center justify-center rounded-full border border-border-strong bg-surface-raised shadow-sm" aria-hidden="true">
            <span class="size-2 rounded-full bg-brand opacity-0"></span>
        </span>
    </div>
    <div>
        <label for="plan-enterprise" class="block text-sm font-medium text-fg">Enterprise</label>
        <p class="mt-0.5 text-sm text-fg-muted">Contact sales to unlock this plan.</p>
    </div>
</div>

Select

Docs

Native select control styled to match input heights, with a decorative chevron, label, hint, error, and attribute forwarding.

Basic select

Label, hint, placeholder option, and required state.

Copied!

Controls access to invoices, stock, and approvals.

<x-ui.select
    name="role"
    label="Team role"
    hint="Controls access to invoices, stock, and approvals."
    placeholder="Select a role"
    required
    class="max-w-sm"
>
    <option value="owner">Owner</option>
    <option value="finance">Finance</option>
    <option value="ops">Operations</option>
    <option value="viewer">Viewer</option>
</x-ui.select>
<div class="w-full max-w-sm">
    <label for="team-role" class="mb-1.5 block text-sm font-medium text-fg">Team role</label>
    <div class="relative">
        <select
            id="team-role"
            name="role"
            required
            aria-describedby="team-role-hint"
            class="h-9 w-full appearance-none rounded-control border border-border bg-surface-raised pl-3.5 pr-9 text-sm text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong focus:border-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        >
            <option value="" disabled selected>Select a role</option>
            <option value="owner">Owner</option>
            <option value="finance">Finance</option>
            <option value="ops">Operations</option>
            <option value="viewer">Viewer</option>
        </select>
        <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5 text-fg-subtle" aria-hidden="true">
            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
                <path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
            </svg>
        </span>
    </div>
    <p id="team-role-hint" class="mt-1.5 text-xs text-fg-muted leading-relaxed">Controls access to invoices, stock, and approvals.</p>
</div>

Sizes

sm, md, and lg heights matching input.

Copied!
<div class="flex max-w-sm flex-col gap-4">
    <x-ui.select size="sm" name="size_sm" aria-label="Warehouse small">
        <option>Gudang A · Jakarta</option>
    </x-ui.select>
    <x-ui.select size="md" name="size_md" aria-label="Warehouse medium">
        <option>Gudang B · Bandung</option>
    </x-ui.select>
    <x-ui.select size="lg" name="size_lg" aria-label="Warehouse large">
        <option>Gudang C · Surabaya</option>
    </x-ui.select>
</div>
<div class="flex max-w-sm flex-col gap-4">
    <div class="relative">
        <select aria-label="Warehouse small" class="h-8 w-full appearance-none rounded-control border border-border bg-surface-raised pl-2.5 pr-8 text-xs text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong focus:border-brand">
            <option>Gudang A · Jakarta</option>
        </select>
        <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5 text-fg-subtle" aria-hidden="true">
            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" /></svg>
        </span>
    </div>
    <div class="relative">
        <select aria-label="Warehouse medium" class="h-9 w-full appearance-none rounded-control border border-border bg-surface-raised pl-3.5 pr-9 text-sm text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong focus:border-brand">
            <option>Gudang B · Bandung</option>
        </select>
        <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5 text-fg-subtle" aria-hidden="true">
            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" /></svg>
        </span>
    </div>
    <div class="relative">
        <select aria-label="Warehouse large" class="h-10 w-full appearance-none rounded-control border border-border bg-surface-raised pl-4 pr-10 text-sm text-fg shadow-2xs transition-colors duration-150 hover:border-border-strong focus:border-brand">
            <option>Gudang C · Surabaya</option>
        </select>
        <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5 text-fg-subtle" aria-hidden="true">
            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" /></svg>
        </span>
    </div>
</div>

Validation error

Error text linked through aria-describedby.

Copied!

Choose IDR or USD before issuing INV-2026-089.

<x-ui.select
    name="currency"
    label="Settlement currency"
    placeholder="Select currency"
    error="Choose IDR or USD before issuing INV-2026-089."
    class="max-w-sm"
>
    <option value="idr">IDR · Rupiah</option>
    <option value="usd">USD · US Dollar</option>
    <option value="sgd">SGD · Singapore Dollar</option>
</x-ui.select>
<div class="w-full max-w-sm">
    <label for="currency-error" class="mb-1.5 block text-sm font-medium text-fg">Settlement currency</label>
    <div class="relative">
        <select
            id="currency-error"
            name="currency"
            aria-invalid="true"
            aria-describedby="currency-error-msg"
            class="h-9 w-full appearance-none rounded-control border border-danger bg-surface-raised pl-3.5 pr-9 text-sm text-fg shadow-2xs focus:border-danger focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-danger focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        >
            <option value="" disabled selected>Select currency</option>
            <option value="idr">IDR · Rupiah</option>
            <option value="usd">USD · US Dollar</option>
            <option value="sgd">SGD · Singapore Dollar</option>
        </select>
        <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5 text-fg-subtle" aria-hidden="true">
            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" /></svg>
        </span>
    </div>
    <p id="currency-error-msg" class="mt-1.5 flex items-start gap-1.5 text-xs text-danger">
        <svg class="mt-0.5 size-3.5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v4m0 4h.01M10.3 4.2 2.8 17.1A2 2 0 0 0 4.5 20h15a2 2 0 0 0 1.7-2.9L13.7 4.2a2 2 0 0 0-3.4 0Z" />
        </svg>
        <span>Choose IDR or USD before issuing INV-2026-089.</span>
    </p>
</div>

Sidebar

Docs

Collapsible application sidebar with section headings, icon and label items, active states, and mobile off-canvas overlay.

Operations menu

Sections for penjualan and inventori in an SME ops app.

Copied!
<x-ui.sidebar label="Operasional">
    <x-slot:brand>Operasional</x-slot:brand>

    <x-ui.sidebar.section title="Penjualan">
        <x-ui.sidebar.item href="#" current label="Pesanan">
            <x-slot:icon><x-icons.search class="size-4" /></x-slot:icon>
            Pesanan
        </x-ui.sidebar.item>
        <x-ui.sidebar.item href="#" label="Faktur">
            <x-slot:icon><x-icons.copy class="size-4" /></x-slot:icon>
            Faktur
        </x-ui.sidebar.item>
    </x-ui.sidebar.section>

    <x-ui.sidebar.section title="Inventori">
        <x-ui.sidebar.item href="#" label="Stok gudang">
            <x-slot:icon><x-icons.menu class="size-4" /></x-slot:icon>
            Stok gudang
        </x-ui.sidebar.item>
    </x-ui.sidebar.section>
</x-ui.sidebar>
<div x-data="{ open: false, close() { this.open = false } }" x-on:keydown.escape.window="if (open) close()" x-on:resize.window="if (window.innerWidth >= 1024 && open) close()">
    <button
        type="button"
        class="mb-3 inline-flex h-10 items-center gap-2 rounded-control border border-border bg-surface-raised px-3 text-sm shadow-2xs lg:hidden"
        x-on:click="open = true"
        x-bind:aria-expanded="open.toString()"
        aria-controls="ui-sidebar-panel-demo"
        aria-label="Open sidebar menu"
    >
        Menu
    </button>

    <nav class="hidden w-64 flex-col gap-4 rounded-card border border-border bg-surface-raised p-4 lg:flex" aria-label="Sidebar">
        <p class="text-sm font-semibold tracking-tight text-fg">Operasional</p>
        <p class="px-3 text-xs font-semibold uppercase tracking-wider text-fg-subtle">Penjualan</p>
        <a href="#" class="flex h-10 items-center rounded-control bg-surface-sunken px-3 text-sm font-medium text-fg" aria-current="page">Pesanan</a>
        <a href="#" class="flex h-10 items-center rounded-control px-3 text-sm font-medium text-fg-muted hover:bg-surface-sunken">Faktur</a>
        <p class="mt-2 px-3 text-xs font-semibold uppercase tracking-wider text-fg-subtle">Inventori</p>
        <a href="#" class="flex h-10 items-center rounded-control px-3 text-sm font-medium text-fg-muted hover:bg-surface-sunken">Stok gudang</a>
    </nav>

    <template x-teleport="body">
        <div x-show="open" x-cloak role="dialog" aria-modal="true" aria-label="Operasional" class="fixed inset-0 z-50 lg:hidden">
            <div class="fixed inset-0 bg-overlay backdrop-blur-[2px]" x-on:click="close()" x-show="open" x-transition:enter="transition-opacity ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition-opacity ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" aria-hidden="true"></div>
            <nav
                id="ui-sidebar-panel-demo"
                class="relative flex h-full w-[min(20rem,calc(100vw-2.5rem))] flex-col gap-2 overflow-y-auto overscroll-contain border-r border-border bg-surface-raised p-4 shadow-xl"
                x-show="open"
                x-trap.inert.noscroll="open"
                x-transition:enter="transition ease-out duration-200 transform"
                x-transition:enter-start="-translate-x-full"
                x-transition:enter-end="translate-x-0"
                x-transition:leave="transition ease-in duration-150 transform"
                x-transition:leave-start="translate-x-0"
                x-transition:leave-end="-translate-x-full"
                aria-label="Sidebar"
            >
                <div class="mb-2 flex h-10 items-center justify-between">
                    <p class="text-sm font-semibold text-fg">Operasional</p>
                    <button type="button" class="inline-flex size-9 items-center justify-center rounded-control text-fg-muted" x-on:click="close()" aria-label="Close sidebar">×</button>
                </div>
                <p class="px-3 text-xs font-semibold uppercase tracking-wider text-fg-subtle">Penjualan</p>
                <a href="#" class="flex h-10 items-center rounded-control bg-brand/10 px-3 text-sm font-medium text-brand" aria-current="page">Pesanan</a>
                <a href="#" class="flex h-10 items-center rounded-control px-3 text-sm text-fg-muted">Faktur</a>
                <p class="mt-2 px-3 text-xs font-semibold uppercase tracking-wider text-fg-subtle">Inventori</p>
                <a href="#" class="flex h-10 items-center rounded-control px-3 text-sm text-fg-muted">Stok gudang</a>
            </nav>
        </div>
    </template>
</div>

Switch

Docs

Accessible toggle switch built on a native checkbox with role="switch", sm/md sizes, label, description, and attribute forwarding.

Basic switch

Label and description with attribute forwarding.

Copied!

Email me when a deploy finishes.

<x-ui.switch
    name="notifications"
    value="1"
    label="Product notifications"
    description="Email me when a deploy finishes."
    checked
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative inline-flex shrink-0">
            <input
                id="notifications-basic"
                type="checkbox"
                role="switch"
                name="notifications"
                value="1"
                checked
                aria-describedby="notifications-basic-description"
                class="peer absolute inset-0 z-10 h-6 w-11 cursor-pointer opacity-0"
            >
            <span class="pointer-events-none inline-flex h-6 w-11 items-center rounded-pill bg-border-strong p-0.5 transition-colors peer-checked:bg-brand peer-checked:[&_.thumb]:translate-x-5" aria-hidden="true">
                <span class="thumb block size-5 rounded-full bg-surface-raised shadow-sm transition-transform"></span>
            </span>
        </div>
        <div>
            <label for="notifications-basic" class="block text-sm font-medium text-fg">Product notifications</label>
            <p id="notifications-basic-description" class="mt-0.5 text-sm text-fg-muted">Email me when a deploy finishes.</p>
        </div>
    </div>
</div>

Sizes

sm and md track sizes.

Copied!
<div class="flex flex-col gap-4">
    <x-ui.switch size="sm" name="size_sm" aria-label="Small switch" label="Small" />
    <x-ui.switch size="md" name="size_md" aria-label="Medium switch" label="Medium" checked />
</div>
<div class="flex flex-col gap-4">
    <div class="flex items-center gap-3">
        <div class="relative inline-flex">
            <input type="checkbox" role="switch" class="peer absolute inset-0 z-10 h-5 w-9 cursor-pointer opacity-0" aria-label="Small switch">
            <span class="pointer-events-none inline-flex h-5 w-9 items-center rounded-pill bg-border-strong p-0.5 peer-checked:bg-brand peer-checked:[&_.thumb]:translate-x-4" aria-hidden="true">
                <span class="thumb block size-4 rounded-full bg-surface-raised shadow-sm transition-transform"></span>
            </span>
        </div>
        <span class="text-sm text-fg">Small</span>
    </div>
    <div class="flex items-center gap-3">
        <div class="relative inline-flex">
            <input type="checkbox" role="switch" checked class="peer absolute inset-0 z-10 h-6 w-11 cursor-pointer opacity-0" aria-label="Medium switch">
            <span class="pointer-events-none inline-flex h-6 w-11 items-center rounded-pill bg-border-strong p-0.5 peer-checked:bg-brand peer-checked:[&_.thumb]:translate-x-5" aria-hidden="true">
                <span class="thumb block size-5 rounded-full bg-surface-raised shadow-sm transition-transform"></span>
            </span>
        </div>
        <span class="text-sm text-fg">Medium</span>
    </div>
</div>

Disabled

Non-interactive disabled state.

Copied!

Only owners can change this setting.

<x-ui.switch
    name="maintenance"
    label="Maintenance mode"
    description="Only owners can change this setting."
    disabled
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative inline-flex shrink-0">
            <input
                id="maintenance-disabled"
                type="checkbox"
                role="switch"
                name="maintenance"
                disabled
                class="peer absolute inset-0 z-10 h-6 w-11 cursor-not-allowed opacity-0"
            >
            <span class="pointer-events-none inline-flex h-6 w-11 items-center rounded-pill bg-border-strong p-0.5 opacity-50" aria-hidden="true">
                <span class="thumb block size-5 rounded-full bg-surface-raised shadow-sm"></span>
            </span>
        </div>
        <div>
            <label for="maintenance-disabled" class="block text-sm font-medium text-fg">Maintenance mode</label>
            <p class="mt-0.5 text-sm text-fg-muted">Only owners can change this setting.</p>
        </div>
    </div>
</div>

Table

Docs

Compound data table with keyboard-scrollable region, striped and hoverable rows, sortable headers, bulk select, and empty state.

Orders table

Indonesian SME order list with badges and sortable amount.

Copied!
Invoice Customer Status
INV-2026-089 Toko Maju Jaya Paid Rp 2.450.000
INV-2026-090 CV Nusantara Pending USD 480.00
INV-2026-091 Warung Sederhana Draft Rp 125.000
<x-ui.table striped hoverable sticky-header label="Daftar pesanan">
    <x-ui.table.head>
        <x-ui.table.cell header>Invoice</x-ui.table.cell>
        <x-ui.table.cell header>Customer</x-ui.table.cell>
        <x-ui.table.cell header>Status</x-ui.table.cell>
        <x-ui.table.cell header sortable sort="descending" numeric>Total</x-ui.table.cell>
    </x-ui.table.head>
    <x-ui.table.body>
        <x-ui.table.row>
            <x-ui.table.cell numeric>INV-2026-089</x-ui.table.cell>
            <x-ui.table.cell>Toko Maju Jaya</x-ui.table.cell>
            <x-ui.table.cell><x-ui.badge color="success" size="sm">Paid</x-ui.badge></x-ui.table.cell>
            <x-ui.table.cell numeric>Rp 2.450.000</x-ui.table.cell>
        </x-ui.table.row>
        <x-ui.table.row>
            <x-ui.table.cell numeric>INV-2026-090</x-ui.table.cell>
            <x-ui.table.cell>CV Nusantara</x-ui.table.cell>
            <x-ui.table.cell><x-ui.badge color="warning" size="sm">Pending</x-ui.badge></x-ui.table.cell>
            <x-ui.table.cell numeric>USD 480.00</x-ui.table.cell>
        </x-ui.table.row>
        <x-ui.table.row>
            <x-ui.table.cell numeric>INV-2026-091</x-ui.table.cell>
            <x-ui.table.cell>Warung Sederhana</x-ui.table.cell>
            <x-ui.table.cell><x-ui.badge color="info" size="sm">Draft</x-ui.badge></x-ui.table.cell>
            <x-ui.table.cell numeric>Rp 125.000</x-ui.table.cell>
        </x-ui.table.row>
    </x-ui.table.body>
</x-ui.table>
<div class="overflow-x-auto rounded-card border border-border" role="region" aria-label="Daftar pesanan" tabindex="0">
    <table class="w-full min-w-[40rem] border-collapse text-left text-sm [&_tbody_tr]:transition-colors [&_tbody_tr:hover]:bg-surface-sunken/50">
        <thead class="bg-surface-sunken/60 text-xs font-semibold uppercase tracking-wider text-fg-muted">
            <tr>
                <th class="px-4 py-2.5 font-semibold">Invoice</th>
                <th class="px-4 py-2.5 font-semibold">Customer</th>
                <th class="px-4 py-2.5 font-semibold">Status</th>
                <th class="px-4 py-2.5 font-semibold">
                    <button type="button" class="inline-flex items-center gap-1 uppercase tracking-wider" aria-sort="descending">Total <span aria-hidden="true">↓</span></button>
                </th>
            </tr>
        </thead>
        <tbody class="divide-y divide-border bg-surface-raised">
            <tr>
                <td class="px-4 py-3 tabular-nums">INV-2026-089</td>
                <td class="px-4 py-3">Toko Maju Jaya</td>
                <td class="px-4 py-3"><span class="inline-flex items-center rounded-pill bg-success-soft px-2 py-0.5 text-xs font-medium tabular-nums text-success shadow-2xs">Paid</span></td>
                <td class="px-4 py-3 tabular-nums">Rp 2.450.000</td>
            </tr>
            <tr>
                <td class="px-4 py-3 tabular-nums">INV-2026-090</td>
                <td class="px-4 py-3">CV Nusantara</td>
                <td class="px-4 py-3"><span class="inline-flex items-center rounded-pill bg-warning-soft px-2 py-0.5 text-xs font-medium tabular-nums text-warning shadow-2xs">Pending</span></td>
                <td class="px-4 py-3 tabular-nums">USD 480.00</td>
            </tr>
            <tr>
                <td class="px-4 py-3 tabular-nums">INV-2026-091</td>
                <td class="px-4 py-3">Warung Sederhana</td>
                <td class="px-4 py-3"><span class="inline-flex items-center rounded-pill bg-info-soft px-2 py-0.5 text-xs font-medium tabular-nums text-info shadow-2xs">Draft</span></td>
                <td class="px-4 py-3 tabular-nums">Rp 125.000</td>
            </tr>
        </tbody>
    </table>
</div>

Bulk select

Header and row checkboxes driven by Alpine.

Copied!
Invoice Due date Amount
INV-2026-089 28 Oct 2026 Rp 2.450.000
INV-2026-090 02 Nov 2026 USD 480.00
<x-ui.table selectable label="Faktur terpilih">
    <x-ui.table.head>
        <x-ui.table.cell header select-all />
        <x-ui.table.cell header>Invoice</x-ui.table.cell>
        <x-ui.table.cell header>Due date</x-ui.table.cell>
        <x-ui.table.cell header numeric>Amount</x-ui.table.cell>
    </x-ui.table.head>
    <x-ui.table.body>
        <x-ui.table.row id="INV-2026-089">
            <x-ui.table.cell select-id="INV-2026-089" />
            <x-ui.table.cell numeric>INV-2026-089</x-ui.table.cell>
            <x-ui.table.cell numeric>28 Oct 2026</x-ui.table.cell>
            <x-ui.table.cell numeric>Rp 2.450.000</x-ui.table.cell>
        </x-ui.table.row>
        <x-ui.table.row id="INV-2026-090">
            <x-ui.table.cell select-id="INV-2026-090" />
            <x-ui.table.cell numeric>INV-2026-090</x-ui.table.cell>
            <x-ui.table.cell numeric>02 Nov 2026</x-ui.table.cell>
            <x-ui.table.cell numeric>USD 480.00</x-ui.table.cell>
        </x-ui.table.row>
    </x-ui.table.body>
</x-ui.table>
<div x-data="{ selected: [], toggle(id) { this.selected = this.selected.includes(id) ? this.selected.filter(v => v !== id) : [...this.selected, id] } }">
    <div class="overflow-x-auto rounded-card border border-border" role="region" aria-label="Faktur terpilih" tabindex="0">
        <table class="w-full min-w-[32rem] text-left text-sm [&_tbody_tr]:transition-colors [&_tbody_tr:hover]:bg-surface-sunken/50">
            <thead class="bg-surface-sunken/60 text-xs font-semibold uppercase tracking-wider text-fg-muted">
                <tr>
                    <th class="px-4 py-2.5"><span class="sr-only">Select</span></th>
                    <th class="px-4 py-2.5 font-semibold">Invoice</th>
                    <th class="px-4 py-2.5 font-semibold">Due date</th>
                    <th class="px-4 py-2.5 font-semibold">Amount</th>
                </tr>
            </thead>
            <tbody class="divide-y divide-border">
                <tr>
                    <td class="px-4 py-3"><input type="checkbox" x-on:change="toggle('INV-2026-089')" aria-label="Select INV-2026-089"></td>
                    <td class="px-4 py-3 tabular-nums">INV-2026-089</td>
                    <td class="px-4 py-3 tabular-nums">28 Oct 2026</td>
                    <td class="px-4 py-3 tabular-nums">Rp 2.450.000</td>
                </tr>
                <tr>
                    <td class="px-4 py-3"><input type="checkbox" x-on:change="toggle('INV-2026-090')" aria-label="Select INV-2026-090"></td>
                    <td class="px-4 py-3 tabular-nums">INV-2026-090</td>
                    <td class="px-4 py-3 tabular-nums">02 Nov 2026</td>
                    <td class="px-4 py-3 tabular-nums">USD 480.00</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

Empty state

Clean empty table with action slot.

Copied!
SKU Stock

No products match

Try Gudang A / Bandung or clear the SKU filter.

<x-ui.table label="Hasil pencarian kosong">
    <x-ui.table.head>
        <x-ui.table.cell header>SKU</x-ui.table.cell>
        <x-ui.table.cell header>Stock</x-ui.table.cell>
    </x-ui.table.head>
    <x-ui.table.body>
        <x-ui.table.empty
            title="No products match"
            description="Try Gudang A / Bandung or clear the SKU filter."
        >
            <x-slot:action>
                <x-ui.button size="sm">Add product</x-ui.button>
            </x-slot:action>
        </x-ui.table.empty>
    </x-ui.table.body>
</x-ui.table>
<div class="overflow-x-auto rounded-card border border-border" role="region" aria-label="Hasil pencarian kosong" tabindex="0">
    <table class="w-full text-sm">
        <thead class="bg-surface-sunken/60 text-xs font-semibold uppercase tracking-wider text-fg-muted">
            <tr>
                <th class="px-4 py-2.5 font-semibold">SKU</th>
                <th class="px-4 py-2.5 font-semibold">Stock</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td colspan="2" class="px-4 py-12 text-center">
                    <p class="text-sm font-semibold tracking-tight text-fg">No products match</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">Try Gudang A / Bandung or clear the SKU filter.</p>
                </td>
            </tr>
        </tbody>
    </table>
</div>

Tabs

Docs

Compound WAI-ARIA tabs with underline, pills, and vertical variants, roving tabindex, and arrow key navigation.

Underline

Default horizontal underline tabs.

Copied!
Manage your profile and sign-in methods.
Invoices, payment methods, and plan changes.
Invite teammates once this plan is upgraded.
<x-ui.tabs variant="underline" active="account">
    <x-slot:list>
        <x-ui.tabs.tab id="account">Account</x-ui.tabs.tab>
        <x-ui.tabs.tab id="billing">Billing</x-ui.tabs.tab>
        <x-ui.tabs.tab id="team" disabled>Team</x-ui.tabs.tab>
    </x-slot:list>

    <x-ui.tabs.panel id="account">Manage your profile and sign-in methods.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="billing">Invoices, payment methods, and plan changes.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="team">Invite teammates once this plan is upgraded.</x-ui.tabs.panel>
</x-ui.tabs>
<div
    class="space-y-4"
    x-data="{ active: 'account', select(id) { this.active = id } }"
>
    <div role="tablist" aria-orientation="horizontal" class="flex gap-1 border-b border-border">
        <button type="button" role="tab" id="account-tab" aria-controls="account-panel" x-bind:aria-selected="(active === 'account').toString()" x-bind:tabindex="active === 'account' ? 0 : -1" class="border-b-2 px-3 py-2 text-sm font-medium" x-bind:class="active === 'account' ? 'border-brand text-fg' : 'border-transparent text-fg-muted'" x-on:click="select('account')">Account</button>
        <button type="button" role="tab" id="billing-tab" aria-controls="billing-panel" x-bind:aria-selected="(active === 'billing').toString()" x-bind:tabindex="active === 'billing' ? 0 : -1" class="border-b-2 px-3 py-2 text-sm font-medium" x-bind:class="active === 'billing' ? 'border-brand text-fg' : 'border-transparent text-fg-muted'" x-on:click="select('billing')">Billing</button>
    </div>
    <div role="tabpanel" id="account-panel" aria-labelledby="account-tab" x-show="active === 'account'" class="text-sm text-fg">Manage your profile and sign-in methods.</div>
    <div role="tabpanel" id="billing-panel" aria-labelledby="billing-tab" x-show="active === 'billing'" x-cloak class="text-sm text-fg">Invoices, payment methods, and plan changes.</div>
</div>

Pills

Compact pill-style tablist.

Copied!
Project health and recent deploys.
Audit log entries for this workspace.
<x-ui.tabs variant="pills" active="overview">
    <x-slot:list>
        <x-ui.tabs.tab id="overview">Overview</x-ui.tabs.tab>
        <x-ui.tabs.tab id="activity">Activity</x-ui.tabs.tab>
    </x-slot:list>

    <x-ui.tabs.panel id="overview">Project health and recent deploys.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="activity">Audit log entries for this workspace.</x-ui.tabs.panel>
</x-ui.tabs>
<div class="space-y-4" x-data="{ active: 'overview', select(id) { this.active = id } }">
    <div role="tablist" class="inline-flex gap-1 rounded-control bg-surface-sunken p-1">
        <button type="button" role="tab" x-bind:aria-selected="(active === 'overview').toString()" x-on:click="select('overview')" class="rounded-control px-3 py-1.5 text-sm" x-bind:class="active === 'overview' ? 'bg-surface-raised text-fg shadow-sm' : 'text-fg-muted'">Overview</button>
        <button type="button" role="tab" x-bind:aria-selected="(active === 'activity').toString()" x-on:click="select('activity')" class="rounded-control px-3 py-1.5 text-sm" x-bind:class="active === 'activity' ? 'bg-surface-raised text-fg shadow-sm' : 'text-fg-muted'">Activity</button>
    </div>
    <div role="tabpanel" x-show="active === 'overview'" class="text-sm text-fg">Project health and recent deploys.</div>
    <div role="tabpanel" x-show="active === 'activity'" x-cloak class="text-sm text-fg">Audit log entries for this workspace.</div>
</div>

Vertical

Vertical orientation with up/down arrow keys.

Copied!
Workspace name and default locale.
Session lifetime and SSO providers.
<x-ui.tabs variant="vertical" active="general">
    <x-slot:list>
        <x-ui.tabs.tab id="general">General</x-ui.tabs.tab>
        <x-ui.tabs.tab id="security">Security</x-ui.tabs.tab>
    </x-slot:list>

    <x-ui.tabs.panel id="general">Workspace name and default locale.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="security">Session lifetime and SSO providers.</x-ui.tabs.panel>
</x-ui.tabs>
<div class="flex gap-6" x-data="{ active: 'general', select(id) { this.active = id } }">
    <div role="tablist" aria-orientation="vertical" class="flex w-40 flex-col gap-1 border-r border-border pr-4">
        <button type="button" role="tab" x-bind:aria-selected="(active === 'general').toString()" x-on:click="select('general')" class="rounded-control px-3 py-2 text-left text-sm" x-bind:class="active === 'general' ? 'bg-surface-sunken text-fg' : 'text-fg-muted'">General</button>
        <button type="button" role="tab" x-bind:aria-selected="(active === 'security').toString()" x-on:click="select('security')" class="rounded-control px-3 py-2 text-left text-sm" x-bind:class="active === 'security' ? 'bg-surface-sunken text-fg' : 'text-fg-muted'">Security</button>
    </div>
    <div class="flex-1">
        <div role="tabpanel" x-show="active === 'general'" class="text-sm text-fg">Workspace name and default locale.</div>
        <div role="tabpanel" x-show="active === 'security'" x-cloak class="text-sm text-fg">Session lifetime and SSO providers.</div>
    </div>
</div>

Textarea

Docs

Multi-line form control with rows or autosize, optional character counter, label, hint, error, and attribute forwarding.

Basic textarea

Label, hint, and rows-based sizing.

Copied!

Keep it short and specific.

<x-ui.textarea
    name="bio"
    label="Bio"
    hint="Keep it short and specific."
    placeholder="Tell us about your product"
    rows="4"
    class="max-w-lg"
/>
<div class="w-full max-w-lg">
    <label for="bio-basic" class="mb-1.5 block text-sm font-medium text-fg">Bio</label>
    <textarea
        id="bio-basic"
        name="bio"
        rows="4"
        placeholder="Tell us about your product"
        aria-describedby="bio-basic-hint"
        class="min-h-24 w-full resize-y rounded-control border border-border bg-surface-raised px-3 py-2 text-sm text-fg shadow-sm placeholder:text-fg-subtle focus:border-border-strong"
    ></textarea>
    <p id="bio-basic-hint" class="mt-1.5 text-xs text-fg-muted">Keep it short and specific.</p>
</div>

Character counter

Live count / max linked through aria-describedby.

Copied!

0 / 120

<x-ui.textarea
    name="summary"
    label="Summary"
    maxlength="120"
    rows="3"
    class="max-w-lg"
/>
<div class="w-full max-w-lg" x-data="{ count: 0, max: 120 }">
    <label for="summary-counter" class="mb-1.5 block text-sm font-medium text-fg">Summary</label>
    <textarea
        id="summary-counter"
        name="summary"
        rows="3"
        maxlength="120"
        aria-describedby="summary-counter-counter"
        class="min-h-24 w-full resize-y rounded-control border border-border bg-surface-raised px-3 py-2 text-sm text-fg shadow-sm focus:border-border-strong"
        x-on:input="count = $event.target.value.length"
    ></textarea>
    <p id="summary-counter-counter" class="mt-1.5 text-right text-xs text-fg-subtle" aria-live="polite">
        <span x-text="count">0</span> / 120
    </p>
</div>

Validation error

Error text with aria-invalid.

Copied!

Please add at least 20 characters.

<x-ui.textarea
    name="notes"
    label="Notes"
    value="Too short"
    error="Please add at least 20 characters."
    rows="3"
    class="max-w-lg"
/>
<div class="w-full max-w-lg">
    <label for="notes-error" class="mb-1.5 block text-sm font-medium text-fg">Notes</label>
    <textarea
        id="notes-error"
        name="notes"
        rows="3"
        aria-invalid="true"
        aria-describedby="notes-error-error"
        class="min-h-24 w-full resize-y rounded-control border border-danger bg-surface-raised px-3 py-2 text-sm text-fg shadow-sm"
    >Too short</textarea>
    <p id="notes-error-error" class="mt-1.5 text-xs text-danger">Please add at least 20 characters.</p>
</div>

Toast

Docs

Global toast notifications via Alpine.store and the ui:toast window event, rendered through a fixed body-level viewport.

Dispatch toasts

Buttons that fire the ui:toast window event.

Copied!
<div class="flex flex-wrap gap-3">
    <x-ui.button
        x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'success', title: 'Payout approved', message: 'Rp 18.750.000 released to CV Nusantara.', duration: 4000 } }))"
    >
        Show success toast
    </x-ui.button>
    <x-ui.button
        variant="outline"
        x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'info', title: 'Export queued', message: 'Orders CSV for Oct 2026 will be ready shortly.' } }))"
    >
        Show info toast
    </x-ui.button>
</div>
<div class="flex flex-wrap gap-3">
    <button
        type="button"
        class="inline-flex h-9 items-center rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px]"
        x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'success', title: 'Payout approved', message: 'Rp 18.750.000 released to CV Nusantara.', duration: 4000 } }))"
    >
        Show success toast
    </button>
    <button
        type="button"
        class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm font-medium text-fg shadow-2xs"
        x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'info', title: 'Export queued', message: 'Orders CSV for Oct 2026 will be ready shortly.' } }))"
    >
        Show info toast
    </button>
</div>

Types

info, success, warning, and danger payloads.

Copied!
<div class="flex flex-wrap gap-3">
    <x-ui.button variant="outline" size="sm" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'info', title: 'Sync queued', message: 'Stock levels for Gudang A will refresh in ~30s.' } }))">Info</x-ui.button>
    <x-ui.button variant="outline" size="sm" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'success', title: 'Invoice sent', message: 'INV-2026-089 emailed to finance@tokomaju.id.' } }))">Success</x-ui.button>
    <x-ui.button variant="outline" size="sm" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'warning', title: 'Low stock', message: 'SKU-2048 has 8 units left in Gudang B.' } }))">Warning</x-ui.button>
    <x-ui.button variant="outline" size="sm" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'danger', title: 'Payment failed', message: 'Midtrans declined the Rp 2.450.000 charge.' } }))">Danger</x-ui.button>
</div>
<div class="flex flex-wrap gap-3">
    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm shadow-2xs" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'info', title: 'Sync queued', message: 'Stock levels for Gudang A will refresh in ~30s.' } }))">Info</button>
    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm shadow-2xs" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'success', title: 'Invoice sent', message: 'INV-2026-089 emailed to finance@tokomaju.id.' } }))">Success</button>
    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm shadow-2xs" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'warning', title: 'Low stock', message: 'SKU-2048 has 8 units left in Gudang B.' } }))">Warning</button>
    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm shadow-2xs" x-on:click="window.dispatchEvent(new CustomEvent('ui:toast', { detail: { type: 'danger', title: 'Payment failed', message: 'Midtrans declined the Rp 2.450.000 charge.' } }))">Danger</button>
</div>

Tooltip

Docs

Lightweight tooltip wrapper shown on hover and keyboard focus, dismissed by Escape without moving focus, linked via aria-describedby.

Basic tooltip

Top placement on a focusable button.

Copied!
24 units left in Gudang A
<x-ui.tooltip content="24 units left in Gudang A">
    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm text-fg shadow-2xs">
        Stock status
    </button>
</x-ui.tooltip>
<span
    class="relative inline-flex"
    x-data="{ open: false }"
    x-on:mouseenter="open = true"
    x-on:mouseleave="open = false"
    x-on:focusin="open = true"
    x-on:focusout="open = false"
    x-on:keydown.escape.stop="open = false"
>
    <button type="button" class="inline-flex h-9 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm text-fg shadow-2xs" aria-describedby="tip-basic">
        Stock status
    </button>
    <span
        id="tip-basic"
        role="tooltip"
        x-show="open"
        x-cloak
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="opacity-0 scale-95 translate-y-1"
        x-transition:enter-end="opacity-100 scale-100 translate-y-0"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="opacity-100 scale-100"
        x-transition:leave-end="opacity-0 scale-95"
        class="pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 rounded-control bg-fg px-2 py-1 text-xs text-fg-inverse shadow-md ring-1 ring-black/5"
    >
        24 units left in Gudang A
    </span>
</span>

Positions

top, bottom, left, and right placements.

Copied!
SKU-2048 Lead time 3d MOQ 12 USD 4.20
<div class="flex flex-wrap items-center justify-center gap-8 py-10">
    <x-ui.tooltip content="SKU-2048" position="top">
        <x-ui.button variant="outline" size="sm">Top</x-ui.button>
    </x-ui.tooltip>
    <x-ui.tooltip content="Lead time 3d" position="bottom">
        <x-ui.button variant="outline" size="sm">Bottom</x-ui.button>
    </x-ui.tooltip>
    <x-ui.tooltip content="MOQ 12" position="left">
        <x-ui.button variant="outline" size="sm">Left</x-ui.button>
    </x-ui.tooltip>
    <x-ui.tooltip content="USD 4.20" position="right">
        <x-ui.button variant="outline" size="sm">Right</x-ui.button>
    </x-ui.tooltip>
</div>
<div class="flex flex-wrap items-center justify-center gap-8 py-10">
    <span class="relative inline-flex" x-data="{ open: false }" x-on:mouseenter="open = true" x-on:mouseleave="open = false" x-on:focusin="open = true" x-on:focusout="open = false">
        <button type="button" class="inline-flex h-9 items-center rounded-control border border-border px-3.5 text-sm shadow-2xs" aria-describedby="tip-top">Top</button>
        <span id="tip-top" role="tooltip" x-show="open" x-cloak x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0 scale-95 translate-y-1" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="absolute bottom-full left-1/2 mb-2 -translate-x-1/2 rounded-control bg-fg px-2 py-1 text-xs text-fg-inverse shadow-md ring-1 ring-black/5">SKU-2048</span>
    </span>
    <span class="relative inline-flex" x-data="{ open: false }" x-on:mouseenter="open = true" x-on:mouseleave="open = false" x-on:focusin="open = true" x-on:focusout="open = false">
        <button type="button" class="inline-flex h-9 items-center rounded-control border border-border px-3.5 text-sm shadow-2xs" aria-describedby="tip-bottom">Bottom</button>
        <span id="tip-bottom" role="tooltip" x-show="open" x-cloak x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0 scale-95 -translate-y-1" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="absolute top-full left-1/2 mt-2 -translate-x-1/2 rounded-control bg-fg px-2 py-1 text-xs text-fg-inverse shadow-md ring-1 ring-black/5">Lead time 3d</span>
    </span>
    <span class="relative inline-flex" x-data="{ open: false }" x-on:mouseenter="open = true" x-on:mouseleave="open = false" x-on:focusin="open = true" x-on:focusout="open = false">
        <button type="button" class="inline-flex h-9 items-center rounded-control border border-border px-3.5 text-sm shadow-2xs" aria-describedby="tip-left">Left</button>
        <span id="tip-left" role="tooltip" x-show="open" x-cloak x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0 scale-95 translate-x-1" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="absolute right-full top-1/2 mr-2 -translate-y-1/2 rounded-control bg-fg px-2 py-1 text-xs text-fg-inverse shadow-md ring-1 ring-black/5">MOQ 12</span>
    </span>
    <span class="relative inline-flex" x-data="{ open: false }" x-on:mouseenter="open = true" x-on:mouseleave="open = false" x-on:focusin="open = true" x-on:focusout="open = false">
        <button type="button" class="inline-flex h-9 items-center rounded-control border border-border px-3.5 text-sm shadow-2xs" aria-describedby="tip-right">Right</button>
        <span id="tip-right" role="tooltip" x-show="open" x-cloak x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0 scale-95 -translate-x-1" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="absolute left-full top-1/2 ml-2 -translate-y-1/2 rounded-control bg-fg px-2 py-1 text-xs text-fg-inverse shadow-md ring-1 ring-black/5">USD 4.20</span>
    </span>
</div>