Skip to content
Praesepe UI

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

Props API

Component props
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

Talk to the studio

WhatsApp: TODO Email the studio Visit Praesepe