Badge
A static, inline label for a status, count, or short piece of metadata.
@garn/ui/badgeDefault
Tone + appearance defaults.
Tone × appearance
Every tone rendered against every appearance. Hover a cell for the data attributes it emits; click the copy icon for that combination's JSX.
| solid | soft | outline | subtle | framed | |
|---|---|---|---|---|---|
| neutral | Badge | Badge | Badge | Badge | Badge |
| brand | Badge | Badge | Badge | Badge | Badge |
| info | Badge | Badge | Badge | Badge | Badge |
| success | Badge | Badge | Badge | Badge | Badge |
| warning | Badge | Badge | Badge | Badge | Badge |
| danger | Badge | Badge | Badge | Badge | Badge |
Sizes
xs · sm · md · lg — scales font, padding, and slot icons together.
Shapes
sharp · rounded · pill.
Status
Dot, pulse, loading, and progress affordances.
Count
A trailing count, rendered by Counter (caps at 99+).
Icons
Leading/trailing icons; an icon-only badge needs an aria-label.
Custom Color
A custom color with automatic foreground contrast.
Truncation
Only the label ellipsizes; the full text stays in the title.
Compound
Explicit Badge.Leading / .Label / .Trailing for full control.
As Child
Renders as a link via asChild, keeping the badge styling.
Plan Badge
Badge.Separator (dot / line) divides inline segments — a status·action pill and a metadata divider. The action is a composed link; Badge stays non-interactive.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add badge- Registry
badge- Source
- badge.tsx
- Deps
@radix-ui/react-slotclass-variance-authoritylucide-react- Registry deps
utilsuse-clipboarduse-announcespinnercounter
Import
import { Badge } from "@garn/ui/badge";Props
Generated from the component's TypeScript types.
appearance- Description
- No description.
- Type
"framed" | "outline" | "soft" | "solid" | "subtle"- Default
soft
shape- Description
- No description.
- Type
"pill" | "rounded" | "sharp"- Default
rounded
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xs"- Default
md
tone- Description
- No description.
- Type
"brand" | "danger" | "info" | "neutral" | "success" | "warning"- Default
neutral
asChild- Description
- No description.
- Type
boolean
avatar- Description
- No description.
- Type
React.ReactNode
color- Description
- Custom token reference or hex/rgb value; overrides `tone`.
- Type
string
copyable- Description
- No description.
- Type
boolean
copyValue- Description
- No description.
- Type
string
count- Description
- A trailing count, rendered by the Counter component (not a plain number): it caps at 99+, rolls its digits on change, and announces the true count to screen readers. Tone and size follow the badge; the count's appearance inverts against the badge's so it always contrasts.
- Type
number
dot- Description
- No description.
- Type
boolean
leading- Description
- No description.
- Type
React.ReactNode
loading- Description
- No description.
- Type
boolean
maxWidth- Description
- No description.
- Type
number | string
progress- Description
- 0–100; snaps to a check at 100.
- Type
number
pulse- Description
- No description.
- Type
boolean
trailing- Description
- No description.
- Type
React.ReactNode
truncate- Description
- No description.
- Type
boolean
Plus 280 inherited native <span> attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="badge"][data-slot="badge-leading"][data-slot="badge-label"][data-slot="badge-trailing"][data-slot="badge-separator"][data-slot="badge-dot"][data-slot="badge-copy"][data-slot="badge-count"]| Property | Token | Tier |
|---|---|---|
fill | --garn-badge-success-bgbrandable | semantic |
ring | --garn-ringbrandable | semantic |
Private instance vars (never externalize): --b-soft-bg--b-soft-fg--b-solid-bg--b-solid-fg--b-line--b-text--badge-c--badge-fg
Overview
Badge tags or categorizes inline — a status, a short label, a count beside content. Its look is orthogonal: tone sets the meaning color (neutral / brand / success / warning / danger / info), appearance sets the fill (solid / soft / surface / outline), and size the scale — the same vocabulary across the whole chip family. It's a read-only marker; for a removable/selectable chip use Tag, for a value the user changes use BadgeSelect, and for a pure number use Counter.
Guidelines
Let tone carry meaning, consistently. Map each status to one tone and reuse it everywhere (success = done, danger = error); a rainbow of tones for decoration trains people to ignore them. Keep the label to a word or two — a badge is a marker, not a sentence.
Don't make a badge do a control's job. It isn't clickable, focusable, or dismissible by contract; if users need to remove or toggle it, reach for Tag or BadgeSelect. Place it adjacent to what it describes so the association is obvious.
Mind contrast and redundancy. Pair tone with text (or an icon) so meaning survives for color-blind users and in forced-colors mode. Use soft/surface for quiet metadata and reserve solid for the one status that must stand out.
Best practices
- Keep the text to one or two words.
- Use a tone for state (success/warning/danger) with a readable label — not color alone.
- Reach for
framedwhen a soft chip needs more definition;solidfor the highest emphasis.
- Don't make a badge clickable — its only interaction is optional copy-to-clipboard.
- Don't pack sentences into a badge; only the label truncates — keep leading/trailing to glyphs.
Content guidelines
- Keep the label to one or two words — a state or category, not a sentence (“Paid”, “In review”).
- Use sentence case, and prefer a noun or adjective over a verb (“Active”, not “Activated it”).
- Pair tone with text, never color alone — a success badge still reads “Paid”, not just green.
- Keep the same label for the same state across the product so it stays scannable.
- For counts, show the number only (“12”), and let Counter handle large or notification-style totals.
Accessibility
- Focus
- 2px ring in --garn-ring on the asChild anchor and the copy button.
Enforceable contracts
Provide aria-label (or aria-labelledby / title / sr-only text).
when no text label (icon/dot/avatar only) && !asChild && no aria-label/labelledby/title
- • Badge is a plain <span> with no implicit role — keep a readable text label; don't encode status by color alone.
- • loading sets aria-busy. copyable renders a real <button> with an aria-label and announces via an aria-live=polite region.
- • Badge.Separator is aria-hidden and select-none — purely decorative, so it never enters the badge's accessible name. A composed action (e.g. an inline link) keeps its own name and focus ring; Badge itself stays non-interactive.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.1.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.