Button
A button or a link that looks like one, in six variants and four sizes.
Variants
Six intents. primary is the accent; danger is for destructive actions.
Code
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button variant="danger">Delete</Button>Usage
import { Button } from "@elements/ui/button";
<Button variant="primary" onclick={() => save(form)}>Save changes</Button>
<Button variant="ghost" icon label="Close"><Icon name="x" /></Button>
<Button href="/docs" variant="link">Read the docs</Button>
<Button variant="primary" loading={state.saving}>Save</Button>Examples
Sizes
Heights come from the theme's control tokens.
Code
<Button variant="primary" size="xs">Extra small</Button>
<Button variant="primary" size="sm">Small</Button>
<Button variant="primary">Medium</Button>
<Button variant="primary" size="lg">Large</Button>
<Button icon size="xs" label="Add"><Icon name="plus" /></Button>
<Button icon size="lg" label="Add"><Icon name="plus" /></Button>Rounded
round makes a pill, or a circle with icon.
Code
<Button variant="primary" round>Subscribe</Button>
<Button icon round label="Next"><Icon name="arrow-right" /></Button>Icons
icon makes a square button and needs a label. Icons beside text size themselves.
Code
<Button icon label="Settings"><Icon name="settings" /></Button>
<Button variant="ghost" icon label="More"><Icon name="more-horizontal" /></Button>
<Button variant="secondary"><Icon name="mail" /> Email</Button>
<Button variant="primary">Next <Icon name="arrow-right" /></Button>Loading
The label is hidden, not removed, so the width holds. Focus stays and clicks are ignored.
Code
<Button variant="primary" loading={state.saving} onclick={() => save(state)}>
Save changes
</Button>Announce the busy label
Some screen readers do not announce a change to the text inside a button. Point aria-labelledby at the label, and its new text becomes the button's name. Both labels share one cell, so the width holds.
Code
<Button variant="primary" loading={state.submitting} attrs={{ "aria-labelledby": "submit-label" }}>
<span id="submit-label" class="swap-label" data-state={state.submitting ? "on" : "off"}>
<span class="swap-off">Submit</span>
<span class="swap-on">Submitting</span>
</span>
</Button>Loading never resizes
Every variant and size, with and without an icon: entering and leaving loading keeps the box identical.
Code
<Button variant="primary" size="sm" loading={state.loading}>Save</Button>
<Button variant="outline" icon label="Refresh" loading={state.loading}><Icon name="loader" /></Button>Disabled and links
href renders an <a>. A disabled link has no href and leaves the tab order.
Code
<Button disabled>Disabled</Button>
<Button variant="primary" disabled>Disabled</Button>
<Button href="#examples">Link as button</Button>
<Button href="#examples" disabled>Disabled link</Button>Focusable when disabled
focusableWhenDisabled keeps a disabled button in the tab order with aria-disabled, so focus is not lost while a request runs.
Code
<Button variant="primary" disabled={state.pending} focusableWhenDisabled
onclick={() => submit(state)}>Submit</Button>Right to left
Under dir=rtl the icon and label follow the reading direction.
Code
<div dir="rtl">
<Button variant="secondary"><Icon name="mail" /> بريد</Button>
</div>Other elements
buttonClass() gives any element the button look, such as a label for a file input.
Code
<label class={buttonClass("secondary")}>
<input type="file" class="sr-only"> Upload
</label>Accessible name and description
The name comes from the content, label, or aria-labelledby through attrs; aria-describedby adds a description.
Code
<Button attrs={{ "aria-labelledby": "plan-heading", "aria-describedby": "plan-note" }}>
Choose
</Button>In a form
type=submit submits on click and on Enter; loading blocks a second submit.
Code
<form onsubmit={() => subscribe(state)}>
<input type="email" value={state.email} placeholder="you@example.com">
<Button type="submit" variant="primary" block loading={state.saving}>Subscribe</Button>
</form>Invalid
aria-invalid marks a button that stands for a field with an error, such as a date picker's trigger.
Code
<Button attrs={{ "aria-invalid": "true" }}>Pick a date</Button>Opens a dialog
APG: a button that opens a dialog ends its label with an ellipsis, so the label says more input follows.
Code
<Dialog title="Rename file">
<Button slot="trigger">Rename…</Button>
<input aria-label="Name" value="report.pdf">
<DialogFooter>
<DialogClose><Button>Cancel</Button></DialogClose>
<DialogClose><Button variant="primary">Rename</Button></DialogClose>
</DialogFooter>
</Dialog>