Skip to main content
WireKit
Copy for LLM

Button

A flexible button component composed from intent (semantic color) and surface (visual style), with sizes, icons, and a loading state.

Live Sandbox

The "Live preview" toggle on the block below swaps the static rendered HTML for a hydrated Livewire instance — every prop in the schema becomes editable from the iframe and the result re-renders server-side. Toggle off to return to the static preview.

Sandbox

Basic Usage

Common shapes

Sizes

Sizes
A button that is really a link

Renders as <a> instead of <button>.

Loading State

<x-wirekit::button loading wire:click="save">Save changes</x-wirekit::button>

The loading prop adds a spinner that is visible only during Livewire requests (via wire:loading). The button is also automatically disabled while loading to prevent double-clicks.

On a page that polls (wire:poll) or has several action buttons, add loading-target so the spinner only reacts to this button's own action instead of flashing on every commit:

<x-wirekit::button loading wire:click="redeliver('{{ $id }}')" loading-target="redeliver">Retry</x-wirekit::button>

The preview below simulates that behavior with Alpine — clicking the button flips into a 2-second "saving" state so you can see the spinner + disabled treatment without a Livewire backend:

Click to simulate a Livewire request

Click the button.

Icons

With WireKit Icon Component

Use the iconLeft or iconRight slots together with the Icon component:

Buttons with icon aliases

With Custom SVG

You can also pass raw SVGs for full control — use the iconLeft / iconRight slots and size the SVG with an inline style="width: 1rem; height: 1rem;" (1rem = Tailwind w-4 h-4) so the glyph matches the default icon sizing:

Single button with raw SVG

Mix the WireKit <x-wirekit::icon> component with raw SVGs in the same button row — the two sizing approaches (class="w-4 h-4" on the icon component vs style="width: 1rem; height: 1rem;" on the raw SVG) render identically, so they can be used side by side:

Icon component and raw SVG side by side

Intent × Surface

The button is composed from two orthogonal props: intent selects the semantic color (primary, neutral, success, warning, danger, info) and surface selects the visual style (filled, outline, soft, ghost, link). Any combination is valid — the same color reads as a strong action when filled, a neutral chrome when outlined, and a tertiary affordance when soft or ghost.

Primary intent across surfaces
Danger intent across surfaces
All intents with filled surface

Button Group

Wrap buttons in <x-wirekit::button.group> to join them into a single segmented control — inner corners are squared, the shared border seam collapses to one line, and the active button rises above the seam. Add label for the group's accessible name, and orientation="vertical" to stack instead.

Joined button group
Vertical button group

The same wrapper joins an input with a trailing button — an attached search field, a newsletter signup, or a copy-link row:

Input with attached button

Width & Layout

Buttons auto-size to their content by default. Use Tailwind classes for full-width or fixed-width buttons:

{{-- Full-width button --}}
<x-wirekit::button class="w-full">Submit</x-wirekit::button>

{{-- Fixed width --}}
<x-wirekit::button class="w-48">Save Changes</x-wirekit::button>

Height is controlled by the size prop (xs, sm, md, lg).

Touch targets xs (~28px tall) sits below the ~44px comfortable touch-target size. Reach for it in dense desktop UIs — toolbars, table-row actions, filter chips — not for a primary action a finger has to hit on a phone, where md or larger keeps the target roomy.

Props

Prop Type Default Description
intent string 'primary' Semantic intent: primary, neutral, success, warning, danger, info
surface string 'filled' Visual surface: filled, outline, soft, ghost, link
size string 'md' xs, sm, md, lg, xl
type string 'button' HTML button type
href string|null null Renders as <a> when set
disabled bool false Disables the button
loading bool false Shows spinner during Livewire requests (auto-detects wire:click context)
loadingTarget string|null null Scopes the spinner + disable to this button's own Livewire action (e.g. loading-target="redeliver"). Without it, an untargeted wire:loading flashes the spinner on every commit — including wire:poll refreshes and unrelated actions. Opt-in; omit for the current untargeted behavior.
forceLoading bool false Renders the spinner unconditionally and disables the button regardless of any wire:loading gate. Useful for static documentation demos, non-Livewire contexts, or stories that want to preview the loading state without a backend request in flight.
scope string|null null Scoped personalization name

<x-wirekit::button.group>

Prop Type Default Description
orientation string 'horizontal' 'horizontal' (join left-to-right) or 'vertical' (stack)
label string|null null Accessible name for the group (role="group")
scope string|null null Scoped personalization name

Slots

Slot Description
default Button label text
iconLeft Content rendered before the label (e.g. SVG icon)
iconRight Content rendered after the label (e.g. SVG icon)

Accessibility

The button component uses native <button> semantics (or <a> when href is set), so browsers and assistive technology handle roles, focus, and activation automatically.

  • type="button" — Every <button> renders with type="button" by default, preventing accidental form submission. Override with type="submit" when needed.

  • Disabled state — The disabled prop sets the native disabled attribute. The component applies opacity-[var(--opacity-wk-disabled)] and pointer-events-none so the button is visually muted and unreachable by pointer.

  • Focus ring — Uses focus-visible (not focus) so the ring only appears for keyboard navigation, never for mouse clicks.

  • Loading state — When loading is set, the button is automatically disabled during Livewire requests via wire:loading.attr="disabled", preventing double submissions. The spinner SVG includes aria-hidden="true".

  • Icon-only buttons — When a button has no visible text label (icon only), add an aria-label attribute:

    <x-wirekit::button intent="neutral" surface="ghost" aria-label="Close">
        <x-slot:iconLeft><x-wirekit::icon name="close" class="w-4 h-4" /></x-slot:iconLeft>
    </x-wirekit::button>
    
  • External links — When href and target="_blank" are used together, the component automatically injects rel="noopener noreferrer" (prevents tabnabbing) and a screen-reader hint "(opens in new tab)".

  • KeyboardEnter and Space activate the button (native browser behavior). No custom key handlers needed.

Keyboard Interaction

Key Action
Tab Move focus to the button
Enter / Space Activate the button

Pitfalls

  • Don't omit type="button" inside a <form>. The HTML default is submit — clicking the button will submit the surrounding form even if you wired a wire:click handler. The component already injects type="button" for non-submit usage, but if you splat {{ $attributes }} over a wrapper, double-check the rendered HTML.
  • Don't hardcode class="bg-..." to recolor. Use intent="primary|neutral|danger|..." (optionally combined with surface=) so the component picks the right token-based foreground/background pair (auto-switches in dark mode). Hardcoded Tailwind colors bypass theming.
  • Don't add aria-label to a button with visible text. Screen readers read the visible label; the aria-label overrides it and creates a contradiction. Only set aria-label on icon-only buttons.

Design Tokens

The button derives its color palette from intent × surface via Pushery\WireKit\VariantResolver — see theming.md for the mapping. The chrome below is shared across every combination:

Token Used for
--font-wk-sans Button font family
--font-wk-body-weight Font weight
--font-wk-letter-spacing Letter spacing
--font-wk-line-height Line height
--text-wk-sm / --text-wk-md / --text-wk-lg Font size per size prop
--size-wk-sm / --size-wk-md / --size-wk-lg Button height per size prop
--padding-wk-x-sm / --padding-wk-x-md / --padding-wk-x-lg Horizontal padding per size
--radius-wk-sm / --radius-wk-md / --radius-wk-lg Border radius per size
--border-wk-width Border width
--color-wk-ring / --color-wk-ring-offset Focus ring color + offset
--ring-wk-width / --ring-wk-offset Focus ring geometry
--opacity-wk-disabled Disabled visual weight
--transition-wk-duration / --transition-wk-easing Hover / focus transition

Usage & Conventions

Prop conventions — this component uses one or more of the shared semantic prop names (intent / variant / tone / surface). See Prop naming conventions for the canonical vocabulary, alias matrix, and decision tree.

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store