motif svelteArc

Button ported

A clear, responsive action with quiet secondary states.

A clear, responsive action with quiet secondary states.

Live specimen

Loading demo…

Guidance

When to use

  • Any single action on a page, form, or dialog, such as Save, Continue, or Cancel.
  • Actions whose label changes in place, like Save to Saved, where the width should spring instead of jump.
  • Short async work where a spinner on the button is enough feedback, via loading.

When not to use

  • Use action-button when the button itself should show pending and success states after an async commit.
  • Use split-button when one default action has two to five close variants.
  • Use hold-to-confirm for destructive actions that need more than a single click.

Installation

pnpm dlx shadcn-svelte@latest add @arcui/button

Usage

<script lang="ts">
  import { Button } from '$lib/components/button';

  let saving = $state(false);

  async function save() {
    saving = true;
    await submit(); // your async work
    saving = false;
  }
</script>

<Button variant="primary" loading={saving} onclick={save}>
  Save changes
</Button>

Variants & Examples

Visual weights

One primary action per surface, with secondary, ghost, and danger as the quiet alternatives.

<Button variant="primary">Save changes</Button>
<Button variant="secondary">Preview</Button>
<Button variant="ghost">Cancel</Button>
<Button variant="danger">Delete</Button>

Label that morphs after saving

Pass changing children to get the width morph for free — the width springs to fit the new label.

<Button variant="secondary" loading={saving} onclick={save}>
  {saved ? 'Saved' : 'Save draft'}
</Button>

Loading state

loading shows a spinner, sets aria-busy, and swallows clicks while keeping keyboard focus.

<Button variant="primary" loading={true}>Saving changes…</Button>

API Reference

PropTypeDefaultDescription
variant"primary" | "secondary" | "ghost" | "danger"'primary'Visual weight. Use one primary action per surface.
size"sm" | "md" | "lg"'md'Height and padding.
loadingbooleanfalseShows a spinner, sets aria-busy, and swallows clicks while keeping focus.
...propsHTMLButtonAttributes–Forwarded to the underlying native button (disabled, type, onclick, aria-*). ref binds the element.

Keyboard Interactions

KeyAction
Enter / SpaceActivates the button.

Accessibility

  • Renders a native button, so role and focus come for free.
  • Loading uses aria-busy and aria-disabled instead of disabled, so keyboard focus is not lost mid-action.
  • Icon-only buttons need an aria-label.

Motion

- Presses scale to about 0.97 on a snappy spring; icon-sized buttons press slightly deeper. - A new label crossfades with a short blur while the width springs to fit. - Reduced motion drops the press scale and swaps labels with a plain fade.

Notes for AI

  • Default choice for any single action. Use action-button for dense icon toolbars and split-button when one action has close alternatives.
  • Pass a changing label (Save → Saved) as children to get the width morph for free.
  • Wrap in a popup trigger (e.g. a bits-ui Trigger child snippet); the press scale turns off automatically for popup anchors.

Source of truth: registry/components/button/button.tsx