Button
Versatile, accessible buttons with variants, loading states, and micro-interactions.
Playground
<Button
intent="primary"
loadingPlacement="overlay"
mint="scale"
size="lg"
>
Get Started
</Button>01 Examples
States Matrix
<div
role="group"
aria-label="Visual states reference — non-interactive demo grid"
class="flex flex-col items-start gap-3"
>
<div class="flex flex-wrap items-center gap-3">
<Button variant="filled" intent="primary">Default</Button>
<Button variant="filled" intent="primary" active>Active</Button>
<Button variant="filled" intent="primary" pressed>Pressed</Button>
<Button variant="filled" intent="primary" loading>Loading</Button>
<Button variant="filled" intent="primary" disabled>Disabled</Button>
</div>
<div class="flex flex-wrap items-center gap-3">
<Button variant="outlined" intent="primary">Default</Button>
<Button variant="outlined" intent="primary" active>Active</Button>
<Button variant="outlined" intent="primary" pressed>Pressed</Button>
<Button variant="outlined" intent="primary" loading>Loading</Button>
<Button variant="outlined" intent="primary" disabled>Disabled</Button>
</div>
<div class="flex flex-wrap items-center gap-3">
<Button variant="ghost" intent="primary">Default</Button>
<Button variant="ghost" intent="primary" active>Active</Button>
<Button variant="ghost" intent="primary" pressed>Pressed</Button>
<Button variant="ghost" intent="primary" loading>Loading</Button>
<Button variant="ghost" intent="primary" disabled>Disabled</Button>
</div>
<div class="flex flex-wrap items-center gap-3">
<Button variant="text" intent="primary">Default</Button>
<Button variant="text" intent="primary" active>Active</Button>
<Button variant="text" intent="primary" pressed>Pressed</Button>
<Button variant="text" intent="primary" loading>Loading</Button>
<Button variant="text" intent="primary" disabled>Disabled</Button>
</div>
</div>Mint micro-interactions
<div class="flex flex-wrap items-center gap-3">
<Button intent="primary" mint="scale">Scale</Button>
<Button intent="primary" mint="translate">Translate</Button>
<Button intent="primary" mint="rotate">Rotate</Button>
<Button intent="primary" mint="glow">Glow</Button>
<Button intent="secondary" mint="pulse">Pulse</Button>
<Button intent="secondary" mint="wiggle">Wiggle</Button>
</div>
<div class="flex flex-wrap items-center gap-3">
<Button intent="primary" mint="ripple">Ripple</Button>
<Button intent="primary" mint="bounce">Bounce</Button>
<Button intent="danger" mint="shake">Shake</Button>
</div>Layering and tuning effects
<Button intent="primary" mint={['scale', 'ripple']}>Scale + Ripple</Button>
<Button intent="success" mint={['glow', 'bounce']}>Glow + Bounce</Button>
<Button intent="primary" mint={[{ name: 'scale', config: { duration: 200 } }, 'ripple']}
>Fast Scale + Ripple</Button
>
<Button intent="warning" mint={[{ name: 'glow', config: { duration: 500 } }]}
>Slow Glow</Button
>02 Customization
Gradient CTA
<Button
size="lg"
mint={['scale', 'ripple']}
slotClasses={{
base: 'bg-linear-to-r from-violet-600 to-fuchsia-500 border-none shadow-lg shadow-violet-500/30 hover:shadow-violet-500/50'
}}
>
Launch Project
</Button>Pill & Icon-only
<Button variant="outlined" intent="primary" slotClasses={{ base: 'rounded-full' }}
>Subscribe</Button
>
<Button
variant="ghost"
intent="neutral"
aria-label="Confirm"
slotClasses={{ base: 'rounded-full p-0 w-10' }}
>
<CheckIcon size={20} />
</Button>
<Button
variant="filled"
intent="danger"
aria-label="Cancel"
slotClasses={{ base: 'rounded-full p-0 w-10' }}
>
<CloseIcon size={20} />
</Button>Neon Outline
<Button
unstyled
class="rounded-lg border border-emerald-400 px-5 py-2.5 font-medium text-emerald-400 shadow-[0_0_15px_rgba(52,211,153,0.3)] transition-all hover:bg-emerald-400/10 hover:shadow-[0_0_25px_rgba(52,211,153,0.5)]"
>
Neon Green
</Button>
<Button
unstyled
class="rounded-lg border border-sky-400 px-5 py-2.5 font-medium text-sky-400 shadow-[0_0_15px_rgba(56,189,248,0.3)] transition-all hover:bg-sky-400/10 hover:shadow-[0_0_25px_rgba(56,189,248,0.5)]"
>
Neon Blue
</Button>Fully Custom (unstyled)
<Button
unstyled
mint="scale"
class="inline-flex items-center gap-3 rounded-2xl bg-linear-to-br from-amber-200 to-orange-400 px-8 py-4 font-bold text-neutral-900 shadow-xl transition-all hover:shadow-2xl"
>
Unstyled Magic
</Button>
<Button
unstyled
mint="scale"
class="text-text-primary hover:bg-text-primary hover:text-surface-base inline-flex items-center gap-2 rounded-none border-2 border-current px-6 py-3 font-mono text-sm font-bold tracking-widest uppercase transition-all"
>
Brutalist
</Button>The gradient CTA is the canonical preset case: register it once under presets.Button on BlocksProvider and every call site becomes preset="cta" — hover, dark mode, and Mint stay
coherent. See Customization.
03 Accessibility
Built-in ARIA
Automatically manages aria-pressed for toggle
buttons, aria-busy during loading, and aria-disabled when disabled. Focus indication uses focus-visible: so mouse users see no ring.
State Semantics
active = persistent selection (a radio-style segment,
the current sort column, the selected tool). Set this when the button represents an enduring
chosen state. pressed = transient toggle (bold/italic in a toolbar,
mute/unmute). Set this on a button that flips between two complementary states. loading = busy; suppresses interaction without removing
focus.
Keyboard
Tab to focus, Enter / Space to activate. Loading state disables interaction automatically while keeping the button focusable.
Reduced Motion
All Mint effects are suppressed when the user enables prefers-reduced-motion. Transitions, transforms, and
ripple animations are removed entirely.
04 API Reference
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. | |
onclick | (event: MouseEvent) => void | — | Onclick property for the Button component | |
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 | |
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 | — | Remove default tailwind-variants classes. Only user classes apply. | |
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') |
05 Types
Local type definitions used by this component.
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. |
06 Installation
Import
import { Button } from '@urbicon-ui/blocks';