Button
A clickable interactive element with the lily click-scale feel.
Installation
Installs the style saved in lily.json. For a new Diamond project,
run lily-svelte init --style diamond first.
Install the lily base and `utils` (run once per project).
npx lily-svelte@latest init Copy the button source from the registry into $lib/components/ui/button.
Usage
<script lang="ts">
import { Button } from '$lib/components/ui/button';
</script>
<Button>Click me</Button> Variants
solid, soft and ghost are three weights of the same button — loudest to quietest. Give a
screen one solid button and let everything else step down from there. destructive is a
meaning, not a weight, so it sits outside the ladder.
<Button>Solid</Button>
<Button variant="soft">Soft</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button> ghost is transparent until hovered, which is what a row of icon buttons needs — with a resting
tint, a toolbar of eight reads as eight filled chips.
Sizes
<Button size="sm">Small</Button>
<Button>Default</Button>
<Button size="lg">Large</Button> Icon-only buttons are square and come in the same three steps, so they line up with the text button beside them.
<Button size="icon-sm" variant="ghost" aria-label="Add">…</Button>
<Button size="icon" variant="ghost" aria-label="Add">…</Button>
<Button size="icon-lg" variant="ghost" aria-label="Add">…</Button> Loading
loading shows a spinner and swallows clicks while an action is in flight.
<script lang="ts">
let saving = $state(false);
async function save() {
saving = true;
await fetch('/api/save', { method: 'POST' });
saving = false;
}
</script>
<Button loading={saving} onclick={save}>Save changes</Button> Three things it handles that a hand-rolled disabled={saving} does not:
- The button does not resize. The label keeps its box and simply stops painting, so the spinner sits on top without the width jumping.
- Focus is kept. A
disabledbutton loses focus the moment the action starts, stranding a keyboard user at the top of the page.loadingusesaria-disabled+aria-busyinstead and swallows the click, so focus stays put and screen readers announce the busy state. - Fast actions do not flash. The spinner waits
loadingDelay(150ms) before appearing, so anything that resolves quickly shows nothing at all.
<!-- for an action you know is slow, show it immediately -->
<Button loading={uploading} loadingDelay={0}>Upload</Button> Link
A Button renders as an <a> when you pass an href.
<Button href="/docs">Documentation</Button> buttonVariants
To make something that is not a Button look like one, use buttonVariants().
<script lang="ts">
import { buttonVariants } from '$lib/components/ui/button';
</script>
<a href="/docs" class={buttonVariants({ variant: 'ghost', size: 'sm' })}>Docs</a>
<Dialog.Trigger class={buttonVariants({ variant: 'soft' })}>Open</Dialog.Trigger> It takes the same variant and size values as the component, plus a class that is merged
last.