Skip to content
Sveltier
Mode
Components

Button

Starts an action, or goes somewhere when it has a link. Six variants in any colour of the theme, sizes on the golden scale, and a loading state that keeps focus.

Usage

A Button is a <button type="button">, so it never submits a form by accident; pass type="submit" for the one that should. Every attribute you give it reaches the element.

Transfer.svelte
<script>
  import { Button } from 'sveltier';
</script>

<Button onclick={() => (open = true)}>New transfer</Button>
<Button variant="outlined" color="error">Cancel payment</Button>

Variants and colours

elevated sets the colour on a raised surface, flat fills with it, tonal tints with it, outlined adds a border, text is the colour alone, and plain has no hover or press wash. Every pairing meets WCAG AA contrast in all five theme packs, light and dark.

elevated flat tonal outlined text plain

Size and density

size moves the height half a step of φ and the text a quarter step; density tightens or loosens height and padding without touching the text. See Shared props.

Icons

prepend and append put content before and after the label. Icons are sized to φ^½ of the text. With icon, the button is square and holds only its icon, so give it an aria-label.

Toolbar.svelte
<Button>
  {#snippet prepend()}<PlusIcon />{/snippet}
  New transfer
</Button>

<!-- An icon alone needs a label for screen readers. -->
<Button icon variant="text" aria-label="Close"><CloseIcon /></Button>

Loading and disabled

loading shows a spinner over the label and ignores presses, while the button keeps its size and focus. Screen readers hear that it is loading. A disabled button cannot be focused or pressed.

A link A disabled link

Nothing paid yet. Press Pay invoice twice quickly: the second press is ignored while it loads.

PayInvoice.svelte
<script>
  import { Button } from 'sveltier';
  let paying = $state(false);

  async function pay() {
    paying = true;
    try {
      await payInvoice();
    } finally {
      paying = false;
    }
  }
</script>

<!-- Ignores presses while loading, so the invoice is paid once. -->
<Button loading={paying} onclick={pay}>Pay invoice</Button>

With href, the Button is an <a>: it navigates, opens in a new tab and works with your router like any link. A disabled link drops its href.

Nav.svelte
<!-- href makes it a link: middle-click, open in a new tab and
     client-side navigation work as they do for any link. -->
<Button href="/statements" variant="tonal">Statements</Button>

Defaults

Set Button props for a whole app or section with defaults on UIProvider or a DefaultsProvider. A prop on the Button itself still wins.

+layout.svelte
<UIProvider
  themes={monolith}
  defaults={{ Button: { variant: 'tonal', rounded: 'full' } }}
>
  <!-- Every Button below is tonal and round unless it says otherwise. -->
</UIProvider>

Accessibility

  • Enter and Space press a button; Enter follows a link. Both are reached with Tab.
  • Disabled buttons use the native disabled attribute and leave the tab order. A loading button stays focusable and is marked aria-disabled; you can do the same for a button that should stay discoverable while unavailable.
  • An icon button needs aria-label or aria-labelledby.
  • Hover and press are shown with a tint and a one-pixel press, and reduced motion keeps it still.

API

NameTypeDefaultDescription
variant'elevated' | 'flat' | 'tonal' | 'outlined' | 'text' | 'plain''flat'Visual weight.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Height and text size.
density'compact' | 'default' | 'comfortable''default'Multiplies the height and padding, never the text.
colorColor'primary'The colour the variant is painted with.
roundedboolean | 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'trueCorner rounding. true keeps the theme's radius.
blockbooleanfalseTakes the full width of its container.
disabledbooleanfalseA disabled button cannot be focused or pressed; a disabled link loses its href.
as'button' | 'a'–The element to render. Defaults to 'a' when href is given, otherwise 'button'.
iconbooleanfalseSquare, for a single icon as the content. Give it an aria-label.
loadingbooleanfalseShows a spinner and ignores presses, while the button keeps focus and its label for screen readers, which also hear that it is loading.
prependSnippet–Content before the label, usually an icon.
appendSnippet–Content after the label, usually an icon.
childrenSnippet–The label.
classClassValue | null–Classes added after the component's own: a string, an array or an object.
refHTMLButtonElement | HTMLAnchorElement | null–The root element, bindable with bind:ref.