Loading…
Loading…
A small status indicator or label used to highlight information.
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:
When NOT to use a Badge:
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.
| 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. |
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 | 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 badges overlay their parent element. Common positions:
The dot should overlap the parent's bounding box by roughly 25–40% to feel attached without obscuring content.
| 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.
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.
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. |
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:
aria-label or aria-describedby on the parent element: aria-label="Notifications, 5 unread".<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.
Do:
maxCount to prevent layout-breaking widthsDon't:
Content guidelines:
maxCount and apply it consistently<!-- 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>
);
}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.