Components
Badge
A small label for status, categories, or counts. Available in seven tonal variants and two sizes, sizing any icon child for you and rendering as whatever element you hand it.
Variants
primary is a kept alias of default — the two render the same classes.
Sizes
With an icon
Any direct <svg> child is sized to 12px and made pointer-events-none by the badge itself, and the 4px gap is already there — drop the icon in without a size prop and it lines up with the label.
Render as another element
asChild merges the badge classes onto its single child instead of rendering a <span>. Use it when the badge is genuinely interactive — a tag link or a removable filter — so the element carries the right semantics and keyboard behaviour rather than a click handler on a span.
Use cases
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'primary' | 'secondary' | 'outline' | 'success' | 'warning' | 'destructive' | 'default' | Visual style of the badge. primary is an alias of default and renders identically. |
size | 'sm' | 'md' | 'md' | Controls padding and font size — sm is 10px text, md is 12px. |
asChild | boolean | false | Renders the single child element with the badge classes merged onto it (Radix Slot) instead of a <span>. |
className | string | — | Additional Tailwind classes. Merged after the variant classes, so anything you pass here wins. |
...rest | React.ComponentProps<'span'> | — | All native span props are forwarded. |
The cva recipe is exported as badgeVariants for the rare case where you need the classes without the element.
How the tonal variants are built
Badge text is 10–12px, so it has to clear AA 4.5:1 against the very tint it sits on. Each tonal variant therefore pairs a background token with the matching ink token — never the background token used as a text colour:
success—bg-green/10(dark:/20) withtext-green-ink.warning—bg-yellow/10withtext-yellow-ink.destructive—bg-red/10withtext-destructive-ink.outline— a transparent fill withborder-control-edge, notborder-border: with no fill the border is the only thing outlining the badge, so it owes the 3:1 non-text threshold that the decorative border tier (1.16:1) does not meet.
Because the ink tokens flip with the theme, no variant needs a dark: text override. If you tint a badge yourself via className, /20 is the darkest tint that stays safe for every tone in both themes.
Accessibility
- Badge is a
span— purely decorative unless given a role. - For notification counts, consider
aria-labelor visually hidden text. - Color alone should not convey meaning — pair with text or icons.
- If the badge is clickable, use
asChildover a real<a>or<button>so it is focusable and reachable by keyboard.