Button
A flat, tokenized button for actions. It comes in several visual variants for different emphasis levels, a range of sizes, and built-in loading and disabled states. It can also render as a link (or any element you choose) while keeping its styling, so a navigating call-to-action stays visually consistent with one that triggers an action.
Use the variant to signal importance: primary for the main action on a view, secondary or outline for supporting actions, ghost for low-emphasis or toolbar actions, link for inline text actions, and destructive for dangerous ones.
Usage
<script setup lang="ts">
import { Button } from "@dlbcodes/ui";
</script>
<template>
<Button>Click me</Button>
</template>
Variants
Seven variants cover the common emphasis levels. Limit yourself to one primary button per section — if everything is primary, nothing is.
<script setup lang="ts">
import { Button } from "@dlbcodes/ui";
</script>
<template>
<div class="flex flex-wrap items-center gap-3">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
</div>
</template>
primary is filled with the theme's --brand color, and destructive with --destructive. The others use neutral tokens, so they follow your theme automatically. See Theming.
Sizes
Sizes run sm, base (default), lg, and xl. There are also icon and icon-sm sizes for square icon-only buttons.
<script setup lang="ts">
import { Button } from "@dlbcodes/ui";
</script>
<template>
<div class="flex flex-wrap items-center gap-3">
<Button size="sm">Small</Button>
<Button size="base">Base</Button>
<Button size="lg">Large</Button>
<Button size="xl">Extra large</Button>
</div>
</template>
| Size | Height |
|---|---|
sm | 32px |
base | 40px |
lg | 48px |
xl | 56px |
These are the same heights as Input at the same size, so a button and an input line up when placed in a row. icon and icon-sm are sized by their padding around the icon.
Corners follow the theme's --radius, so they change with your radius setting.
With icons
An svg placed inside a button is sized to 16px automatically and never intercepts clicks. Add your own size-* class to the icon to override it.
<Button>
<PhPlus />
New project
</Button>Loading
Set loading to show a spinner and disable interaction while an action is in flight. The button keeps its size, so the layout doesn't jump: the label stays in place (hidden) and the spinner is drawn on top, in the button's own text color.
<script setup lang="ts">
import { Button } from "@dlbcodes/ui";
</script>
<template>
<div class="flex flex-wrap items-center gap-3">
<Button :loading="true" loading-text="Saving…">Save</Button>
<Button variant="outline" :loading="true">Loading</Button>
</div>
</template>
loadingText is the label announced to screen readers while loading. It isn't shown visually.
As a link
Pass to to render a link instead of a button while keeping the styling. By default it renders a router link; pass as to override the element (for example as="a" for a plain anchor, or a framework link component).
<script setup lang="ts">
import { Button } from "@dlbcodes/ui";
</script>
<template>
<!-- `as="a"` overrides the default RouterLink, rendering a plain anchor -->
<Button to="https://example.com" as="a" target="_blank" variant="link">
Visit site
</Button>
</template>
to can be a route location (a string or a route object) with a router link. With a native element such as as="a", it must be a string URL, and a route object logs a warning.
A disabled or loading link is not clickable, including by keyboard: it is removed from the tab order and its navigation is blocked.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "icon" | "primary" | Visual emphasis level. |
size | "sm" | "base" | "lg" | "xl" | "icon" | "icon-sm" | "base" | Button size. Use icon/icon-sm for square icon-only buttons. |
to | string | RouteLocationRaw | — | When set, renders a link (a router link by default) instead of a button. |
as | Component | string | RouterLink | Overrides the element used when to is set — e.g. "a" or a link component. |
type | "button" | "submit" | "reset" | "button" | Native button type (ignored when rendering a link). |
loading | boolean | false | Shows a spinner over the button and disables interaction. The button keeps its size. |
loadingText | string | "Loading…" | Label announced to screen readers while loading (not shown visually). |
disabled | boolean | false | Disables the button. |
class | string | — | Classes merged onto the root. |
Accessibility
- Renders a native
<button>by default (or your chosen element whentois set), so keyboard and screen-reader behavior work as expected. - When
loading, the button setsaria-busyand is disabled to prevent duplicate submissions.loadingTextis announced to screen readers. - Disabled and loading states set
aria-disabled. For links, which have no nativedisabled, the button also removes the link from the tab order and blocks activation. - Keyboard focus shows a visible ring in the theme's
--ringcolor. - For
icon/icon-smbuttons, provide anaria-labelso the action has an accessible name (there's no visible text).