Badge
Small read-only marks for a count or an emphasis, set inside a chip, a button, or a row, in seven role colors plus a status dot.
Badge is a small mark you set inside something else, such as a count inside a chip, an emphasis
beside a row title, or a short word in a button. It renders a <span>, so it nests validly inside a
button, a table cell, or a line of text, and its colors are the look's, so every role variant
repaints when the look changes. For a standalone label like "Beta" or "Pro", reach for a category
chip instead. CircleBadge is an 8px status
dot that sits beside text. Chip ships in the same @gesso/badge
install and has its own page, for a value a person picked or can dismiss.
Usage
import { Badge } from '@/components/ui/badge';
<Badge variant="secondary">Draft</Badge>Pick the variant by weight
default is the primary fill with its contrast ink, and destructive is the destructive fill, for
a status that needs attention. secondary is a quiet fill, and muted is the sunken well with
muted text, for a count or a detail that should recede. outline draws a border around primary
text, ghost is the label with no fill, and link is primary text that underlines on hover when it is a link. Each
role variant takes the look's rounded-control, so a Badge is an 8px rectangle in Washi and a pill
in ICS.
The default, secondary, and destructive variants wear a tone, the hue, and take it from their
variant: primary, neutral, and danger. Pass tone to change it, as in <Badge tone="success">,
or tone="inherit" to take the surrounding scope. The other variants do not read a tone.
The text is text-xs and semibold, with px-2.5 and py-0.5 around it, and the label stays on
one line, which makes a Badge 22px tall. A Badge has no size prop, so set a size in className.
Icons and links
A Badge adds no gap between its children, so put an icon inside as a child and add gap-1 to the
Badge. Hide a decorative icon from assistive tech with aria-hidden.
asChild hands the Badge's classes to its one child and renders that instead, so a Badge can be a
link.
import Link from 'next/link';
<Badge asChild variant="outline">
<Link href="/changelog">What is new</Link>
</Badge>A plain Badge is a mark, not a control, so it does not change on hover. The hover fills of
default, secondary, destructive, ghost, and link apply only when asChild makes the
Badge a link or a button, which is what to use when it should act.
A status dot
CircleBadge is a solid 8px circle in bg-primary, with no children. It is a dot, not a count,
so put the status or the number in text beside it. Recolor it with className, like
bg-destructive. It is aria-hidden already, and the text next to it carries the meaning.
Category color
color takes a category hue and draws the mark
as a tinted fill, an ink label, and a hairline in that hue, with font-medium and 8px corners in
every look, even one that rounds its buttons into pills. It wins over variant and tone, because
a role variant repaints with the look and a category hue keeps its meaning. variant="tag" alone is
the same shape with no color.
<Badge color="orange">High</Badge>For a colored option in a table cell, use CategoryTag from
Cell fields, which wraps this and adds a remove
button.
Props
Badge takes every <span> attribute, plus these.
| Prop | Type | Default | What it does |
|---|---|---|---|
variant | "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "muted" | "tag" | "default" | The weight of the mark. tag is the category shape with no color. |
tone | "neutral" | "primary" | "support" | "danger" | "success" | "warning" | "info" | "inherit" | By variant | The hue, for default, secondary, and destructive. |
color | "gray" | "red" | "orange" | "amber" | "yellow" | "green" | "blue" | "purple" | "pink" | A category hue, drawn on the tag shape. Wins over variant and tone. | |
asChild | boolean | false | Renders its one child with the Badge's classes, instead of a <span>. |
CircleBadge takes every <span> attribute except children. badgeVariants({ variant }) returns
the classes, for styling something that is not a Badge.
Accessibility
- A Badge is a plain
<span>with no role, so a screen reader reads its text in the flow of the sentence, and it takes no focus. A change in a Badge, like a count going up, is not announced, so use a live region when it needs to be. - Color alone should not carry a status. The label says what it is, and a Badge with no text would have no name, so keep the word in the Badge.
CircleBadgeisaria-hidden, so a screen reader skips it. The text beside it has to say what the dot says, like "Active" or "Failed".- With
asChildon a link or a button, the Badge shows a 2px focus ring on:focus-visible, offset from its edge. A plain Badge takes no focus. - Badge sets no forced-colors rules. Windows high contrast replaces its fills, so a filled variant
can lose its edge, and a
CircleBadge, which is only a fill, can disappear. Keep the meaning in the text. - The contrast of each variant's text on its fill is not checked here.
When to use something else
- Category colors, through
CategoryTag, for a standalone label like "Beta", "Pro", or "Paid", and for any value that keeps its own hue in every look, like a priority that is orange at High. A category chip is the usual way to label something, and a Badge is the small mark inside it. - Chip for a value a person picked, an applied filter, or anything
with a dismiss button. Chip takes a leading icon and an
onRemove, and it ships in this install. - Avatar for a person, whose fallback is initials on a neutral fill.
- Button for something that runs an action. Reach for
BadgewithasChildonly when the mark is a small link.
Install
shadcn add @gesso/badgeThis installs Chip and chip.config with it, and Badge, CircleBadge, and Chip import from
@/components/ui/badge.