Skip to content

Badge

A small status indicator or label used to highlight information.

  • Chip
  • Label
  • Pill

Overview

The Badge is a compact visual indicator used to convey status, category, or count information at a glance. Badges appear in two primary forms: status badges (small dots or icons attached to another element like an avatar) and label badges (standalone text pills showing metadata such as "New", "Beta", or a notification count).

Despite their small size, badges carry significant semantic weight. A red dot on a bell icon means "you have unread notifications." A green badge on an avatar means "online." A "Deprecated" badge on a documentation page changes how the reader interprets the entire content. Getting badge semantics wrong — or worse, using color alone to convey meaning — creates accessibility failures that affect real users.

When to use a Badge:

  • To display notification counts on icons or navigation items
  • To indicate status (online/offline, active/inactive, published/draft)
  • To label categorization metadata (plan tier, role, content type)
  • To highlight newness or urgency ("New", "Updated", "Urgent")

When NOT to use a Badge:

  • For user-applied categorization or filtering — use a Tag instead
  • For dismissible or interactive labels — use a Tag with a remove button
  • For detailed status messages — use an Alert
  • For binary toggles — use a Switch

Use the Color Tool to select semantically appropriate badge colors, and verify text-to-background contrast with the Contrast Checker to ensure every badge meets WCAG requirements.

Variants

Badge Variants

VariantPurposeVisual Treatment
SolidHigh emphasis, critical status, primary categorization.Filled background with contrasting text. Strongest visual weight.
Subtle / SoftMedium emphasis, non-critical metadata.Tinted background (10–15% opacity of the base color) with saturated text.
OutlineLow emphasis, secondary information.Transparent background with colored border and text.
DotMinimal indicator, presence/attention signal.Small colored circle (6–10px) with no text. Attached to parent element.
Numeric / CountNotification counts, unread indicators.Circular or pill-shaped, typically overlapping its parent's top-right corner.

Semantic Color Variants

Badges lean heavily on color semantics. Use the Color Tool to choose values that carry the intended meaning:

ColorSemanticExample Use
Gray / NeutralDefault, informational, inactive"Draft", "Archived", "N/A"
Blue / InfoInformational, in-progress"In Review", "Beta", "New"
Green / SuccessPositive, active, complete"Active", "Published", "Online"
Yellow / WarningCaution, pending"Pending", "Expiring Soon"
Red / ErrorCritical, destructive, urgent"Failed", "Overdue", "Deprecated"
PurplePremium, special"Pro", "Admin", "Featured"

Size Variants

SizeHeightPaddingFont SizeUse Case
Small (sm)18px4px 6px11pxDense UIs, table cells, inline metadata
Medium (md)22px4px 8px12pxDefault for most contexts
Large (lg)26px4px 10px13pxStandalone badges, marketing pages

Dot Badge Positioning

Dot badges overlay their parent element. Common positions:

  • Top-right (default): Notification indicator on icons and avatars
  • Top-left: Less common, used in RTL layouts
  • Bottom-right: Status indicator on avatars (online/offline)

The dot should overlap the parent's bounding box by roughly 25–40% to feel attached without obscuring content.

Properties

Badge Properties

PropertyTypeDefaultDescription
variant'solid' | 'subtle' | 'outline' | 'dot''subtle'Visual style of the badge
color'gray' | 'blue' | 'green' | 'yellow' | 'red' | 'purple''gray'Semantic color scheme
size'sm' | 'md' | 'lg''md'Controls height, padding, and font size
countnumber—For numeric badges. When set, renders count text.
maxCountnumber99Caps display at this number (shows "99+" when exceeded)
showZerobooleanfalseWhether to render the badge when count is 0
dotbooleanfalseRenders as a small dot indicator without text
placement'top-right' | 'top-left' | 'bottom-right' | 'bottom-left''top-right'Position when used as an overlay on a parent
pillbooleantrueApplies full border-radius for pill shape
leftIconReactNode—Icon rendered before the label
childrenReactNode—Badge label content, or the element to attach a dot/count badge to

Important: When using the dot variant as a status indicator, never rely on color alone. Include a visually hidden text label or aria-label that describes the status (e.g., "Online" or "3 unread notifications"). This is a WCAG 1.4.1 (Use of Color) requirement.

Token Mappings

Design Token Mappings

Badges consume a focused set of tokens. For a full token reference, see our Design Tokens Guide.

Token CategoryToken ExampleBadge Usage
Color – Solid Fill--color-green-600Solid variant background
Color – Subtle Fill--color-green-100Subtle variant tinted background
Color – Subtle Text--color-green-700Subtle variant text color
Color – Outline Border--color-green-300Outline variant border
Color – On Fill--color-whiteSolid variant text
Border Radius--radius-full (9999px)Pill shape (default)
Border Radius--radius-sm (4px)Square badge variant
Typography--font-size-xs, --font-weight-mediumBadge label text
Spacing--space-1 / --space-2Horizontal and vertical padding
Shadow--shadow-xsOverlay dot/count badges need a subtle shadow to separate from the parent
Z-Index--z-badge (10)Overlay badges stack above their parent

Use the Color Tool to generate the full tint spectrum (100–900) from a single base color.

States

Badge States

Badges are predominantly non-interactive, but they do carry visual states:

StateVisual ChangeNotes
DefaultStandard appearance per variant and color.—
Empty (count = 0)Hidden by default unless showZero is true.Prevents clutter when there are no notifications.
Overflow (count > max)Displays "99+" (or custom maxCount).Cap avoids absurdly wide badges.
Pulsing / AnimatedSubtle ping animation (scale + fade ring).Used to draw attention to new/urgent badges. Keep animation brief and respect prefers-reduced-motion.
Interactive (if clickable)Hover: slight brightness change. Focus: visible focus ring.Badges are occasionally clickable (e.g., notification badge opens panel). When interactive, they must have role="button" or use a <button> wrapper and show focus indicators per WCAG 2.4.7.

Accessibility

Accessibility

Badges present unique accessibility challenges because they are small, color-dependent, and often overlaid on other elements.

WCAG 1.4.1 — Use of Color: This is the most commonly violated SC for badges. A red badge and a green badge must be distinguishable by more than just color. Add text labels ("Error", "Success"), icons (✓, ✕), or patterns to convey meaning. Use the Contrast Checker to verify that badge text meets 4.5:1 against its background (WCAG 1.4.3), or 3:1 for large text (WCAG 1.4.3 Level AA).

WCAG 1.4.3 — Contrast (Minimum): Badge text must achieve a 4.5:1 contrast ratio against the badge background for text at or below 14px. The badge background itself should achieve 3:1 against the page background (WCAG 1.4.11 — Non-Text Contrast) so users can perceive the badge shape. Subtle and outline variants often fail this — test them carefully with the Contrast Checker.

WCAG 4.1.2 — Name, Role, Value: Dot badges and count badges that overlay icons carry implicit meaning. This meaning must be programmatically exposed:

  • Count badges: Use aria-label or aria-describedby on the parent element: aria-label="Notifications, 5 unread".
  • Dot badges: Use visually hidden text: <span class="sr-only">New notifications available</span>.

WCAG 1.3.1 — Info and Relationships: Status badges grouped in lists or tables must have their semantic role communicated. Don't just render a colored dot in a table cell — include text or an aria-label on the cell.

Screen reader behavior: Badge content is typically read inline. A badge saying "Beta" next to a heading "API Reference" should be read as "API Reference, Beta." Ensure DOM order matches visual order so screen readers encounter the badge in the right context.

WCAG 2.3.1 — Three Flashes: Animated/pulsing badges must not flash more than three times per second. Use a single, gentle ping animation that respects prefers-reduced-motion: reduce.

Usage Guidelines

Usage Guidelines

Do:

  • Use badges sparingly — too many badges create visual noise that defeats their purpose
  • Cap notification counts with maxCount to prevent layout-breaking widths
  • Pair color with a text label or icon for status badges (accessibility requirement)
  • Use consistent color semantics across your application (green = success everywhere)
  • Place dot badges consistently (top-right for notifications, bottom-right for status)

Don't:

  • Don't use badges for interactive controls — if users need to dismiss or select, use a Tag
  • Don't create new badge colors for one-off cases; stick to your semantic palette
  • Don't animate badges continuously — a brief pulse on appearance is acceptable, perpetual animation is not
  • Don't put badges on badges (avoid nested indicators)
  • Don't use badge text longer than ~20 characters; keep labels concise ("New", not "This item was recently added")

Content guidelines:

  • Badge labels should be 1–2 words maximum
  • Use sentence case ("In review") not uppercase ("IN REVIEW") unless it's a well-known acronym
  • For count badges, decide on a global maxCount and apply it consistently

Code Snippets

html
<!-- Solid badge -->
<span class="badge badge--solid badge--green">Active</span>

<!-- Subtle badge -->
<span class="badge badge--subtle badge--blue">In Review</span>

<!-- Outline badge -->
<span class="badge badge--outline badge--red">Deprecated</span>

<!-- Count badge on icon -->
<div class="badge-wrapper">
  <button aria-label="Notifications, 5 unread" class="icon-button">
    <svg aria-hidden="true"><!-- bell icon --></svg>
  </button>
  <span class="badge badge--count badge--red" aria-hidden="true">5</span>
</div>

<!-- Dot badge on avatar -->
<div class="badge-wrapper">
  <img src="avatar.jpg" alt="Jane Doe" class="avatar" />
  <span class="badge badge--dot badge--green" aria-hidden="true"></span>
  <span class="sr-only">Online</span>
</div>

<style>
.badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  border-radius: var(--radius-full);
  padding: 2px 8px;
  white-space: nowrap;
}
.badge--solid.badge--green {
  background: var(--color-green-600);
  color: var(--color-white);
}
.badge--subtle.badge--blue {
  background: var(--color-blue-100);
  color: var(--color-blue-700);
}
.badge--outline.badge--red {
  background: transparent;
  border: 1px solid var(--color-red-300);
  color: var(--color-red-700);
}
.badge--dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border-radius: 50%;
}
.badge-wrapper {
  position: relative;
  display: inline-flex;
}
.badge--count,
.badge--dot {
  position: absolute;
  top: -2px;
  right: -2px;
}
</style>
tsx
interface BadgeProps {
  variant?: 'solid' | 'subtle' | 'outline';
  color?: 'gray' | 'blue' | 'green' | 'yellow' | 'red' | 'purple';
  size?: 'sm' | 'md' | 'lg';
  pill?: boolean;
  leftIcon?: React.ReactNode;
  children: React.ReactNode;
}

function Badge({
  variant = 'subtle',
  color = 'gray',
  size = 'md',
  pill = true,
  leftIcon,
  children,
}: BadgeProps) {
  return (
    <span
      className={`badge badge--${variant} badge--${color} badge--${size}`}
      style={{ borderRadius: pill ? '9999px' : '4px' }}
    >
      {leftIcon && <span className="badge__icon">{leftIcon}</span>}
      {children}
    </span>
  );
}

// Count badge wrapper
interface CountBadgeProps {
  count: number;
  maxCount?: number;
  showZero?: boolean;
  color?: 'red' | 'blue' | 'gray';
  children: React.ReactNode;
}

function CountBadge({
  count,
  maxCount = 99,
  showZero = false,
  color = 'red',
  children,
}: CountBadgeProps) {
  const display = count > maxCount ? `${maxCount}+` : String(count);
  const visible = count > 0 || showZero;

  return (
    <span className="badge-wrapper">
      {children}
      {visible && (
        <span
          className={`badge badge--count badge--solid badge--${color}`}
          aria-hidden="true"
        >
          {display}
        </span>
      )}
      {visible && (
        <span className="sr-only">{`${count} unread`}</span>
      )}
    </span>
  );
}

Design Systems

Design System Implementations

Material Design 3 offers Badge through MUI as a wrapper component. <Badge badgeContent={4} color="primary"><MailIcon /></Badge> renders a count badge overlaid on the icon. It supports variant="dot" for dot-only, max for overflow capping, invisible for hiding, and anchorOrigin for placement. Material's badge uses a pulsing ring animation (@keyframes ripple) for the dot variant. The color prop accepts "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning".

Ant Design provides Badge with count, dot, overflowCount (default 99), showZero, status (for standalone dot + text combinations), and color (preset or custom hex). Ant's standalone Badge.Ribbon variant renders a ribbon-shaped badge at the corner of a card — a unique layout variant. Status badges (status="processing") include a pulsing animation.

Chakra UI provides Badge as a simple label component with variant="solid|subtle|outline" and colorScheme. For notification-style overlay badges, Chakra does not have a dedicated wrapper — developers typically compose this manually with Box and absolute positioning. Chakra's badge is intentionally simple: a styled <span>.

Radix UI does not provide a Badge primitive, treating it as a styled element rather than a behavioral widget. Shadcn/ui builds on this with a Tailwind-styled Badge that offers variant="default|secondary|destructive|outline" — lean and effective.

Headless UI does not include a Badge component — no behavioral logic is needed for a static indicator.

Use the Color Tool to generate consistent semantic badge palettes that work across light and dark themes.

Data DisplayChipLabelPill