Skip to content

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

Copied!

Same-day cut-off is 14:00 WIB for Jabodetabek. Outer islands ship within 2 business days.

Start a return within 14 days of delivery. Damaged goods require photo proof for INV credit.

WhatsApp ops desk replies within one business day for stock and packing issues.
<x-ui.accordion type="single" default="sla">
    <x-ui.accordion.item id="sla" title="Fulfillment SLA">
        Same-day cut-off is 14:00 WIB for Jabodetabek. Outer islands ship within 2 business days.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="returns" title="Returns & credit memos">
        Start a return within 14 days of delivery. Damaged goods require photo proof for INV credit.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="support" title="Ops support">
        WhatsApp ops desk replies within one business day for stock and packing issues.
    </x-ui.accordion.item>
</x-ui.accordion>
<div class="divide-y divide-border rounded-card border border-border bg-surface-raised" x-data="{ openId: 'sla', 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 min-h-11 w-full items-center justify-between py-4 text-left text-sm font-medium text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-on:click="toggle('sla')" x-bind:aria-expanded="isOpen('sla').toString()" aria-controls="sla-panel" id="sla-header">
                <span>Fulfillment SLA</span>
            </button>
        </h3>
        <div id="sla-panel" role="region" aria-labelledby="sla-header" class="grid transition-[grid-template-rows] duration-200 ease-out" x-bind:class="isOpen('sla') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted leading-relaxed">Same-day cut-off is 14:00 WIB for Jabodetabek. Outer islands ship within 2 business days.</p>
            </div>
        </div>
    </div>
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex min-h-11 w-full items-center justify-between py-4 text-left text-sm font-medium text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-on:click="toggle('returns')" x-bind:aria-expanded="isOpen('returns').toString()" aria-controls="returns-panel" id="returns-header">
                <span>Returns & credit memos</span>
            </button>
        </h3>
        <div id="returns-panel" role="region" aria-labelledby="returns-header" class="grid transition-[grid-template-rows] duration-200 ease-out" x-bind:class="isOpen('returns') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted leading-relaxed">Start a return within 14 days of delivery. Damaged goods require photo proof for INV credit.</p>
            </div>
        </div>
    </div>
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex min-h-11 w-full items-center justify-between py-4 text-left text-sm font-medium text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-on:click="toggle('support')" x-bind:aria-expanded="isOpen('support').toString()" aria-controls="support-panel" id="support-header">
                <span>Ops support</span>
            </button>
        </h3>
        <div id="support-panel" role="region" aria-labelledby="support-header" class="grid transition-[grid-template-rows] duration-200 ease-out" x-bind:class="isOpen('support') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted leading-relaxed">WhatsApp ops desk replies within one business day for stock and packing issues.</p>
            </div>
        </div>
    </div>
</div>

Multiple type

Copied!

Subscribe to order.paid, order.packed, and invoice.voided events.

Standard workspaces share 60 requests per minute per API key.

Generate personal access tokens from Settings → Integrations.
<x-ui.accordion type="multiple" :default="['webhooks', 'limits']">
    <x-ui.accordion.item id="webhooks" title="Order webhooks">
        Subscribe to order.paid, order.packed, and invoice.voided events.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="limits" title="API rate limits">
        Standard workspaces share 60 requests per minute per API key.
    </x-ui.accordion.item>
    <x-ui.accordion.item id="tokens" title="Staff access tokens">
        Generate personal access tokens from Settings → Integrations.
    </x-ui.accordion.item>
</x-ui.accordion>
<div class="divide-y divide-border rounded-card border border-border bg-surface-raised" x-data="{ openIds: ['webhooks', 'limits'], toggle(id) { this.openIds = this.openIds.includes(id) ? this.openIds.filter((value) => value !== id) : [...this.openIds, id] }, isOpen(id) { return this.openIds.includes(id) } }">
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex min-h-11 w-full items-center justify-between py-4 text-left text-sm font-medium text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-on:click="toggle('webhooks')" x-bind:aria-expanded="isOpen('webhooks').toString()" aria-controls="webhooks-panel" id="webhooks-header">
                <span>Order webhooks</span>
            </button>
        </h3>
        <div id="webhooks-panel" role="region" aria-labelledby="webhooks-header" class="grid transition-[grid-template-rows] duration-200 ease-out" x-bind:class="isOpen('webhooks') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted leading-relaxed">Subscribe to order.paid, order.packed, and invoice.voided events.</p>
            </div>
        </div>
    </div>
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex min-h-11 w-full items-center justify-between py-4 text-left text-sm font-medium text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-on:click="toggle('limits')" x-bind:aria-expanded="isOpen('limits').toString()" aria-controls="limits-panel" id="limits-header">
                <span>API rate limits</span>
            </button>
        </h3>
        <div id="limits-panel" role="region" aria-labelledby="limits-header" class="grid transition-[grid-template-rows] duration-200 ease-out" x-bind:class="isOpen('limits') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted leading-relaxed">Standard workspaces share 60 requests per minute per API key.</p>
            </div>
        </div>
    </div>
    <div class="px-4">
        <h3 class="m-0">
            <button type="button" class="flex min-h-11 w-full items-center justify-between py-4 text-left text-sm font-medium text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-on:click="toggle('tokens')" x-bind:aria-expanded="isOpen('tokens').toString()" aria-controls="tokens-panel" id="tokens-header">
                <span>Staff access tokens</span>
            </button>
        </h3>
        <div id="tokens-panel" role="region" aria-labelledby="tokens-header" class="grid transition-[grid-template-rows] duration-200 ease-out" x-bind:class="isOpen('tokens') ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'">
            <div class="overflow-hidden">
                <p class="pb-4 text-sm text-fg-muted leading-relaxed">Generate personal access tokens from Settings → Integrations.</p>
            </div>
        </div>
    </div>
</div>

Alert

Docs

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

Variants

Copied!

e-Faktur sync queued

INV-2026-104 will upload to DJP within 15 minutes.

Payment recorded

Rp 2.450.000 from BCA VA matched ORD-2026-4418.

Stock below reorder

SKU GLASS-12 is at 8 units in Jakarta Pusat.
<div class="space-y-3">
    <x-ui.alert variant="info" title="e-Faktur sync queued">INV-2026-104 will upload to DJP within 15 minutes.</x-ui.alert>
    <x-ui.alert variant="success" title="Payment recorded">Rp 2.450.000 from BCA VA matched ORD-2026-4418.</x-ui.alert>
    <x-ui.alert variant="warning" title="Stock below reorder">SKU GLASS-12 is at 8 units in Jakarta Pusat.</x-ui.alert>
    <x-ui.alert variant="danger" title="Payout blocked">PO-8841 bank account failed validation. Review before approving.</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 shadow-2xs" role="status">
        <span class="mt-0.5 shrink-0 text-info" aria-hidden="true">i</span>
        <div class="min-w-0 flex-1">
            <p class="text-sm font-semibold tracking-tight text-fg">e-Faktur sync queued</p>
            <p class="mt-1 text-sm text-fg-muted leading-relaxed">INV-2026-104 will upload to DJP within 15 minutes.</p>
        </div>
    </div>
    <div class="flex gap-3 rounded-card border border-success/30 bg-success-soft p-4 shadow-2xs" role="status">
        <span class="mt-0.5 shrink-0 text-success" aria-hidden="true">✓</span>
        <div class="min-w-0 flex-1">
            <p class="text-sm font-semibold tracking-tight text-fg">Payment recorded</p>
            <p class="mt-1 text-sm text-fg-muted leading-relaxed">Rp 2.450.000 from BCA VA matched ORD-2026-4418.</p>
        </div>
    </div>
    <div class="flex gap-3 rounded-card border border-warning/30 bg-warning-soft p-4 shadow-2xs" role="status">
        <span class="mt-0.5 shrink-0 text-warning" aria-hidden="true">!</span>
        <div class="min-w-0 flex-1">
            <p class="text-sm font-semibold tracking-tight text-fg">Stock below reorder</p>
            <p class="mt-1 text-sm text-fg-muted leading-relaxed">SKU GLASS-12 is at 8 units in Jakarta Pusat.</p>
        </div>
    </div>
    <div class="flex gap-3 rounded-card border border-danger/30 bg-danger-soft p-4 shadow-2xs" role="alert">
        <span class="mt-0.5 shrink-0 text-danger" aria-hidden="true">!</span>
        <div class="min-w-0 flex-1">
            <p class="text-sm font-semibold tracking-tight text-fg">Payout blocked</p>
            <p class="mt-1 text-sm text-fg-muted leading-relaxed">PO-8841 bank account failed validation. Review before approving.</p>
        </div>
    </div>
</div>

Dismissible

Copied!

Warehouse capacity

Jakarta Pusat is at 92% bin utilization. Consider diverting ORD-2026-4500+ to Surabaya.
<x-ui.alert variant="warning" title="Warehouse capacity" dismissible>
    Jakarta Pusat is at 92% bin utilization. Consider diverting ORD-2026-4500+ to Surabaya.
</x-ui.alert>
<div
    class="flex gap-3 rounded-card border border-warning/30 bg-warning-soft p-4 shadow-2xs"
    role="status"
    x-data="{ show: true }"
    x-show="show"
    x-cloak
    x-transition:leave="transition ease-in duration-100"
    x-transition:leave-start="opacity-100"
    x-transition:leave-end="opacity-0"
>
    <span class="mt-0.5 shrink-0 text-warning" aria-hidden="true">!</span>
    <div class="min-w-0 flex-1">
        <p class="text-sm font-semibold tracking-tight text-fg">Warehouse capacity</p>
        <p class="mt-1 text-sm text-fg-muted leading-relaxed">Jakarta Pusat is at 92% bin utilization. Consider diverting ORD-2026-4500+ to Surabaya.</p>
    </div>
    <button
        type="button"
        class="inline-flex size-10 shrink-0 items-center justify-center rounded-control text-fg-muted transition-colors duration-150 hover:bg-surface/50 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        x-on:click="show = false"
        aria-label="Dismiss alert"
    >
        <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="M6 6l12 12M18 6 6 18" /></svg>
    </button>
</div>

Avatar

Docs

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

Customer avatars

Copied!
NK Status: Online AW Status: Busy SD Status: Offline
<div class="flex items-center gap-4">
    <x-ui.avatar name="Nadia Kusuma" alt="Nadia Kusuma - Finance Lead" status="online" />
    <x-ui.avatar name="Arif Wijaya" alt="Arif Wijaya - Warehouse Ops" status="busy" />
    <x-ui.avatar name="Sinta Dewi" alt="Sinta Dewi - Procurement" status="offline" />
</div>
<div class="flex items-center gap-4">
    <span class="relative inline-flex shrink-0">
        <span class="relative inline-flex size-10 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Nadia Kusuma - Finance Lead">NK</span>
        <span class="absolute right-0 bottom-0 size-2.5 rounded-full bg-success ring-2 ring-surface" aria-hidden="true" title="Online"></span>
        <span class="sr-only">Status: Online</span>
    </span>
    <span class="relative inline-flex shrink-0">
        <span class="relative inline-flex size-10 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Arif Wijaya - Warehouse Ops">AW</span>
        <span class="absolute right-0 bottom-0 size-2.5 rounded-full bg-warning ring-2 ring-surface" aria-hidden="true" title="Busy"></span>
        <span class="sr-only">Status: Busy</span>
    </span>
    <span class="relative inline-flex shrink-0">
        <span class="relative inline-flex size-10 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Sinta Dewi - Procurement">SD</span>
        <span class="absolute right-0 bottom-0 size-2.5 rounded-full bg-fg-subtle ring-2 ring-surface" aria-hidden="true" title="Offline"></span>
        <span class="sr-only">Status: Offline</span>
    </span>
</div>

Sizes

Copied!
NK AW SD RP DK
<div class="flex items-end gap-3">
    <x-ui.avatar size="xs" name="Nadia Kusuma" alt="Nadia Kusuma - Finance Lead" />
    <x-ui.avatar size="sm" name="Arif Wijaya" alt="Arif Wijaya - Warehouse Ops" />
    <x-ui.avatar size="md" name="Sinta Dewi" alt="Sinta Dewi - Procurement" />
    <x-ui.avatar size="lg" name="Rizky Pratama" alt="Rizky Pratama - Store Manager" />
    <x-ui.avatar size="xl" name="Dewi Kartika" alt="Dewi Kartika - Owner" />
</div>
<div class="flex items-end gap-3">
    <span class="relative inline-flex size-6 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-[10px] font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Nadia Kusuma - Finance Lead">NK</span>
    <span class="relative inline-flex size-8 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-xs font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Arif Wijaya - Warehouse Ops">AW</span>
    <span class="relative inline-flex size-10 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Sinta Dewi - Procurement">SD</span>
    <span class="relative inline-flex size-12 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-base font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Rizky Pratama - Store Manager">RP</span>
    <span class="relative inline-flex size-16 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-lg font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 after:content-['']" role="img" aria-label="Dewi Kartika - Owner">DK</span>
</div>

Avatar group

Copied!
NK AW SD +3
<x-ui.avatar.group>
    <x-ui.avatar name="Nadia Kusuma" alt="Nadia Kusuma - Finance Lead" class="z-[1]" />
    <x-ui.avatar name="Arif Wijaya" alt="Arif Wijaya - Warehouse Ops" class="z-[2]" />
    <x-ui.avatar name="Sinta Dewi" alt="Sinta Dewi - Procurement" 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 overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 after:content-[''] dark:after:ring-white/15" role="img" aria-label="Nadia Kusuma - Finance Lead">NK</span>
    <span class="relative z-[2] inline-flex size-10 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 after:content-[''] dark:after:ring-white/15" role="img" aria-label="Arif Wijaya - Warehouse Ops">AW</span>
    <span class="relative z-[3] inline-flex size-10 items-center justify-center overflow-hidden rounded-full bg-surface-sunken text-sm font-medium ring-2 ring-surface after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:ring-1 after:ring-inset after:ring-black/10 after:content-[''] dark:after:ring-white/15" role="img" aria-label="Sinta Dewi - Procurement">SD</span>
    <span class="relative z-[4] inline-flex size-10 items-center justify-center rounded-full bg-surface-sunken text-xs font-medium tabular-nums text-fg ring-2 ring-surface" aria-label="3 more staff">+3</span>
</div>

Badge

Docs

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

Invoice statuses

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 ring-1 ring-inset ring-current/15">
        <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 ring-1 ring-inset ring-current/15">
        <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 ring-1 ring-inset ring-current/15">
        <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 ring-1 ring-inset ring-current/15">
        <span class="size-1.5 rounded-full bg-info" aria-hidden="true"></span>
        Draft
    </span>
</div>

Variants and 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 items-center rounded-pill bg-brand px-2 py-0.5 text-xs font-medium tabular-nums text-on-brand shadow-2xs">Brand</span>
        <span class="inline-flex items-center rounded-pill bg-success px-2 py-0.5 text-xs font-medium tabular-nums text-on-success shadow-2xs">Success</span>
        <span class="inline-flex items-center rounded-pill border border-danger bg-transparent px-2 py-0.5 text-xs font-medium tabular-nums text-danger shadow-2xs">Danger</span>
    </div>
    <div class="flex flex-wrap gap-2">
        <span class="inline-flex items-center rounded-pill bg-surface-sunken px-1.5 py-0.5 text-[11px] font-medium tabular-nums text-fg shadow-2xs ring-1 ring-inset ring-current/15">Small</span>
        <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 ring-1 ring-inset ring-current/15">12 open</span>
    </div>
</div>

Removable filter chip

Copied!
Cabang Bandung
<div x-data="{ show: true }">
    <span x-show="show" x-cloak>
        <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"
        x-cloak
        class="inline-flex items-center gap-1.5 rounded-pill bg-brand/10 px-2 py-0.5 text-xs font-medium tabular-nums text-brand shadow-2xs ring-1 ring-inset ring-current/15"
    >
        Cabang Bandung
        <button
            type="button"
            class="relative -mr-0.5 ml-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-pill opacity-80 transition-colors hover:bg-black/10 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring dark:hover:bg-white/15 after:absolute after:-inset-2 after:content-['']"
            aria-label="Remove"
            x-on:click="show = false"
        >
            <svg class="size-3 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="M6 6l12 12M18 6 6 18" /></svg>
        </button>
    </span>
</div>

Breadcrumb

Docs

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

App path

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 items-center gap-1.5 overflow-x-auto whitespace-nowrap py-1 text-fg-muted">
        <li class="inline-flex max-w-[12rem] shrink-0 items-center gap-1.5 sm:max-w-none">
            <a href="/docs" class="truncate rounded-control text-fg-muted transition-colors duration-150 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">Penjualan</a>
            <span class="shrink-0 text-fg-subtle" aria-hidden="true">/</span>
        </li>
        <li class="inline-flex max-w-[12rem] shrink-0 items-center gap-1.5 sm:max-w-none">
            <a href="/docs/components" class="truncate rounded-control text-fg-muted transition-colors duration-150 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">Pesanan</a>
            <span class="shrink-0 text-fg-subtle" aria-hidden="true">/</span>
        </li>
        <li class="inline-flex max-w-[12rem] shrink-0 items-center gap-1.5 sm:max-w-none">
            <span class="truncate font-medium text-fg" aria-current="page">ORD-2201</span>
        </li>
    </ol>
</nav>

With home icon

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 items-center gap-1.5 overflow-x-auto whitespace-nowrap py-1 text-fg-muted">
        <li class="inline-flex shrink-0 items-center gap-1.5">
            <a href="/" class="inline-flex items-center gap-1 rounded-control text-fg-muted transition-colors duration-150 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">
                <span class="sr-only">Beranda</span>
                <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="M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1v-9.5Z" /></svg>
            </a>
            <span class="text-fg-subtle" aria-hidden="true">/</span>
        </li>
        <li class="inline-flex max-w-[12rem] shrink-0 items-center gap-1.5 sm:max-w-none">
            <a href="/docs" class="truncate rounded-control text-fg-muted transition-colors duration-150 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">Inventori</a>
            <span class="shrink-0 text-fg-subtle" aria-hidden="true">/</span>
        </li>
        <li class="inline-flex max-w-[12rem] shrink-0 items-center gap-1.5 sm:max-w-none">
            <span class="truncate 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

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-px [&_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-px [&_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 shadow-2xs transition duration-150 hover:bg-surface-sunken motion-safe:active:translate-y-px [&_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 motion-safe:active:translate-y-px [&_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-px [&_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

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

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 shadow-2xs transition duration-150 hover:bg-surface-sunken motion-safe:active:translate-y-px [&_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

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

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

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

Copied!

Create a tax invoice draft when ORD status becomes Paid.

<x-ui.checkbox
    name="e_invoice"
    value="1"
    label="Issue e-Faktur automatically"
    description="Create a tax invoice draft when ORD status becomes Paid."
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input
                id="e-invoice-basic"
                type="checkbox"
                name="e_invoice"
                value="1"
                aria-describedby="e-invoice-basic-description"
                class="peer absolute inset-0 z-10 size-full cursor-pointer opacity-0"
            >
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-control border border-border-strong bg-surface-raised text-on-brand shadow-2xs peer-checked:border-brand peer-checked:bg-brand peer-checked:[&_svg]:opacity-100 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <svg class="size-3 shrink-0 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 class="min-w-0 flex-1 pt-2">
            <label for="e-invoice-basic" class="block text-sm font-medium text-fg">Issue e-Faktur automatically</label>
            <p id="e-invoice-basic-description" class="mt-0.5 text-sm text-fg-muted leading-relaxed">Create a tax invoice draft when ORD status becomes Paid.</p>
        </div>
    </div>
</div>

Indeterminate

Copied!

3 of 12 orders on this page are selected for bulk packing.

<x-ui.checkbox
    name="select_all"
    label="Select all open orders"
    description="3 of 12 orders on this page are selected for bulk packing."
    indeterminate
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input
                id="select-all-orders"
                type="checkbox"
                name="select_all"
                class="peer absolute inset-0 z-10 size-full cursor-pointer opacity-0"
                x-data
                x-init="$el.indeterminate = true"
            >
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-control border border-border-strong bg-surface-raised text-on-brand shadow-2xs peer-indeterminate:border-brand peer-indeterminate:bg-brand peer-indeterminate:[&_svg]:opacity-100 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <svg class="size-3 shrink-0 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 class="min-w-0 flex-1 pt-2">
            <label for="select-all-orders" class="block text-sm font-medium text-fg">Select all open orders</label>
            <p class="mt-0.5 text-sm text-fg-muted leading-relaxed">3 of 12 orders on this page are selected for bulk packing.</p>
        </div>
    </div>
</div>

Validation error

Copied!

Finance requires confirmation before voiding a posted invoice.

<x-ui.checkbox
    name="terms"
    value="1"
    label="Confirm void of INV-2026-104"
    error="Finance requires confirmation before voiding a posted invoice."
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <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-full cursor-pointer opacity-0"
            >
            <span class="pointer-events-none flex size-4 items-center justify-center rounded-control border border-danger bg-surface-raised text-on-brand shadow-2xs peer-checked:border-brand peer-checked:bg-brand peer-checked:[&_svg]:opacity-100 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <svg class="size-3 shrink-0 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 class="min-w-0 flex-1 pt-2">
            <label for="terms-error" class="block text-sm font-medium text-fg">Confirm void of INV-2026-104</label>
        </div>
    </div>
    <p id="terms-error-error" 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.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z" /></svg>
        <span>Finance requires confirmation before voiding a posted invoice.</span>
    </p>
</div>

Dropdown

Docs

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

Basic menu

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.$nextTick(() => 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();
        },
        focusByTypeahead(char) {
            const list = this.items();
            if (!list.length || !char) return;
            const needle = char.toLowerCase();
            const current = list.indexOf(document.activeElement);
            const ordered = list.slice(current + 1).concat(list.slice(0, current + 1));
            ordered.find((el) => (el.textContent || '').trim().toLowerCase().startsWith(needle))?.focus();
        }
    }"
    x-on:click.outside="if (open) hide()"
    x-on:keydown.escape.window="if (open) hide()"
    x-on:keydown="
        if (!open) return;
        if ($event.key === 'ArrowDown') { $event.preventDefault(); focusByOffset(1); }
        else if ($event.key === 'ArrowUp') { $event.preventDefault(); focusByOffset(-1); }
        else if ($event.key.length === 1 && !$event.ctrlKey && !$event.metaKey && !$event.altKey) { $event.preventDefault(); focusByTypeahead($event.key); }
    "
>
    <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 motion-safe:active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        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-0.5"
        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-0.5"
        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-border/60"
    >
        <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-fg hover:bg-surface-sunken focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">Edit invoice</button>
        <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-fg hover:bg-surface-sunken focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">
            <span class="min-w-0 flex-1">Duplicate</span>
            <kbd class="ml-auto pl-4 font-mono text-[11px] tracking-tight 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 min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-danger hover:bg-danger-soft focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">Void invoice</button>
    </div>
</div>

Right aligned

Copied!
<div class="flex justify-end">
    <x-ui.dropdown align="right" label="Staff account">
        <x-ui.dropdown.item>Profile & store access</x-ui.dropdown.item>
        <x-ui.dropdown.item>Billing · IDR</x-ui.dropdown.item>
        <x-ui.dropdown.separator />
        <x-ui.dropdown.item variant="danger">Sign out</x-ui.dropdown.item>
    </x-ui.dropdown>
</div>
<div class="flex justify-end">
    <div
        class="relative inline-block text-left"
        x-data="{
            open: false,
            show() { this.open = true },
            hide() { this.open = false; this.$nextTick(() => this.$refs.trigger?.focus()) },
            toggle() { this.open ? this.hide() : this.show() },
        }"
        x-on:click.outside="if (open) hide()"
        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 motion-safe:active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
            aria-haspopup="menu"
            x-bind:aria-expanded="open.toString()"
            x-on:click="toggle()"
        >
            Staff account
            <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 9 6 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-0.5"
            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-0.5"
            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-border/60"
        >
            <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-fg hover:bg-surface-sunken focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">Profile & store access</button>
            <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-fg hover:bg-surface-sunken focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">Billing · IDR</button>
            <div class="my-1 h-px bg-border" role="separator"></div>
            <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-danger hover:bg-danger-soft focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">Sign out</button>
        </div>
    </div>
</div>

Disabled item

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,
        show() { this.open = true },
        hide() { this.open = false; this.$nextTick(() => this.$refs.trigger?.focus()) },
        toggle() { this.open ? this.hide() : this.show() },
    }"
    x-on:click.outside="if (open) hide()"
    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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        aria-haspopup="menu"
        x-bind:aria-expanded="open.toString()"
        x-on:click="toggle()"
    >
        More
    </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-0.5"
        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-0.5"
        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-border/60"
    >
        <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-fg hover:bg-surface-sunken focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">Share link</button>
        <button type="button" role="menuitem" tabindex="-1" disabled aria-disabled="true" class="flex min-h-10 w-full cursor-not-allowed items-center rounded-control px-3 py-2.5 text-left text-sm text-fg-subtle opacity-60">Archive (needs Owner)</button>
        <button type="button" role="menuitem" tabindex="-1" class="flex min-h-10 w-full items-center rounded-control px-3 py-2.5 text-left text-sm text-fg hover:bg-surface-sunken focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()" x-on:keydown.enter.prevent="hide()" x-on:keydown.space.prevent="hide()">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

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

Copied!
<x-ui.input
    type="password"
    name="password"
    label="Owner password"
    placeholder="At least 12 characters"
    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="At least 12 characters"
            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 min-w-10 items-center justify-center rounded-control px-3 text-fg-muted transition-colors duration-150 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
            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

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

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,
    trigger: null,
    show() {
        this.trigger = document.activeElement;
        this.open = true;
        this.$nextTick(() => {
            const panel = this.$refs.panel;
            const target = panel?.querySelector('[data-autofocus]') || panel?.querySelector('button');
            target?.focus?.();
        });
    },
    hide() {
        this.open = false;
        this.$nextTick(() => this.trigger?.focus?.());
    }
}">
    <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-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        x-on:click="show()"
    >
        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) hide()"
        >
            <div
                class="absolute inset-0 bg-overlay backdrop-blur-[2px]"
                x-show="open"
                x-cloak
                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="hide()"
            ></div>
            <div
                x-ref="panel"
                tabindex="-1"
                class="relative z-10 w-full max-w-lg rounded-card border border-border bg-surface-raised p-5 shadow-lg ring-1 ring-border/60 focus:outline-none"
                x-show="open"
                x-cloak
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0 scale-95 translate-y-2"
                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-2"
                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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()">Close</button>
                    <button type="button" data-autofocus class="inline-flex h-9 items-center rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-2xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" x-on:click="hide()">Send invoice</button>
                </div>
            </div>
        </div>
    </template>
</div>

Confirm variant

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

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
    class="flex flex-wrap items-center gap-3"
    x-data="{
        open: false,
        trigger: null,
        show() {
            this.trigger = document.activeElement;
            this.open = true;
            this.$nextTick(() => {
                const panel = this.$refs.panel;
                const autofocus = panel?.querySelector('[data-autofocus]');
                const primary = panel?.querySelector('[data-ui-modal-primary]');
                const focusable = panel?.querySelector(
                    'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\'-1\'])'
                );
                (autofocus || primary || focusable || panel)?.focus?.();
            });
        },
        hide() {
            this.open = false;
            this.$nextTick(() => {
                if (this.trigger && typeof this.trigger.focus === 'function') {
                    this.trigger.focus();
                }
            });
        },
    }"
    x-on:ui-modal-open.window="if ($event.detail?.id === 'demo-event') show()"
    x-on:keydown.escape.window="if (open) hide()"
>
    <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 shadow-2xs transition duration-150 hover:bg-surface-sunken motion-safe:active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
        x-on:click="window.dispatchEvent(new CustomEvent('ui-modal-open', { detail: { id: 'demo-event' } }))"
    >
        Open via event
    </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-event-title"
            aria-describedby="demo-event-description"
            x-trap.inert.noscroll="open"
        >
            <div
                class="absolute inset-0 bg-overlay backdrop-blur-[2px]"
                x-show="open"
                x-cloak
                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="hide()"
            ></div>

            <div
                x-ref="panel"
                tabindex="-1"
                class="relative z-10 w-full max-w-lg rounded-card border border-border bg-surface-raised p-5 shadow-lg ring-1 ring-border/60 focus:outline-none"
                x-show="open"
                x-cloak
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0 scale-95 translate-y-2"
                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-2"
                x-on:click.stop
            >
                <div class="flex items-start gap-3">
                    <div class="min-w-0 flex-1">
                        <h2 id="demo-event-title" class="text-lg font-semibold tracking-tight text-balance text-fg">Approval queue</h2>
                        <p id="demo-event-description" class="mt-1 text-sm text-fg-muted leading-relaxed text-pretty">Open INV-2026-089 from the finance toolbar without nesting the trigger.</p>
                    </div>
                    <button
                        type="button"
                        class="inline-flex size-10 shrink-0 items-center justify-center rounded-control text-fg-muted transition-colors duration-150 hover:bg-surface-sunken hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
                        x-on:click="hide()"
                        aria-label="Close dialog"
                    >
                        <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="M18 6 6 18M6 6l12 12" /></svg>
                    </button>
                </div>

                <div class="mt-4 text-sm text-fg leading-relaxed">
                    <p>Listen for the ui-modal-open window event and match detail.id.</p>
                </div>

                <div class="mt-6 flex flex-wrap justify-end gap-2">
                    <button
                        type="button"
                        data-ui-modal-primary
                        class="inline-flex h-9 items-center rounded-control border border-border bg-surface-sunken px-3.5 text-sm font-medium text-fg shadow-2xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
                        x-on:click="hide()"
                    >
                        Close
                    </button>
                </div>
            </div>
        </div>
    </template>
</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

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 ? 'true' : 'false'"
                    aria-controls="ui-navbar-drawer-demo"
                    x-bind:aria-label="mobileOpen ? 'Close navigation menu' : '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 ? 'true' : 'false'"
                aria-controls="ui-navbar-floating-demo"
                x-bind:aria-label="mobileOpen ? 'Close navigation menu' : '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

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 flex-wrap items-center gap-1">
        <li><a href="?page=2" class="inline-flex size-10 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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" aria-label="Previous page">‹</a></li>
        <li><a href="?page=1" class="inline-flex size-10 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">1</a></li>
        <li><span class="inline-flex size-10 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-10 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">2</a></li>
        <li><a href="?page=3" class="inline-flex size-10 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)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" aria-current="page">3</a></li>
        <li><a href="?page=4" class="inline-flex size-10 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">4</a></li>
        <li><span class="inline-flex size-10 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-10 items-center justify-center rounded-control border border-border bg-surface-raised text-sm tabular-nums shadow-2xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">5</a></li>
        <li><a href="?page=4" class="inline-flex size-10 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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" aria-label="Next page">›</a></li>
    </ul>
</nav>

Simple

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-10 items-center rounded-control border border-border bg-surface-raised px-3.5 text-sm shadow-2xs opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" aria-disabled="true" tabindex="-1">Previous</a>
        <a href="?page=2" class="inline-flex h-10 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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface">Next</a>
    </div>
</nav>

Radio

Docs

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

Inline group

Copied!
Invoice tax mode
<fieldset class="max-w-sm space-y-1">
    <legend class="mb-2 text-sm font-medium text-fg">Invoice tax mode</legend>
    <x-ui.radio name="tax_mode" value="inclusive" label="PPN inclusive" checked />
    <x-ui.radio name="tax_mode" value="exclusive" label="PPN exclusive" />
</fieldset>
<fieldset class="max-w-sm space-y-1">
    <legend class="mb-2 text-sm font-medium text-fg">Invoice tax mode</legend>
    <div class="inline-flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input id="tax-inclusive" type="radio" name="tax_mode" value="inclusive" checked class="peer absolute inset-0 z-10 size-full 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-2xs peer-checked:border-brand peer-checked:[&_.dot]:opacity-100 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <span class="dot size-2 rounded-full bg-brand opacity-0"></span>
            </span>
        </div>
        <label for="tax-inclusive" class="pt-2 text-sm font-medium text-fg">PPN inclusive</label>
    </div>
    <div class="inline-flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input id="tax-exclusive" type="radio" name="tax_mode" value="exclusive" class="peer absolute inset-0 z-10 size-full 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-2xs peer-checked:border-brand peer-checked:[&_.dot]:opacity-100 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <span class="dot size-2 rounded-full bg-brand opacity-0"></span>
            </span>
        </div>
        <label for="tax-exclusive" class="pt-2 text-sm font-medium text-fg">PPN exclusive</label>
    </div>
</fieldset>

Card variant

Copied!
Fulfillment warehouse
<fieldset class="grid max-w-xl gap-3 sm:grid-cols-2">
    <legend class="mb-1 text-sm font-medium text-fg">Fulfillment warehouse</legend>
    <x-ui.radio
        variant="card"
        name="warehouse"
        value="jkt"
        label="Jakarta Pusat"
        description="Same-day for Jabodetabek orders."
        checked
    />
    <x-ui.radio
        variant="card"
        name="warehouse"
        value="sby"
        label="Surabaya Timur"
        description="East Java hub with cold-chain bay."
    />
</fieldset>
<fieldset class="grid max-w-xl gap-3 sm:grid-cols-2">
    <legend class="mb-1 text-sm font-medium text-fg">Fulfillment warehouse</legend>
    <label class="flex cursor-pointer items-start gap-3 rounded-card border border-border bg-surface-raised p-4 shadow-2xs transition-colors duration-150 hover:border-border-strong has-[:checked]:border-brand has-[:checked]:bg-brand/5 focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-surface">
        <span class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input id="warehouse-jkt" type="radio" name="warehouse" value="jkt" checked class="peer absolute inset-0 z-10 size-full 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-2xs 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 class="min-w-0 flex-1 pt-2">
            <span class="block text-sm font-medium text-fg">Jakarta Pusat</span>
            <span class="mt-0.5 block text-sm text-fg-muted leading-relaxed">Same-day for Jabodetabek orders.</span>
        </span>
    </label>
    <label class="flex cursor-pointer items-start gap-3 rounded-card border border-border bg-surface-raised p-4 shadow-2xs transition-colors duration-150 hover:border-border-strong has-[:checked]:border-brand has-[:checked]:bg-brand/5 focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-surface">
        <span class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input id="warehouse-sby" type="radio" name="warehouse" value="sby" class="peer absolute inset-0 z-10 size-full 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-2xs 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 class="min-w-0 flex-1 pt-2">
            <span class="block text-sm font-medium text-fg">Surabaya Timur</span>
            <span class="mt-0.5 block text-sm text-fg-muted leading-relaxed">East Java hub with cold-chain bay.</span>
        </span>
    </label>
</fieldset>

Disabled 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

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" x-data="{ empty: true }">
    <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"
            x-on:change="empty = $event.target.value === ''"
            x-bind:class="empty ? 'text-fg-subtle' : 'text-fg'"
            class="h-9 w-full appearance-none rounded-control border border-border bg-surface-raised pl-3.5 pr-9 text-sm 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]:bg-surface-raised [&_option]:text-fg"
        >
            <option value="" disabled selected class="text-fg-subtle">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

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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface [&_option]:bg-surface-raised [&_option]:text-fg">
            <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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface [&_option]:bg-surface-raised [&_option]:text-fg">
            <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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface [&_option]:bg-surface-raised [&_option]:text-fg">
            <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

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

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

Copied!

Notify ops when SKU stock falls below reorder point.

<x-ui.switch
    name="low_stock_alerts"
    value="1"
    label="Low-stock warehouse alerts"
    description="Notify ops when SKU stock falls below reorder point."
    checked
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input
                id="low-stock-alerts"
                type="checkbox"
                role="switch"
                name="low_stock_alerts"
                value="1"
                checked
                aria-describedby="low-stock-alerts-description"
                class="peer absolute inset-0 z-10 size-full 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 duration-150 peer-checked:bg-brand peer-checked:[&_.thumb]:translate-x-5 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <span class="thumb block size-5 rounded-full bg-surface-raised shadow-sm transition-transform duration-150"></span>
            </span>
        </div>
        <div class="min-w-0 flex-1 pt-2">
            <label for="low-stock-alerts" class="block text-sm font-medium text-fg">Low-stock warehouse alerts</label>
            <p id="low-stock-alerts-description" class="mt-0.5 text-sm text-fg-muted leading-relaxed">Notify ops when SKU stock falls below reorder point.</p>
        </div>
    </div>
</div>

Sizes

Copied!
<div class="flex flex-col gap-2">
    <x-ui.switch size="sm" name="size_sm" aria-label="Compact low-stock alert" label="Compact (toolbar)" />
    <x-ui.switch size="md" name="size_md" aria-label="Default tax remittance reminder" label="Default (settings)" checked />
</div>
<div class="flex flex-col gap-2">
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input type="checkbox" role="switch" class="peer absolute inset-0 z-10 size-full cursor-pointer opacity-0" aria-label="Compact low-stock alert">
            <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 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <span class="thumb block size-4 rounded-full bg-surface-raised shadow-sm transition-transform duration-150"></span>
            </span>
        </div>
        <span class="pt-2 text-sm text-fg">Compact (toolbar)</span>
    </div>
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input type="checkbox" role="switch" checked class="peer absolute inset-0 z-10 size-full cursor-pointer opacity-0" aria-label="Default tax remittance reminder">
            <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 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface" aria-hidden="true">
                <span class="thumb block size-5 rounded-full bg-surface-raised shadow-sm transition-transform duration-150"></span>
            </span>
        </div>
        <span class="pt-2 text-sm text-fg">Default (settings)</span>
    </div>
</div>

Disabled

Copied!

Only finance owners can reopen a closed pay period.

<x-ui.switch
    name="payroll_lock"
    label="Lock payroll period"
    description="Only finance owners can reopen a closed pay period."
    disabled
    class="max-w-md"
/>
<div class="w-full max-w-md">
    <div class="flex items-start gap-3">
        <div class="relative -ml-1.5 inline-flex size-10 shrink-0 items-center justify-center">
            <input
                id="payroll-lock-disabled"
                type="checkbox"
                role="switch"
                name="payroll_lock"
                disabled
                class="peer absolute inset-0 z-10 size-full 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 class="min-w-0 flex-1 pt-2">
            <label for="payroll-lock-disabled" class="block text-sm font-medium text-fg">Lock payroll period</label>
            <p class="mt-0.5 text-sm text-fg-muted leading-relaxed">Only finance owners can reopen a closed pay period.</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

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

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

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

Copied!
Legal name, NPWP, and storefront contact for Toko Maju.
PPN rates, invoice numbering, and e-Faktur API credentials.
Invite cashiers and warehouse leads once billing is active.
<x-ui.tabs variant="underline" active="profile">
    <x-slot:list>
        <x-ui.tabs.tab id="profile">Store profile</x-ui.tabs.tab>
        <x-ui.tabs.tab id="tax">Tax & e-Faktur</x-ui.tabs.tab>
        <x-ui.tabs.tab id="team" disabled>Team roles</x-ui.tabs.tab>
    </x-slot:list>

    <x-ui.tabs.panel id="profile">Legal name, NPWP, and storefront contact for Toko Maju.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="tax">PPN rates, invoice numbering, and e-Faktur API credentials.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="team">Invite cashiers and warehouse leads once billing is active.</x-ui.tabs.panel>
</x-ui.tabs>
<div
    class="space-y-4"
    x-data="{ active: 'profile', 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="profile-tab" aria-controls="profile-panel" x-bind:aria-selected="(active === 'profile').toString()" x-bind:tabindex="active === 'profile' ? 0 : -1" class="min-h-10 border-b-2 px-3 py-2 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-bind:class="active === 'profile' ? 'border-brand text-fg' : 'border-transparent text-fg-muted'" x-on:click="select('profile')">Store profile</button>
        <button type="button" role="tab" id="tax-tab" aria-controls="tax-panel" x-bind:aria-selected="(active === 'tax').toString()" x-bind:tabindex="active === 'tax' ? 0 : -1" class="min-h-10 border-b-2 px-3 py-2 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-bind:class="active === 'tax' ? 'border-brand text-fg' : 'border-transparent text-fg-muted'" x-on:click="select('tax')">Tax & e-Faktur</button>
    </div>
    <div role="tabpanel" id="profile-panel" aria-labelledby="profile-tab" x-show="active === 'profile'" class="text-sm text-fg leading-relaxed">Legal name, NPWP, and storefront contact for Toko Maju.</div>
    <div role="tabpanel" id="tax-panel" aria-labelledby="tax-tab" x-show="active === 'tax'" x-cloak class="text-sm text-fg leading-relaxed">PPN rates, invoice numbering, and e-Faktur API credentials.</div>
</div>

Pills

Copied!
Open, packed, and shipped counts for today.
Inbound receipts and pick-list adjustments.
<x-ui.tabs variant="pills" active="pipeline">
    <x-slot:list>
        <x-ui.tabs.tab id="pipeline">Order pipeline</x-ui.tabs.tab>
        <x-ui.tabs.tab id="stock">Stock moves</x-ui.tabs.tab>
    </x-slot:list>

    <x-ui.tabs.panel id="pipeline">Open, packed, and shipped counts for today.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="stock">Inbound receipts and pick-list adjustments.</x-ui.tabs.panel>
</x-ui.tabs>
<div class="space-y-4" x-data="{ active: 'pipeline', select(id) { this.active = id } }">
    <div role="tablist" class="inline-flex flex-wrap gap-1 rounded-control bg-surface-sunken p-1">
        <button type="button" role="tab" x-bind:aria-selected="(active === 'pipeline').toString()" x-on:click="select('pipeline')" class="min-h-9 rounded-control px-3 py-2 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-bind:class="active === 'pipeline' ? 'bg-surface-raised text-fg shadow-sm' : 'text-fg-muted'">Order pipeline</button>
        <button type="button" role="tab" x-bind:aria-selected="(active === 'stock').toString()" x-on:click="select('stock')" class="min-h-9 rounded-control px-3 py-2 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-bind:class="active === 'stock' ? 'bg-surface-raised text-fg shadow-sm' : 'text-fg-muted'">Stock moves</button>
    </div>
    <div x-show="active === 'pipeline'" class="text-sm text-fg leading-relaxed">Open, packed, and shipped counts for today.</div>
    <div x-show="active === 'stock'" x-cloak class="text-sm text-fg leading-relaxed">Inbound receipts and pick-list adjustments.</div>
</div>

Vertical

Copied!
Currency IDR, timezone Asia/Jakarta, and receipt footer.
Owner passkeys and staff session timeout policies.
<x-ui.tabs variant="vertical" active="general">
    <x-slot:list>
        <x-ui.tabs.tab id="general">Store defaults</x-ui.tabs.tab>
        <x-ui.tabs.tab id="security">Access & 2FA</x-ui.tabs.tab>
    </x-slot:list>

    <x-ui.tabs.panel id="general">Currency IDR, timezone Asia/Jakarta, and receipt footer.</x-ui.tabs.panel>
    <x-ui.tabs.panel id="security">Owner passkeys and staff session timeout policies.</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-48 shrink-0 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="min-h-10 rounded-control px-3 py-2.5 text-left text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-bind:class="active === 'general' ? 'bg-surface-sunken text-fg' : 'text-fg-muted'">Store defaults</button>
        <button type="button" role="tab" x-bind:aria-selected="(active === 'security').toString()" x-on:click="select('security')" class="min-h-10 rounded-control px-3 py-2.5 text-left text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface" x-bind:class="active === 'security' ? 'bg-surface-sunken text-fg' : 'text-fg-muted'">Access & 2FA</button>
    </div>
    <div class="min-w-0 flex-1 text-sm text-fg leading-relaxed">
        <div x-show="active === 'general'">Currency IDR, timezone Asia/Jakarta, and receipt footer.</div>
        <div x-show="active === 'security'" x-cloak>Owner passkeys and staff session timeout policies.</div>
    </div>
</div>

Textarea

Docs

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

Basic textarea

Copied!

Visible on the pick list for ORD-2026-4418.

<x-ui.textarea
    name="fulfillment_notes"
    label="Fulfillment notes"
    hint="Visible on the pick list for ORD-2026-4418."
    placeholder="Packing instructions for warehouse staff (e.g. fragile glassware, include packing list)"
    rows="4"
    class="max-w-lg"
/>
<div class="w-full max-w-lg">
    <label for="fulfillment-notes-basic" class="mb-1.5 block text-sm font-medium text-fg">Fulfillment notes</label>
    <textarea
        id="fulfillment-notes-basic"
        name="fulfillment_notes"
        rows="4"
        placeholder="Packing instructions for warehouse staff (e.g. fragile glassware, include packing list)"
        aria-describedby="fulfillment-notes-basic-hint"
        class="min-h-24 w-full resize-y rounded-control border border-border bg-surface-raised px-3.5 py-2 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"
    ></textarea>
    <p id="fulfillment-notes-basic-hint" class="mt-1.5 text-xs text-fg-muted leading-relaxed">Visible on the pick list for ORD-2026-4418.</p>
</div>

Character counter

Copied!

0 / 120

<x-ui.textarea
    name="invoice_memo"
    label="Customer-facing invoice memo"
    maxlength="120"
    rows="3"
    class="max-w-lg"
/>
<div class="w-full max-w-lg" x-data="{ count: 0, max: 120 }">
    <label for="invoice-memo-counter" class="mb-1.5 block text-sm font-medium text-fg">Customer-facing invoice memo</label>
    <textarea
        id="invoice-memo-counter"
        name="invoice_memo"
        rows="3"
        maxlength="120"
        aria-describedby="invoice-memo-counter-counter"
        class="min-h-24 w-full resize-y rounded-control border border-border bg-surface-raised px-3.5 py-2 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"
        x-on:input="count = $event.target.value.length"
    ></textarea>
    <p id="invoice-memo-counter-counter" class="mt-1.5 text-right text-xs tabular-nums text-fg-subtle" aria-live="polite">
        <span x-text="count">0</span> / 120
    </p>
</div>

Validation error

Copied!

Add at least 20 characters so finance can audit INV-2026-104.

<x-ui.textarea
    name="credit_memo_reason"
    label="Credit memo reason"
    error="Add at least 20 characters so finance can audit INV-2026-104."
    rows="3"
    value="Damaged"
    class="max-w-lg"
/>
<div class="w-full max-w-lg">
    <label for="credit-memo-notes-error" class="mb-1.5 block text-sm font-medium text-fg">Credit memo reason</label>
    <textarea
        id="credit-memo-notes-error"
        name="credit_memo_reason"
        rows="3"
        aria-invalid="true"
        aria-describedby="credit-memo-notes-error-error"
        class="min-h-24 w-full resize-y rounded-control border border-danger bg-surface-raised px-3.5 py-2 text-sm text-fg shadow-2xs transition-colors duration-150 hover:border-danger 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"
    >Damaged</textarea>
    <p id="credit-memo-notes-error-error" 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.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z" /></svg>
        <span>Add at least 20 characters so finance can audit INV-2026-104.</span>
    </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

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

Copied!

Sync queued

Stock levels for Gudang A will refresh in ~30s.

Invoice sent

INV-2026-089 emailed to finance@tokomaju.id.

Low stock

SKU-2048 has 8 units left in Gudang B.

<div class="space-y-4">
    <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="grid gap-3 sm:grid-cols-2">
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-info bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="status">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-info-soft text-xs font-bold tabular-nums text-info" aria-hidden="true">i</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Sync queued</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">Stock levels for Gudang A will refresh in ~30s.</p>
                </div>
            </div>
        </div>
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-success bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="status">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-success-soft text-xs font-bold tabular-nums text-success" aria-hidden="true">✓</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Invoice sent</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">INV-2026-089 emailed to finance@tokomaju.id.</p>
                </div>
            </div>
        </div>
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-warning bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="status">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-warning-soft text-xs font-bold tabular-nums text-warning" aria-hidden="true">!</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Low stock</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">SKU-2048 has 8 units left in Gudang B.</p>
                </div>
            </div>
        </div>
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-danger bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="alert">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-danger-soft text-xs font-bold tabular-nums text-danger" aria-hidden="true">!</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Payment failed</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">Midtrans declined the Rp 2.450.000 charge.</p>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="space-y-4">
    <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>

    <div class="grid gap-3 sm:grid-cols-2">
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-info bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="status">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-info-soft text-xs font-bold tabular-nums text-info" aria-hidden="true">i</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Sync queued</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">Stock levels for Gudang A will refresh in ~30s.</p>
                </div>
            </div>
        </div>
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-success bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="status">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-success-soft text-xs font-bold tabular-nums text-success" aria-hidden="true">✓</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Invoice sent</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">INV-2026-089 emailed to finance@tokomaju.id.</p>
                </div>
            </div>
        </div>
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-warning bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="status">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-warning-soft text-xs font-bold tabular-nums text-warning" aria-hidden="true">!</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Low stock</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">SKU-2048 has 8 units left in Gudang B.</p>
                </div>
            </div>
        </div>
        <div class="relative overflow-hidden rounded-card border border-border border-l-4 border-l-danger bg-surface-raised p-4 shadow-lg ring-1 ring-border/60" role="alert">
            <div class="flex items-start gap-3">
                <span class="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-pill bg-danger-soft text-xs font-bold tabular-nums text-danger" aria-hidden="true">!</span>
                <div class="min-w-0 flex-1">
                    <p class="text-sm font-semibold tracking-tight text-fg">Payment failed</p>
                    <p class="mt-1 text-sm text-fg-muted leading-relaxed">Midtrans declined the Rp 2.450.000 charge.</p>
                </div>
            </div>
        </div>
    </div>
</div>

Tooltip

Docs

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

Basic tooltip

Copied!
<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,
        style: {},
        place() {
            const trigger = this.$refs.trigger;
            if (! trigger) return;
            const rect = trigger.getBoundingClientRect();
            this.style = {
                position: 'fixed',
                top: (rect.top - 8) + 'px',
                left: (rect.left + rect.width / 2) + 'px',
                transform: 'translate(-50%, -100%)',
            };
        },
        show() { this.open = true; this.$nextTick(() => this.place()); },
        hide() { this.open = false; },
    }"
    x-on:mouseenter="show()"
    x-on:mouseleave="hide()"
    x-on:focusin="show()"
    x-on:focusout="hide()"
    x-on:keydown.escape.stop="hide()"
>
    <button
        type="button"
        x-ref="trigger"
        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>
    <template x-teleport="body">
        <span
            id="tip-basic"
            role="tooltip"
            x-show="open"
            x-cloak
            x-bind:style="style"
            x-transition:enter="transition ease-out duration-150"
            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"
            class="pointer-events-none z-[70] w-max max-w-xs rounded-control bg-fg px-2.5 py-1.5 text-xs leading-snug text-fg-inverse shadow-md ring-1 ring-black/5"
        >
            24 units left in Gudang A
        </span>
    </template>
</span>

Positions

Copied!
<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,
            style: {},
            place() {
                const trigger = this.$refs.trigger;
                if (! trigger) return;
                const rect = trigger.getBoundingClientRect();
                this.style = { position: 'fixed', top: (rect.top - 8) + 'px', left: (rect.left + rect.width / 2) + 'px', transform: 'translate(-50%, -100%)' };
            },
            show() { this.open = true; this.$nextTick(() => this.place()); },
            hide() { this.open = false; },
        }"
        x-on:mouseenter="show()"
        x-on:mouseleave="hide()"
        x-on:focusin="show()"
        x-on:focusout="hide()"
    >
        <button type="button" x-ref="trigger" 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>
        <template x-teleport="body">
            <span id="tip-top" role="tooltip" x-show="open" x-cloak x-bind:style="style" x-transition:enter="transition ease-out duration-150" 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" class="pointer-events-none z-[70] w-max max-w-xs rounded-control bg-fg px-2.5 py-1.5 text-xs leading-snug text-fg-inverse shadow-md ring-1 ring-black/5">SKU-2048</span>
        </template>
    </span>

    <span
        class="relative inline-flex"
        x-data="{
            open: false,
            style: {},
            place() {
                const trigger = this.$refs.trigger;
                if (! trigger) return;
                const rect = trigger.getBoundingClientRect();
                this.style = { position: 'fixed', top: (rect.bottom + 8) + 'px', left: (rect.left + rect.width / 2) + 'px', transform: 'translateX(-50%)' };
            },
            show() { this.open = true; this.$nextTick(() => this.place()); },
            hide() { this.open = false; },
        }"
        x-on:mouseenter="show()"
        x-on:mouseleave="hide()"
        x-on:focusin="show()"
        x-on:focusout="hide()"
    >
        <button type="button" x-ref="trigger" 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>
        <template x-teleport="body">
            <span id="tip-bottom" role="tooltip" x-show="open" x-cloak x-bind:style="style" x-transition:enter="transition ease-out duration-150" 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" class="pointer-events-none z-[70] w-max max-w-xs rounded-control bg-fg px-2.5 py-1.5 text-xs leading-snug text-fg-inverse shadow-md ring-1 ring-black/5">Lead time 3d</span>
        </template>
    </span>

    <span
        class="relative inline-flex"
        x-data="{
            open: false,
            style: {},
            place() {
                const trigger = this.$refs.trigger;
                if (! trigger) return;
                const rect = trigger.getBoundingClientRect();
                this.style = { position: 'fixed', top: (rect.top + rect.height / 2) + 'px', left: (rect.left - 8) + 'px', transform: 'translate(-100%, -50%)' };
            },
            show() { this.open = true; this.$nextTick(() => this.place()); },
            hide() { this.open = false; },
        }"
        x-on:mouseenter="show()"
        x-on:mouseleave="hide()"
        x-on:focusin="show()"
        x-on:focusout="hide()"
    >
        <button type="button" x-ref="trigger" 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>
        <template x-teleport="body">
            <span id="tip-left" role="tooltip" x-show="open" x-cloak x-bind:style="style" x-transition:enter="transition ease-out duration-150" 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" class="pointer-events-none z-[70] w-max max-w-xs rounded-control bg-fg px-2.5 py-1.5 text-xs leading-snug text-fg-inverse shadow-md ring-1 ring-black/5">MOQ 12</span>
        </template>
    </span>

    <span
        class="relative inline-flex"
        x-data="{
            open: false,
            style: {},
            place() {
                const trigger = this.$refs.trigger;
                if (! trigger) return;
                const rect = trigger.getBoundingClientRect();
                this.style = { position: 'fixed', top: (rect.top + rect.height / 2) + 'px', left: (rect.right + 8) + 'px', transform: 'translateY(-50%)' };
            },
            show() { this.open = true; this.$nextTick(() => this.place()); },
            hide() { this.open = false; },
        }"
        x-on:mouseenter="show()"
        x-on:mouseleave="hide()"
        x-on:focusin="show()"
        x-on:focusout="hide()"
    >
        <button type="button" x-ref="trigger" 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>
        <template x-teleport="body">
            <span id="tip-right" role="tooltip" x-show="open" x-cloak x-bind:style="style" x-transition:enter="transition ease-out duration-150" 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" class="pointer-events-none z-[70] w-max max-w-xs rounded-control bg-fg px-2.5 py-1.5 text-xs leading-snug text-fg-inverse shadow-md ring-1 ring-black/5">USD 4.20</span>
        </template>
    </span>
</div>