actions
Button
Polymorphic button and link control with variants, sizes, loading, and disabled states.
Copied!
Examples
Variants
Primary, secondary, outline, ghost, danger, and link styles.
Copied!
<div class="flex flex-wrap items-center gap-3">
<x-ui.button variant="primary">Save invoice</x-ui.button>
<x-ui.button variant="secondary">Draft</x-ui.button>
<x-ui.button variant="outline">Export CSV</x-ui.button>
<x-ui.button variant="ghost">Cancel</x-ui.button>
<x-ui.button variant="danger">Void invoice</x-ui.button>
<x-ui.button variant="link">View ledger</x-ui.button>
</div>
<div class="flex flex-wrap items-center gap-3">
<button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Save invoice
</button>
<button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border bg-surface-sunken px-3.5 text-sm font-medium text-fg shadow-2xs transition duration-150 hover:bg-border/60 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Draft
</button>
<button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border-strong bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Export CSV
</button>
<button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken [&_svg]:shrink-0">
Cancel
</button>
<button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-danger px-3.5 text-sm font-medium text-on-danger shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Void invoice
</button>
<button type="button" class="inline-flex items-center justify-center font-medium text-brand underline-offset-4 hover:underline">
View ledger
</button>
</div>
Sizes
Compact through large, plus icon-only sizes.
Copied!
<div class="flex flex-wrap items-center gap-3">
<x-ui.button size="sm">Small</x-ui.button>
<x-ui.button size="md">Medium</x-ui.button>
<x-ui.button size="lg">Large</x-ui.button>
<x-ui.button size="icon-sm" aria-label="Icon small">
<x-icons.check class="size-4" />
</x-ui.button>
</div>
<div class="flex flex-wrap items-center gap-3">
<button type="button" class="inline-flex h-8 items-center justify-center gap-1.5 rounded-control bg-brand px-2.5 text-xs font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Small
</button>
<button type="button" class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Medium
</button>
<button type="button" class="inline-flex h-10 items-center justify-center gap-2 rounded-control bg-brand px-4 text-sm font-medium text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px] [&_svg]:shrink-0">
Large
</button>
<button type="button" aria-label="Mark as paid" class="inline-flex size-9 items-center justify-center rounded-control bg-brand text-on-brand shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] transition duration-150 hover:opacity-90 motion-safe:active:translate-y-[0.5px]">
<svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12.5 9.5 17 19 7" />
</svg>
</button>
</div>
States
Disabled, loading, and polymorphic link usage.
Copied!
<div class="flex flex-wrap items-center gap-3">
<x-ui.button disabled>Syncing stock</x-ui.button>
<x-ui.button loading>Approving</x-ui.button>
<x-ui.button variant="outline" href="/docs">Open docs</x-ui.button>
</div>
<div class="flex flex-wrap items-center gap-3">
<button type="button" disabled class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand opacity-50 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] [&_svg]:shrink-0">
Syncing stock
</button>
<button type="button" aria-busy="true" disabled class="inline-flex h-9 items-center justify-center gap-2 rounded-control bg-brand px-3.5 text-sm font-medium text-on-brand opacity-50 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_1px_2px_0_rgba(0,0,0,0.06)] [&_svg]:shrink-0">
<svg class="size-4 shrink-0 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3a9 9 0 1 0 9 9" />
</svg>
Approving
</button>
<a href="/docs" class="inline-flex h-9 items-center justify-center gap-2 rounded-control border border-border-strong bg-transparent px-3.5 text-sm font-medium text-fg transition duration-150 hover:bg-surface-sunken [&_svg]:shrink-0">
Open docs
</a>
</div>
Props API
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | primary|secondary|outline|ghost|danger|link | primary | Visual style mapped to complete static class strings. |
| size | sm|md|lg|icon-sm|icon-md|icon-lg | md | Control height and padding, including icon-only sizes. |
| href | string|null | null | When set, renders an anchor instead of a button. |
| type | button|submit|reset | button | Native button type when href is absent. |
| disabled | bool | false | Disables interaction. Anchors use aria-disabled and tabindex=-1. |
| loading | bool | false | Shows a spinner, sets aria-busy, and prevents double activation. |
Accessibility notes
Architected with WCAG 2.2 AA patterns in mind. These notes document the exact keyboard and ARIA behavior for this component.
- Renders a native button with an explicit type, or an anchor when href is provided.
- Disabled anchors expose aria-disabled="true" and tabindex="-1" instead of relying on the disabled attribute.
- Loading state sets aria-busy="true", shows a decorative spinner (aria-hidden), and keeps the control non-interactive.
- Focus-visible outline comes from the global :focus-visible rule using --ui-ring.
- Keyboard: Tab focuses the control. Enter and Space activate buttons. Enter activates links.
Built by Praesepe
Architecture & custom software partner
Praesepe uses these exact UI primitives to ship SME dashboards, inventory systems, invoicing desks, and internal tools faster, without rebuilding a design system for every client.
- Same Blade + Tailwind 4 + Alpine 3 stack you see in this docs site
- Opinionated for Indonesian SME ops: invoices, stock, roles, approvals
- Architected with WCAG 2.2 AA patterns in mind for keyboard and ARIA