Import
import { Badge } from "@timelycare/helix-ui"
Props
interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
variant?:
| "default"
| "secondary"
| "outline"
| "destructive"
| "ghost"
| "link"
| "defaultNeutral"
| "defaultNavy"
| "defaultBerry"
| "defaultSage"
| "defaultOrange"
| "outlineNeutral"
| "outlineNavy"
| "outlineBerry"
| "outlineSage"
| "outlineOrange"
asChild?: boolean
className?: string
children: React.ReactNode
}
Variants
| Variant | Use For | Background | Text Color |
|---|---|---|---|
default | Primary actions, main categories | bg-primary | text-primary-foreground |
secondary | Neutral status, less prominent labels | bg-secondary | text-secondary-foreground |
outline | Subtle indicators, bordered tags | bg-transparent | text-foreground |
destructive | Errors, critical status | bg-destructive | text-destructive-foreground |
ghost | Minimal styling, no background | transparent | inherits |
link | Linked badges with underline on hover | transparent | text-primary |
defaultNeutral | Neutral/category (filled) | tc-gray-900 | white |
defaultNavy | Primary blue (filled) | primary (tc-navy-700) | primary-foreground |
defaultBerry | Berry accent (filled) | tc-berry-600 | white |
defaultSage | Green/success (filled) | tc-sage-600 | white |
defaultOrange | Orange/warning (filled) | tc-orange-300 | black |
outlineNeutral | Neutral outline | tc-gray-100 | tc-gray-900 (border: tc-gray-200) |
outlineNavy | Navy outline | tc-navy-50 | primary (border: tc-navy-200) |
outlineBerry | Berry outline | tc-berry-50 | tc-berry-600 (border: tc-berry-200) |
outlineSage | Sage outline | tc-sage-50 | tc-sage-600 (border: tc-sage-200) |
outlineOrange | Orange outline | tc-orange-50 | tc-orange-900 (border: tc-orange-200) |
Choosing a Variant
- Default: Main categories, active status, primary labels
- Secondary: Tags, neutral metadata, less important info
- Outline: Minimal styling, subtle indicators, bordered tags
- Destructive: Errors, removal actions, critical status
- Ghost: Ultra-minimal, no background or border
- Link: Navigational badges with underline on hover
- defaultNeutral / defaultNavy / defaultBerry / defaultSage / defaultOrange: Filled badges using primitive color tokens; use for status, categories, or emphasis by color.
- outlineNeutral / outlineNavy / outlineBerry / outlineSage / outlineOrange: Outline badges with a solid light background (50-step), colored border (200-step), and matching text; use for subtle tags or secondary emphasis.
Styling
Typography
| Property | Value |
|---|---|
| Font | font-sans (Adelle Sans) |
| Size | text-xs (12px) |
| Weight | font-semibold |
| Line Height | leading-4 (16px) |
Dimensions
| Property | Value |
|---|---|
| Height | 20px |
| Padding | px-2 py-0.5 (8px horizontal, 2px vertical) |
| Border Radius | rounded-full (pill / full rounded shape) |
| Icon Gap | gap-1 (4px) |
All badges use the full rounded (pill) shape. Do not use rounded-sm or other radii for badge components.
Color variants (primitive tokens)
Filled (default) and outline variants use Helix primitive color scales. Anatomy:
Default (filled) — 5 color variants
| Variant | Background token | Text token |
|---|---|---|
defaultNeutral | tc-gray-900 | white |
defaultNavy | primary (tc-navy-700) | primary-foreground |
defaultBerry | tc-berry-600 | white |
defaultSage | tc-sage-600 | white |
defaultOrange | tc-orange-300 | black |
Outline — 5 color variants (solid light background, 1px border, matching text)
| Variant | Background | Border | Text |
|---|---|---|---|
outlineNeutral | tc-gray-100 | tc-gray-200 | tc-gray-900 |
outlineNavy | tc-navy-50 | tc-navy-200 | primary |
outlineBerry | tc-berry-50 | tc-berry-200 | tc-berry-600 |
outlineSage | tc-sage-50 | tc-sage-200 | tc-sage-600 |
outlineOrange | tc-orange-50 | tc-orange-200 | tc-orange-900 |
Icons
Size: size-3 (12×12px) — automatic via [&>svg]:size-3
Spacing: gap-1 (4px) — built into badge flex layout
| Icon | Use For |
|---|---|
Check | Completed, approved |
AlertCircle | Alerts, attention needed |
ArrowRight | Links, navigation |
X | Removable badges |
Star | Featured items |
States
| State | Implementation |
|---|---|
| Default | Base appearance |
| Hover | Built-in opacity/color change (anchor badges only via [a&]:hover:) |
| Focus | ring-[3px] ring-ring/50 border-ring |
Common Patterns
Basic Variants
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="destructive">Destructive</Badge>
With Icons
import { Check, ArrowRight } from "lucide-react"
<Badge>
<Check />
Approved
</Badge>
<Badge>
Link
<ArrowRight />
</Badge>
Notification Count (Badge Number)
// Single digit - circular
<Badge className="size-5 rounded-full p-0 justify-center">8</Badge>
// Double digit
<Badge className="h-5 min-w-5 rounded-full px-1.5 justify-center">99</Badge>
// Overflow
<Badge className="rounded-full px-2">20+</Badge>
Pending/Processing
<Badge variant="outline" className="gap-1">
<LoaderCircle className="size-3 animate-spin" />
Processing
</Badge>
In Context
// With card title
<div className="flex items-center gap-2">
<CardTitle>Article Title</CardTitle>
<Badge variant="secondary">New</Badge>
</div>
// User status
<div className="flex items-center gap-2">
<Avatar />
<span>Username</span>
<Badge className="gap-1">
<BadgeCheck />
Verified
</Badge>
</div>
Component Structure
{/* Standard Badge */}
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-primary px-2 py-0.5 text-xs font-semibold text-primary-foreground">
{/* Optional left icon */}
<Check className="size-3" />
{/* Badge text */}
Badge
{/* Optional right icon */}
<ArrowRight className="size-3" />
</span>
{/* Badge Number (circular) */}
<span className="inline-flex size-5 items-center justify-center rounded-full bg-primary p-0 text-xs font-semibold text-primary-foreground">
8
</span>
Accessibility
- Styled inline element — no interactive behavior
- Screen reader: reads badge text content automatically
- Use
aria-labelif badge meaning isn't clear from text alone - No keyboard interaction (non-interactive element)
- Use
asChildto render as a different element (e.g., anchor for linked badges)
Gotchas
| Problem | Solution |
|---|---|
| Badge too tall | Default height is 20px; use py-0.5 padding |
| Icon wrong size | SVGs auto-size to size-3 (12px) via [&>svg]:size-3 |
| Notification badge not circular | Add rounded-full and size-5 for single digit |
| Need removable badge | Add X icon with click handler |
| Icons not aligned | Use gap-1 for proper spacing |
| Need a navigational badge | Use variant="link" with asChild wrapping an anchor |
See Also
- Related Components: Avatar (avatar with status badge), Button (actionable badges)
- Tokens: Colors — Theme-aware color tokens
Last updated: April 22, 2026