Badge
Badge: a status chip carried by fill alone — a tint of the tone at 12–15%, label inked toward the hue, and a sharp radius from --brand-radius-sm. Five tones (neutral, info, success, warning, danger) plus an optional icon slot. No runtime dependencies.
Live preview
Installation
pnpm dlx shadcn@latest add @sc1m/badgeThis 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 { Badge } from "@/components/ui/badge";Composition
One element. tone picks the fill, icon fills the optional leading slot.
Badge├── (icon — optional, aria-hidden)└── childrenTones
Four tinted tones plus a neutral. Neutral is not a 0% tint of some arbitrary hue — it is the absence of a state, so it takes the plain surface.
<Badge tone="success">Paid</Badge>
<Badge tone="info">Pending</Badge>
<Badge tone="warning">Retrying</Badge>
<Badge tone="danger">Overdue</Badge>
<Badge>Draft</Badge>Icon
The study is deliberately glyphless — the fill carries the state on its own, and every glyph you add is ink the tint had already earned. Reach for the slot when a badge has to survive greyscale, or sits somewhere the tint alone is ambiguous.
Status glyphs take Phosphor's fill weight — see Icons.
The slot fixes the box and stops the glyph shrinking, so a 16px or 24px source
icon still lands at the label's optical weight. It inherits the tone's ink
through currentColor, and is aria-hidden — the label is what gets read.
import { CheckCircle } from "@phosphor-icons/react/dist/ssr";
<Badge tone="success" icon={<CheckCircle weight="fill" />}>
Paid
</Badge>Example
The preview above, with its source. This is the demo module verbatim, so the code and the thing it renders cannot drift apart.
<div className="flex flex-wrap gap-2">
<Badge tone="success">Paid</Badge>
<Badge tone="info">Pending</Badge>
<Badge tone="warning">Retrying</Badge>
<Badge tone="danger">Overdue</Badge>
<Badge>Draft</Badge>
</div>API reference
Badge forwards every prop of a span.
Accessibility
- A badge is a label, not a live region. It announces as its text and takes no role of its own — if a badge reflects state that changes while the page is open, put the live region on the container that owns that state.
- The icon slot is
aria-hidden, so the label carries the whole meaning. Never ship an icon-only badge: the tint is not readable in greyscale, and the glyph is not readable at all. - Colour is never the sole carrier of meaning here — the label always says what the tint says.
Design tokens
The component reads semantic tokens only — no raw colour, radius, or duration appears in its source. Override the token, not the component.
Source
src/components/ui/badge/badge.tsx — no next/* imports, so it drops into any
React 19 + Tailwind v4 app.
Avatar
Avatar component built on Base UI, styled with sc1m tokens.
Banner
Banner: a borderless status note — a flat wash of the tone at 7–8%, a tone-coloured icon, and body copy inked toward the hue instead of muted grey. Four tones (info, warning, success, danger), each driven by three custom properties. No runtime dependencies.