sc1m/design
ComponentsButton

Button

Button component built on Base UI, styled with sc1m tokens.

Stable

Live preview

Installation

pnpm dlx shadcn@latest add @sc1m/button

This also merges the token layer and installs src/lib/cn.ts. For the manual route — prerequisites, components.json, and app-level setup — see Installation.

Usage

import { Button } from "@/components/ui/button";

Example

The preview above, with its source. This is the demo module verbatim, so the code and the thing it renders cannot drift apart.

<Button>Save changes</Button>

States

Every variant shares one focus ring and one disabled treatment; only the hover step is per-variant.

DefaultHoverFocusDisabledprimary
secondary
ghost
danger
StateWhat changesNotes
hoverprimary → bg-accent-hover · secondary/ghost → bg-surface · danger → opacity-90The only per-variant state. Danger shifts opacity rather than hue so the red stays the danger token.
focus-visibleoutline-2 outline-offset-2 outline-ringShared by all four. Keyboard only — a pointer press never paints a ring.
disabledopacity-50 + pointer-events-noneShared by all four. Native disabled, so the button leaves the tab order.
activeThere is no pressed style. A press reads only through the hover state and the 150ms colour transition.
Note:

How this table is rendered

Hover and focus cannot be produced from a real pointer in a static page, so those two columns force the state with the same utility the component applies for it. Default and Disabled are unforced — disabled is the real prop.

Accessibility

  • Keyboard focus draws a 2px ring in --ring, never removed — only shown on :focus-visible, so a pointer press does not paint one.

Design tokens

The component reads semantic tokens only — no raw colour, radius, or duration appears in its source. Override the token, not the component.

TokenApplied to
--ringButton

Source

src/components/ui/button/button.tsx — no next/* imports, so it drops into any React 19 + Tailwind v4 app.