--primitiv-badge-background--primitiv-badge-foreground--primitiv-badge-height--primitiv-badge-padding-inline--primitiv-badge-font-family--primitiv-badge-font-size--primitiv-badge-font-weight--primitiv-badge-line-height--primitiv-badge-radiusA small status/count indicator attached to another element or beside a heading. Read-only, never interactive. Four semantic tones × two treatments (a low-emphasis label chip, a high-emphasis counter chip), sized xs–xl; data-density scales each size further.
primitiv add badge installs it whichever mode you are reading.Preview
import { Badge } from "@/components/ui/badge";
<Badge size="md" variant="label" tone="success">Stable</Badge>Density is set by a data-density ancestor — the Context system, not a Badge prop.
npx primitiv add badgepnpm dlx primitiv add badgeyarn dlx primitiv add badgebunx primitiv add badgeImport
import { Badge } from "@/components/ui/badge";No headless primitive — this one ships only as a copied styled surface, so primitiv add is the only way in, whichever mode you are reading.
Extends HTMLSpanElement — every native attribute of that element is accepted and forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | — | Render the single child element instead of a wrapping <span>, merging
the badge classes onto it — e.g. <Badge asChild><a>...</a></Badge>. |
| tone | "success" | "warning" | "info" | "danger" | success | Semantic colour. |
| variant | "label" | "counter" | label | Visual treatment. label is the low-emphasis status-word chip; counter is the high-emphasis count chip. |
| size | "xs" | "sm" | "md" | "lg" | "xl" | md | Badge size; data-density scales each size further. |
--primitiv-badge-background--primitiv-badge-foreground--primitiv-badge-height--primitiv-badge-padding-inline--primitiv-badge-font-family--primitiv-badge-font-size--primitiv-badge-font-weight--primitiv-badge-line-height--primitiv-badge-radius<span>, takes no focus and handles no events — if you need something clickable that looks like this, use Chip, which is built for it, rather than putting a handler on a badge.Failing in the danger tone, not a bare red dot.8 on its own is announced as "8" — give the surrounding control the context, with an aria-label like "8 unread messages" on the element the badge is attached to.asChild the classes merge onto your element and its semantics stay yours — a <time> is still a <time>. Nothing about the badge overrides the role, which is why it is the right hook for marking up a date or an abbreviation.Badge has no neutral tone by design, so a status is always one of four meanings. Reach for Tag when the label is a category rather than a state.Four tones, and the set is deliberately closed: success, warning, info and danger all mean something, so a badge cannot be used as decoration. There is no neutral tone — if what you want is a plain label with no status attached, that is Tag, which carries the neutral the Badge palette leaves out.
import { Badge } from "@/components/ui/badge";
<Badge tone="success">Success</Badge><Badge tone="warning">Warning</Badge><Badge tone="info">Info</Badge><Badge tone="danger">Danger</Badge>variant="counter" is the number form — a pill sized for one or two digits rather than a word, for unread counts and totals beside a heading or an icon. It is the same component, so a count still carries a tone: an unread count is info, a failing-checks count is danger.
import { Badge } from "@/components/ui/badge";
<Badge variant="counter" tone="info">8</Badge><Badge variant="counter" tone="danger">3</Badge>Five sizes, and every one rescales again with the nearest data-density ancestor — a badge beside a heading wants lg, one in a table row wants xs. Change the density above and the whole ramp shifts: that is the Context system, not a Badge prop.
import { Badge } from "@/components/ui/badge";
<div data-density="comfortable"> <Badge size="xs">Stable</Badge> <Badge size="sm">Stable</Badge> <Badge size="md">Stable</Badge> <Badge size="lg">Stable</Badge> <Badge size="xl">Stable</Badge></div>asChild merges the badge's classes onto your own element instead of wrapping a <span> — useful for an <abbr> or a <time> that should read as a badge. It is not a way to make a badge clickable: a badge is read-only by design, and a link or button that looks like one should be a Chip, which is built to be interactive.
import { Badge } from "@/components/ui/badge";
<Badge asChild tone="info"> <time dateTime="2026-08-24">Aug 24</time></Badge>