Button
A clickable action: five variants across six semantic tones.
Live demo
Button triggers an action or navigates. Visual treatment (variant) and semantic color (tone) are independent axes: any of the five variants (solid, outline, ghost, subtle, link) can carry any of the six tones (accent, neutral, danger, success, warn, info).
It renders a native <button> by default and an <a> when given an href, so the same component covers both actions and links. Icon slots, an icon-only square form, a loading state with an inline accessible spinner, and a full-width block mode round out the surface.
When to use
How this component composes with the rest of the set.
Props
13 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
href is set. | ||||
<a>. Dropped (with aria-disabled) when also disabled. | ||||
aria-disabled and removes href. | ||||
aria-busy, and blocks interaction. | ||||
block or an explicit width): start left-aligns the label and icons for a list-row button, end right-aligns them. | ||||
aria-pressed. Set it (pressed / pressed="true") for on, pressed="false" for off, and omit it entirely for a plain button. Controlled: flip it in your own click handler; the button reflects state, it does not self-toggle. | ||||
false to keep the control out of sequential focus navigation. A component rendering a native control makes it tab-reachable whether or not the app wants it: an app driving selection from its own keyboard cursor otherwise gets two focus rings that diverge, and Enter activates through both, firing one keypress twice. The host's own tabindex cannot express this, because focus lands on the inner control rather than the host. | ||||
aria-current; distinct from pressed's on/off toggle. aria-selected is only valid on tab / option / row roles, so a button carries aria-current instead. Set it (selected / selected="true") for selected, selected="false" for not, and omit it entirely for a button with no selection semantics. Controlled: flip it in your own click handler; the button reflects state, it does not self-toggle. |
Appearance
Variants
solid
Filled with the tone color; the primary, highest-emphasis treatment.
outline
Transparent fill with a tone-colored border and text.
ghost
No chrome until hover, then a neutral state tint; tone-colored text.
subtle
A soft tone-tinted background with tone-colored text.
link
Looks like a link: no box, no padding, underline on hover.
Sizes
xs
Densest, for compact toolbar pills.
sm
Compact.
md
Default.
lg
Large.
States
hover
Pointer over the button: overlay paints the hover tint.
active
Button pressed: overlay paints the press tint.
pressed
Toggle engaged: aria-pressed="true" paints the press overlay persistently, so an on toggle reads as sunken across every variant.
selected
Current within a set: aria-current="true" paints the selected overlay persistently, so a chosen pill reads as active across every variant.
focus-visible
Keyboard focus: a token-colored ring, plus a transparent outline that becomes real in forced-colors mode.
disabled
Non-interactive: muted fill and ink, overlay suppressed.
loading
Busy: content hidden behind the centered spinner, pointer set to progress.
Anatomy
The named parts that make up the component. A ::part() handle is reachable from your own stylesheet; the class beside it is the component's internal selector, which a shadow boundary keeps to itself.
root
The button or anchor element carrying the variant, tone, and size classes.
overlay
The pseudo-element behind the content that paints hover and active state tints.
icon
An icon slot rendered before or after the label (icon-start / icon-end).
label
The text content wrapper, hidden while loading so the spinner takes its place.
spinner
The inline accessible spinner shown while loading, drawn in currentColor.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent--accent-2--accent-2-bg--accent-2-fg--accent-2-text--accent-3--accent-3-bg--accent-3-fg--accent-3-text--accent-4--accent-4-bg--accent-4-fg--accent-4-text--accent-bg--accent-fg--accent-text--black--black-bg--black-fg--black-text--blue--blue-bg--blue-fg--blue-text--border-normal--border-thick--border-thin--brown--brown-bg--brown-fg--brown-text--cyan--cyan-bg--cyan-fg--cyan-text--danger--danger-bg--danger-fg--danger-text--duration-fast--duration-slow--ease-standard--fg-disabled--font-sans--gray--gray-bg--gray-fg--gray-text--green--green-bg--green-fg--green-text--info--info-bg--info-fg--info-text--leading-tight--neutral--neutral-bg--neutral-fg--neutral-text--orange--orange-bg--orange-fg--orange-text--pink--pink-bg--pink-fg--pink-text--purple--purple-bg--purple-fg--purple-text--radius-full--radius-md--radius-sm--red--red-bg--red-fg--red-text--ring--space-0--space-1--space-2--space-3--space-4--space-5--state-disabled--state-hover--state-press--state-selected--success--success-bg--success-fg--success-text--text-body--text-lg--text-sm--text-xs--warn--warn-bg--warn-fg--warn-text--weight-medium--white--white-bg--white-fg--white-text--yellow--yellow-bg--yellow-fg--yellow-textSlots
The button label.
An icon rendered before the label.
An icon rendered after the label.
Accessibility
Code
Variants and tones
The five variants and the six tones are independent. Mix freely.
<xtyle-button variant="solid" tone="accent">Save changes</xtyle-button>
<xtyle-button variant="outline" tone="neutral">Cancel</xtyle-button>
<xtyle-button variant="subtle" tone="danger" icon-only aria-label="Delete">
<svg slot="icon-start" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-3 6h12l-1 12H7L6 9Z" />
</svg>
</xtyle-button>
<xtyle-button variant="solid" tone="success" loading>Saving…</xtyle-button>
<xtyle-button variant="link" tone="info" href="/docs">Read the docs</xtyle-button><script lang="ts">
import { Button } from "@xtyle/svelte";
</script>
<Button variant="solid" tone="accent" onclick={() => save()}>Save changes</Button>
<Button variant="outline" tone="neutral">Cancel</Button>
<Button variant="subtle" tone="danger" iconOnly ariaLabel="Delete">
{#snippet iconStart()}
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-3 6h12l-1 12H7L6 9Z" />
</svg>
{/snippet}
</Button>
<Button variant="solid" tone="success" loading>Saving…</Button>
<Button variant="link" tone="info" href="/docs">Read the docs</Button>---
import { Button } from "@xtyle/astro";
---
<Button variant="solid" tone="accent">Save changes</Button>
<Button variant="outline" tone="neutral">Cancel</Button>
<Button variant="subtle" tone="danger" iconOnly aria-label="Delete">
<svg slot="icon-start" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-3 6h12l-1 12H7L6 9Z" />
</svg>
</Button>
<Button variant="link" tone="info" href="/docs">Read the docs</Button>List-row button
A full-width action row that runs a callback: block gives it the width, align="start" left-aligns the label, and a trailing icon reads as a chevron.
<xtyle-button block align="start" variant="ghost" tone="neutral">
<svg slot="icon-start" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M4 4h16v4H4V4Zm0 6h16v4H4v-4Zm0 6h10v4H4v-4Z" />
</svg>
Recent projects
<svg slot="icon-end" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="m9 6 6 6-6 6-1.4-1.4L11.2 12 7.6 8.4 9 6Z" />
</svg>
</xtyle-button><Button block align="start" variant="ghost" tone="neutral" onclick={() => openRecent()}>
{#snippet iconStart()}
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M4 4h16v4H4V4Zm0 6h16v4H4v-4Zm0 6h10v4H4v-4Z" />
</svg>
{/snippet}
Recent projects
{#snippet iconEnd()}
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="m9 6 6 6-6 6-1.4-1.4L11.2 12 7.6 8.4 9 6Z" />
</svg>
{/snippet}
</Button><Button block align="start" variant="ghost" tone="neutral">
<svg slot="icon-start" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path fill="currentColor" d="M4 4h16v4H4V4Zm0 6h16v4H4v-4Zm0 6h10v4H4v-4Z" />
</svg>
Recent projects
<svg slot="icon-end" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="m9 6 6 6-6 6-1.4-1.4L11.2 12 7.6 8.4 9 6Z" />
</svg>
</Button>