Component · Core
Badge
Overview
A static status, category, or count label — "Active," "Beta," "3 new." Not an interactive control: no click handler, no hover state, no keyboard focus.
Anatomy
Container → optional leading icon → label.
- Container — the pill that carries the variant styling.
- Leading icon — optional; sits before the label.
- Label — the short status, category, or count text.
No dismiss button, no trailing content. A badge that needs to be removable is a different component (a future Tag), not a variant of this one.
Live Example
Variants
Leading icon
Code Example
import { Check, Star } from "lucide-react";
import { Badge } from "@/components/ui/badge";
const VARIANTS = ["primary", "secondary", "destructive", "outline"] as const;
/**
* Live Badge example — the four documented variants and the optional leading
* icon slot, rendered with the real component so the page doubles as a visual
* check. Badge is static: nothing here is clickable or focusable.
*/
export function BadgeShowcase() {
return (
<div className="flex flex-col gap-8">
<section className="flex flex-col gap-3">
<h3 className="text-sm font-medium leading-snug text-muted-foreground">
Variants
</h3>
<div className="flex flex-wrap items-center gap-3">
{VARIANTS.map((variant) => (
<Badge key={variant} variant={variant}>
{variant[0].toUpperCase() + variant.slice(1)}
</Badge>
))}
</div>
</section>
<section className="flex flex-col gap-3">
<h3 className="text-sm font-medium leading-snug text-muted-foreground">
Leading icon
</h3>
<div className="flex flex-wrap items-center gap-3">
<Badge leadingIcon={<Check />}>Verified</Badge>
<Badge variant="outline" leadingIcon={<Star />}>
Featured
</Badge>
</div>
</section>
</div>
);
}Variants
Four variants, reusing Button's non-navigational set. Every value below is an existing semantic token — see Color.
| Variant | Background | Text | Border |
|---|---|---|---|
| Primary | --primary | --primary-foreground | none |
| Secondary | --secondary | --secondary-foreground | none |
| Destructive | --destructive | --destructive-foreground | none |
| Outline | --background | --foreground | --border |
Ghost and Link are dropped — both exist for interactive affordances (hover states, link-like behavior) that a static chip doesn't have.
Sizes
One size — badges are small status labels used at a consistent size across a UI, unlike Button's range of contexts.
| Property | Token | Value |
|---|---|---|
| Padding-x | --scale-1-5 | 6px |
| Padding-y | --scale-0-5 | 2px |
| Radius | --radius-full | pill — see Radius |
| Icon size | --scale-4 | 16px — Icons' "inline with text" recipe |
| Icon-to-label gap | --scale-1 | 4px — same recipe Button uses |
| Text | text-xs font-medium leading-snug | 12px / 500 / 1.375 — Typography's "Captions, metadata" size |
States
| State | Look |
|---|---|
| Default | Variant colors as above |
Badge is static — nothing to hover, focus, activate, or disable.
Usage Guidance
Tokens
Badge uses existing Monogem semantic and primitive tokens to stay consistent with the design system; it introduces no component-specific styling values.
| Token | Where used |
|---|---|
--primary / --primary-foreground | Primary variant |
--secondary / --secondary-foreground | Secondary variant |
--destructive / --destructive-foreground | Destructive variant |
--background / --foreground | Outline variant |
--border | Outline variant border |
--radius-full | Corner radius |
--scale-1-5 | Padding-x |
--scale-0-5 | Padding-y |
--scale-4 | Icon size |
--scale-1 | Icon-to-label gap |
Do / Don’t
Do
- Use Badge for status, category, or count labels that don't need interaction.
- Default to Secondary or Outline for neutral labels.
- Reserve Primary for a badge that needs to stand out, and Destructive for error/failure states.
Don’t
- Make a Badge clickable or give it its own click handler — if it needs to be dismissible or actionable, that's a different (future) component.
- Put more than a couple words in a Badge — it's a label, not a sentence.
Accessibility
- Color alone shouldn't carry the only meaning — pair a status badge with text ("Active," not just a colored dot) so the information survives for colorblind users and screen readers.
- No focus ring or
tabindex— Badge is not in the tab order, since it isn't interactive. - If a badge sits next to other text as a count or status (e.g. inside a button or nav item), make sure its text is included in the accessible name, not lost as decoration.