Button ported
A clear, responsive action with quiet secondary states.
A clear, responsive action with quiet secondary states.
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.
pnpm dlx shadcn-svelte@latest add @arcui/buttonUsage
<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
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "ghost" | "danger" | 'primary' | Visual weight. Use one primary action per surface. |
size | "sm" | "md" | "lg" | 'md' | Height and padding. |
loading | boolean | false | Shows a spinner, sets aria-busy, and swallows clicks while keeping focus. |
...props | HTMLButtonAttributes | – | Forwarded to the underlying native button (disabled, type, onclick, aria-*). ref binds the element. |
Keyboard Interactions
| Key | Action |
|---|---|
Enter / Space | Activates 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