Badge
StableA short, non-interactive label that tags an object with a status, category or count.
Anatomy
- 1DotOptional. A status dot in the badge's color that can pulse for ongoing states.
- 2ContainerA small pill. Semantic variants tint it with the status colors; outline adds a border instead of a fill.
- 3LabelA word or two in sentence case: a status, a count or a category.
- 4IconOptional. Sized by the badge, which tightens its padding on that side.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/badge.jsonUsage
import { Badge, BadgeDot } from "@/components/ui/badge"<Badge variant="success">
<BadgeDot />
Paid
</Badge>Examples
Variants
Variants map to meaning, not to taste. The four status variants — success, warning, destructive, info — are reserved for state, so a green badge always means the same thing everywhere in a product.
Sizes
default (20px) fits alongside body text and in table cells. Use sm for counters inside navigation and lg for badges that sit next to headings.
Status
Pair status badges with BadgeDot. The dot makes a column of states scannable at a glance; the word makes the state unambiguous. Set live to pulse the dot for states that are actively changing — the pulse stops automatically when reduced motion is on.
- APIOperational
- DashboardDegraded
- WebhooksOutage
- Search indexMaintenance
With icons
Mark icons with data-icon="inline-start" or data-icon="inline-end"; the badge tightens its padding on that side and sizes the icon to 12px.
As a link
A badge can navigate — to a changelog entry, a filtered list, a tag. Use the render prop so it becomes a real link; link badges pick up a hover state automatically.
<Badge variant="brand" render={<Link href="/docs/resources/changelog" />}>
prfct 1.0 is here
</Badge>Counts
Counts label a destination with how much is waiting there. Cap them at 99+, use the solid default variant only for unread items that need action, and give the number context for screen readers.
Guidelines
When to use
- To show the state of an object: a payment, a deployment, a service.
- To add short metadata: a role, a version, a plan, a tag.
- To show a count of items waiting behind a navigation item.
When not to use
- To trigger an action — use a Button.
- To filter a view — use a Toggle Group or checkboxes, so the selected state is announced.
- To explain something — if it needs more than two words, it needs a sentence, an Alert or a Tooltip.
Never color alone
Keep it short
Restraint
Badges work because they are rare. If every row in a table carries three badges, none of them is noticed — keep one status badge per object and move the rest of the metadata into plain text.
Accessibility
- Static by default. A badge renders a
<span>and is not focusable. When it navigates, render it as a link so it gets a role, focus and a visible focus ring. - Status is text. Every status variant pairs a color with a word.
BadgeDotisaria-hidden; the label carries the meaning. - Counts need context. "12" alone is ambiguous when read aloud. Add a visually hidden suffix —
<span className="sr-only"> unread</span>— or put the count in the link's accessible name. - Contrast. Soft variants use step 11 text on step 3 fills, which the palette guarantees at 4.5:1 or more in both modes. Solid variants meet 4.5:1 as well.
- Motion. The
livepulse is decorative and is disabled underprefers-reduced-motion.
API reference
Badge
Renders a <span>. Built on Base UI's useRender, so it accepts a render prop.
BadgeDot
A 6px status dot that takes the badge's text color.
badgeVariants
The class generator is exported so other elements — a table cell, a custom link — can share badge styles.
import { badgeVariants } from "@/components/ui/badge"
<span className={badgeVariants({ variant: "info", size: "sm" })}>Beta</span>