Button
The control that triggers an action.
Playground
Variant V Style variant
Tier V Style variant
Loading Placement
·
<Button
intent="primary"
loadingPlacement="overlay"
mint="scale"
size="lg"
>
Get Started
</Button>01 Examples
Toggle buttons
active marks a button as selected or on and sets aria-pressed for you. Reach for it when the choice persists, like a formatting toggle that stays lit. pressed is its momentary cousin for a press-and-release cue. Both are booleans you drive from your own state. ·
{#each formats as fmt (fmt.name)}
<Button variant="ghost" intent="primary" active={fmt.on} onclick={() => (fmt.on = !fmt.on)}>
{fmt.name}
</Button>
{/each}Icons and labels
Put an icon in the button's content, before or after the label. The gap between icon and label comes from the button's
size, so it tracks the button, while the glyph keeps whatever size you set on the icon (size={18} here). ·
<Button intent="primary"><PlusIcon size={18} />New project</Button>
<Button variant="outlined" intent="neutral"><DownloadIcon size={18} />Export</Button>
<Button variant="text" intent="primary">Continue<ArrowRightIcon size={18} /></Button>Submit with a loading state
Flip
loading while a request is in flight. The button blocks activation but stays focusable, so the action can't double-fire. loadingPlacement=start keeps the label beside the spinner, where the default overlay hides it behind the spinner instead. ·
<Button intent="primary" loading={saving} loadingPlacement="start" onclick={save}>
{saving ? 'Saving…' : 'Save changes'}
</Button>Composing micro-interactions
mint layers motion feedback: pass an array to stack effects, or an object to tune a duration. The Playground's Mint control picks one effect at a time, so arrays and per-effect config appear only here. Nine effects ship. Six are held on hover (scale, translate, rotate, glow, pulse, wiggle) and three fire on click (ripple, bounce, shake). ·
<Button intent="primary" mint={['scale', 'ripple']}>Scale + Ripple</Button>
<Button intent="success" mint={['glow', 'bounce']}>Glow + Bounce</Button>
<Button intent="warning" mint={[{ name: 'glow', config: { duration: 500 } }]}
>Slow Glow</Button
>02 As a link
Link that looks like a button
A Button never takes
href: a link in a button's clothes is an <a> wearing buttonVariants(). Keep that in a thin wrapper of your own, so resolve(), target and rel stay decisions of your app — the code below is the wrapper, the two links above it call the same function inline. The anchor gets the variant, intent, size and focus ring; loading, mint and the active / pressed ARIA stay with the real Button. ·
<!-- LinkButton.svelte -->
<script lang="ts">
import { buttonVariants, type ButtonProps } from '@urbicon-ui/blocks';
import type { HTMLAnchorAttributes } from 'svelte/elements';
let {
href,
intent = 'neutral',
variant = 'filled',
size = 'md',
class: className,
children,
...rest
}: HTMLAnchorAttributes & Pick<ButtonProps, 'intent' | 'variant' | 'size' | 'class'> = $props();
</script>
<a {href} class={buttonVariants({ intent, variant, size }).base({ class: className })} {...rest}>
{@render children?.()}
</a>03 Customization
Neon outline
One
class gives the button a neon outline glowing on a dark panel. It keeps the button's radius tier, padding and press behaviour, and only the border, text and glow are raw. The colours are raw because a neon hue has no token equivalent. ·
<Button
class="border border-emerald-400 bg-transparent text-emerald-400 shadow-[0_0_15px_rgba(52,211,153,0.3)] hover:bg-emerald-400/10 hover:shadow-[0_0_25px_rgba(52,211,153,0.5)]"
>
Deploy
</Button>
<Button
class="border border-sky-400 bg-transparent text-sky-400 shadow-[0_0_15px_rgba(56,189,248,0.3)] hover:bg-sky-400/10 hover:shadow-[0_0_25px_rgba(56,189,248,0.5)]"
>
Preview
</Button>This is one of five ways to restyle a block. See Customization for class, slotClasses, unstyled, preset and provider-level overrides.
04 Accessibility
Built-in ARIA
The button manages aria-pressed for toggle and
selected states, aria-busy while loading, and aria-disabled when disabled. Focus indication uses focus-visible, so the ring appears only for keyboard
focus.
Keyboard
Tab moves focus. Enter / Space activate. While loading, the button ignores activation but stays focusable.
Reduced motion
Mint effects respect prefers-reduced-motion: with it
enabled, the hover and click animations are suppressed and the ripple is never drawn.
05 API Reference
20 props20 props
Add filter
Sort
Grouping · No column can be grouped
Summary · No column can be summarized
Column visibility
Prop | Type | Default | Description | |
|---|---|---|---|---|
active | boolean | false | Whether the button is visually active/selected (e.g. in a ButtonGroup with selection).
Unlike pressed (momentary feedback), active represents a persistent selected state. | |
buttonGroupConnected variant | true | — | Controls the buttonGroupConnected behavior and appearance of the Button component. Available options: true. | |
children | Snippet | — | The content of the button | |
class | string | — | Custom CSS class name | |
disabled | boolean | false | Whether the button is disabled | |
intent variant | dangerneutralprimarysecondary +2 more | neutral | Controls the color theme and semantic meaning of the Button. Affects the overall appearance and user perception. Available options: danger, neutral, primary, and 3 more. | |
loading | boolean | false | Whether the button is in a loading state | |
loadingPlacement | overlaystartend | 'overlay' | Where the loading indicator should appear when loading is true - 'overlay': spinner overlays content and hides it (default) - 'start': spinner appears before the content - 'end': spinner appears after the content | |
mint | MintProp | 'scale' | Micro-interaction preset applied to the button. Only applies while the
button is neither disabled nor loading. Inside a ButtonGroup, the
group's mint always wins over this prop.
mint="none" also flattens the press sink — the dip under a held pointer —
leaving a button that reacts in colour and depth but never moves. Every
ButtonGroup renders its children that way by default (its own mint
defaults to 'none' and wins over this prop), which is what keeps a
connected group's shared seam still on click; it is also what a large or
full-width trigger row wants, where the dip reads as a wobble. | |
onclick | (event: MouseEvent) => void | — | Click handler | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ Button: {...} }}>.
Prefer this over class="bg-…!" overrides when the requested look is outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent and
make the custom look reusable across the project. | |
pressed | boolean | false | Whether the button is pressed (for toggle buttons) | |
size variant | 2xslgmdsm +2 more | md | Controls the dimensions, padding, and text size of the Button. Affects the component's physical footprint. Available options: 2xs, lg, md, and 3 more. | |
slotClasses | Partial<Record<ButtonSlots, string>> | — | Per-slot class overrides merged with tv styles. Slots: base | content | spinner.
A button never shrinks below its label and never clips it; to truncate a
long label instead, let the button shrink (class="min-w-0") and wrap the
label in a block that clips it: <span class="block truncate">…</span>.
Not slotClasses={{ content: 'truncate' }} — the content slot is a flex
row, and text-overflow paints its ellipsis only on a block container. | |
tier variant | commitmodify | commit | Selects the semantic radius tier of the Button — the shape family it belongs to (--radius-commit/-modify/-contain/-bridge). Shape is retuned per family in your theme, so this picks the family rather than a pixel value. Available options: commit, modify. | |
unstyled | boolean | false | Remove the default variant classes and apply yours instead. Not an empty
element: the blocks-* hooks stay on the root (blocks-button,
blocks-intent-*) so a stylesheet can still reach the button — see
ARCHITECTURE.md § The override cascade. | |
value | string | — | The value associated with the button (useful in ButtonGroups) | |
variant variant | filledghostoutlinedtext | filled | Controls the visual style and presentation of the Button. Determines the component's visual treatment. Available options: filled, ghost, outlined, text. | |
...ButtonVariants variant | VariantProps | — | Styling variants from ButtonVariants | |
...HTMLButtonAttributes inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
06 Types
Local type definitions used by this component.
7 types
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
ButtonProps | interface | props | 0 | Props interface for Button component | |
ButtonVariants | type | variant | 1 | — | |
ButtonSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
MintProp | type | helper | 1 | — | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
07 Installation
Import
·
import { Button } from '@urbicon-ui/blocks';