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
| Variant | Purpose | Visual Treatment |
|---|---|---|
| Solid | High emphasis, critical status, primary categorization. | Filled background with contrasting text. Strongest visual weight. |
| Subtle / Soft | Medium emphasis, non-critical metadata. | Tinted background (10–15% opacity of the base color) with saturated text. |
| Outline | Low emphasis, secondary information. | Transparent background with colored border and text. |
| Dot | Minimal indicator, presence/attention signal. | Small colored circle (6–10px) with no text. Attached to parent element. |
| Numeric / Count | Notification 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:
| Color | Semantic | Example Use |
|---|---|---|
| Gray / Neutral | Default, informational, inactive | "Draft", "Archived", "N/A" |
| Blue / Info | Informational, in-progress | "In Review", "Beta", "New" |
| Green / Success | Positive, active, complete | "Active", "Published", "Online" |
| Yellow / Warning | Caution, pending | "Pending", "Expiring Soon" |
| Red / Error | Critical, destructive, urgent | "Failed", "Overdue", "Deprecated" |
| Purple | Premium, special | "Pro", "Admin", "Featured" |
Size Variants
| Size | Height | Padding | Font Size | Use Case |
|---|---|---|---|---|
| Small (sm) | 18px | 4px 6px | 11px | Dense UIs, table cells, inline metadata |
| Medium (md) | 22px | 4px 8px | 12px | Default for most contexts |
| Large (lg) | 26px | 4px 10px | 13px | Standalone 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
| Property | Type | Default | Description |
|---|---|---|---|
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 |
count | number | — | For numeric badges. When set, renders count text. |
maxCount | number | 99 | Caps display at this number (shows "99+" when exceeded) |
showZero | boolean | false | Whether to render the badge when count is 0 |
dot | boolean | false | Renders 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 |
pill | boolean | true | Applies full border-radius for pill shape |
leftIcon | ReactNode | — | Icon rendered before the label |
children | ReactNode | — | 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 Category | Token Example | Badge Usage |
|---|---|---|
| Color – Solid Fill | --color-green-600 | Solid variant background |
| Color – Subtle Fill | --color-green-100 | Subtle variant tinted background |
| Color – Subtle Text | --color-green-700 | Subtle variant text color |
| Color – Outline Border | --color-green-300 | Outline variant border |
| Color – On Fill | --color-white | Solid variant text |
| Border Radius | --radius-full (9999px) | Pill shape (default) |
| Border Radius | --radius-sm (4px) | Square badge variant |
| Typography | --font-size-xs, --font-weight-medium | Badge label text |
| Spacing | --space-1 / --space-2 | Horizontal and vertical padding |
| Shadow | --shadow-xs | Overlay 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:
| State | Visual Change | Notes |
|---|---|---|
| Default | Standard 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 / Animated | Subtle 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-labeloraria-describedbyon 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
maxCountto 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
maxCountand apply it consistently
Code Snippets
<!-- 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>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.