Skip to content

Badge

A <Badge> is a small inline label. It flags status, count, or category next to a noun — never a verb. Six variants carry meaning; never pick a variant for color alone.

import from "@na/ui/components/badge" ▶ Open in Storybook packages/ui/src/components/badge.tsx

When to use

  • Inline status next to a name (Acme Corp [Active]).
  • Counts inline with a label (Inbox 12).
  • Tag chips on cards or list rows.

Don’t use for actions. A clickable badge is a button or a link — use those primitives. Badges are nouns; if you find yourself wanting onClick, you’ve reached for the wrong thing.

Variants

DefaultSecondaryOutlineGhostDestructiveLink
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="ghost">Ghost</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="link">Link</Badge>
VariantUse
defaultActive / primary status. Use sparingly — one per row.
secondaryNeutral status that still earns a chip (e.g., Draft).
outlineCategorical tag where the chip shouldn’t compete visually.
ghostRead-only metadata that lives quietly next to a noun.
destructiveFailure / blocked / archived. Pair with the verb in adjacent text.
linkInside body text where the badge is the link target.

For named entity statuses (DRAFT, PUBLISHED, ARCHIVED, …) use StatusBadge instead — it owns the status-to-variant map.

With icon

A leading icon reinforces meaning. Don’t add an icon if the label alone is unambiguous (Active, Draft).

VerifiedPendingFailed
<Badge><IconCheck />Verified</Badge>
<Badge variant="secondary"><IconClock />Pending</Badge>
<Badge variant="destructive"><IconAlertTriangle />Failed</Badge>

Pass asChild and wrap an <a>. Use variant="link" when the badge is the link.

<Badge asChild variant="link">
<a href="/path">View →</a>
</Badge>

API

Prop Type Default Description
variant "default" | "secondary" | "outline" | "ghost" | "destructive" | "link" "default" Visual + semantic variant.
asChild boolean false Render the styling onto the child element (Radix Slot). Use to render a Badge as an <a> for navigation.
className string Forwarded — use sparingly; prefer variants.
...rest HTMLAttributes<HTMLSpanElement> All native span attributes are forwarded.

Design guidelines

✓ Do

  • Use one default-variant badge per row. The eye should land on it once.
  • Lead with the noun: "Active", "Draft", "Archived". Capitalize like a state machine.
  • For named entity statuses, reach for StatusBadge — it standardizes the variant per status.

✗ Don't

  • Use destructive for non-error states to "draw attention". The system has only one red.
  • Add onClick — that is a Button. Wrap an <a> via asChild for nav.
  • Use a Badge to display a number larger than 99. That is a count, not a chip.

Accessibility

  • Default semantic is a <span>. Inherits surrounding context — no role needed.
  • For dynamic values (counts that update, status changes), wrap the surrounding region in aria-live="polite" on the parent, not the Badge itself.
  • Color is never the only signal — every variant pairs with a label that carries the meaning.
  • StatusBadge — Named entity statuses with the variant map baked in. Use this 90% of the time.
  • Button — Use when the user’s expected action is to click.
  • Tooltip — Wrap a Badge to add an on-hover explanation.

▶ Open Badge stories in Storybook