Badge
Displays a badge or a component that looks like a badge.
Installation
npx shadcn@latest add badge
Usage
import { Badge } from "@/components/ui/badge"
export function BadgeDemo() {
return <Badge variant="default">Badge</Badge>
}
Examples
Variants
With Icons
Interactive
Shapes & Styles
API Reference
Badge
A versatile badge component with multiple variants and styles.
Prop | Type | Default | Description |
---|---|---|---|
variant | "default" | "secondary" | "destructive" | "outline" | "default" | The visual style variant of the badge. |
asChild | boolean | false | Render as a child component (useful for links). |
className | string | - | Additional CSS classes for styling. |
children | React.ReactNode | - | Badge content (text, icons, or components). |
Variants
Default
Primary branded appearance with high contrast.
<Badge>Default Badge</Badge>
Secondary
Neutral appearance with muted colors.
<Badge variant="secondary">Secondary Badge</Badge>
Destructive
Error or warning states with destructive colors.
<Badge variant="destructive">Destructive Badge</Badge>
Outline
Subtle appearance with border styling.
<Badge variant="outline">Outline Badge</Badge>
Implementation Details
Architecture
Built with class-variance-authority
for robust variant management:
- Flexible Composition: Uses Radix Slot for
asChild
functionality - Icon Integration: Automatic sizing and spacing for icons
- Focus Management: Built-in focus states for interactive badges
- Responsive Design: Scales appropriately across screen sizes
Styling System
The badge uses a comprehensive CSS class system:
// Base styles include:
// - Inline flex layout with center alignment
// - Rounded corners and border
// - Text sizing and font weight
// - Icon sizing ([&>svg]:size-3)
// - Focus and hover states
// - Transition animations
Icon Handling
Icons are automatically styled within badges:
<Badge>
<CheckIcon /> {/* Automatically sized to 12px (size-3) */}
Completed
</Badge>
Best Practices
Content Guidelines
- Keep text concise: Single words or short phrases work best
- Use meaningful labels: Provide context for the badge purpose
- Consider accessibility: Ensure sufficient color contrast
- Consistent sizing: Maintain visual hierarchy with appropriate variants
Visual Hierarchy
// Primary information - use default
<Badge>Important</Badge>
// Secondary information - use secondary or outline
<Badge variant="secondary">Optional</Badge>
<Badge variant="outline">Metadata</Badge>
// Errors or warnings - use destructive
<Badge variant="destructive">Error</Badge>
Icon Placement
Icons can be placed before, after, or on both sides of text:
// Leading icon (recommended)
<Badge><CheckIcon />Verified</Badge>
// Trailing icon
<Badge>External<ExternalLinkIcon /></Badge>
// Both sides (use sparingly)
<Badge><StarIcon />Premium<CrownIcon /></Badge>
Common Use Cases
Status Indicators
Perfect for showing states and conditions:
function StatusBadge({ status }: { status: 'active' | 'pending' | 'inactive' }) {
const variants = {
active: { variant: 'default' as const, icon: CheckIcon, text: 'Active' },
pending: { variant: 'secondary' as const, icon: ClockIcon, text: 'Pending' },
inactive: { variant: 'outline' as const, icon: XIcon, text: 'Inactive' }
}
const config = variants[status]
return (
<Badge variant={config.variant}>
<config.icon />
{config.text}
</Badge>
)
}
Notification Counters
Circular badges for counts and numbers:
function NotificationBadge({ count }: { count: number }) {
return (
<Badge
variant={count > 99 ? 'destructive' : 'default'}
className="h-6 w-6 rounded-full p-0 flex items-center justify-center text-xs font-bold"
>
{count > 99 ? '99+' : count}
</Badge>
)
}
Tag Lists
For categorization and filtering:
function TagList({ tags, onRemove }: {
tags: string[]
onRemove?: (tag: string) => void
}) {
return (
<div className="flex flex-wrap gap-2">
{tags.map(tag => (
<Badge key={tag} variant="secondary" className="gap-1">
{tag}
{onRemove && (
<button
onClick={() => onRemove(tag)}
className="ml-1 hover:bg-secondary-foreground/20 rounded-full p-0.5"
aria-label={`Remove ${tag} tag`}
>
<XIcon className="size-3" />
</button>
)}
</Badge>
))}
</div>
)
}
Link Badges
Using the asChild
prop for navigation:
function LinkBadge() {
return (
<Badge asChild>
<Link href="/profile" className="hover:underline">
<UserIcon />
View Profile
</Link>
</Badge>
)
}
Product Labels
E-commerce and feature highlighting:
function ProductBadges({ product }) {
return (
<div className="flex gap-2">
{product.isNew && (
<Badge className="bg-green-500 hover:bg-green-600">
<StarIcon />
New
</Badge>
)}
{product.onSale && (
<Badge variant="destructive">
<PercentIcon />
Sale
</Badge>
)}
{product.isFeatured && (
<Badge className="bg-purple-500 hover:bg-purple-600">
<CrownIcon />
Featured
</Badge>
)}
</div>
)
}
Advanced Patterns
Gradient Badges
Custom styling with gradients:
<Badge className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600">
<StarIcon />
Premium
</Badge>
Animated Badges
Adding motion for attention:
<Badge className="animate-pulse bg-red-500 hover:bg-red-600">
<AlertIcon />
Live
</Badge>
// Or with custom animation
<Badge className="relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer" />
<StarIcon />
Special
</Badge>
Stacked Badges
Multiple badges in hierarchy:
function StackedBadges() {
return (
<div className="relative inline-flex">
<Badge>Primary</Badge>
<Badge
variant="destructive"
className="absolute -top-2 -right-2 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs"
>
3
</Badge>
</div>
)
}
Conditional Badges
Dynamic display based on conditions:
function ConditionalBadges({ user }) {
return (
<div className="flex gap-2">
<Badge>{user.name}</Badge>
{user.isVerified && (
<Badge className="bg-blue-500 hover:bg-blue-600">
<CheckIcon />
Verified
</Badge>
)}
{user.isPremium && (
<Badge className="bg-gold-500 hover:bg-gold-600">
<CrownIcon />
Premium
</Badge>
)}
{user.isOnline ? (
<Badge className="bg-green-500 hover:bg-green-600">Online</Badge>
) : (
<Badge variant="outline">Offline</Badge>
)}
</div>
)
}
Accessibility
Screen Reader Support
Badges automatically provide semantic meaning:
// Good - descriptive content
<Badge><CheckIcon />Verified Account</Badge>
// Better - with aria-label for complex badges
<Badge aria-label="Account verified with email confirmation">
<CheckIcon />
Verified
</Badge>
// Best - with hidden text for icons-only badges
<Badge>
<CheckIcon />
<span className="sr-only">Verified</span>
</Badge>
Color Contrast
Ensure sufficient contrast ratios:
// Built-in variants maintain WCAG AA compliance
<Badge variant="default">High Contrast</Badge>
// Check custom colors
<Badge className="bg-yellow-400 text-black">Custom Color</Badge>
Interactive Badges
Provide proper focus states and keyboard navigation:
<Badge asChild>
<button
onClick={handleClick}
className="focus:outline-none focus:ring-2 focus:ring-primary"
aria-label="Remove tag"
>
Remove <XIcon />
</button>
</Badge>
Performance Considerations
Bundle Size
Badge components are lightweight but consider:
// Import only what you need from icon libraries
import { CheckIcon } from 'lucide-react'
// Not the entire library
import * as Icons from 'lucide-react' // ❌
Rendering Optimization
For large lists of badges:
// Memoize badge components in lists
const BadgeItem = memo(({ item }) => (
<Badge key={item.id}>{item.name}</Badge>
))
// Use keys properly for React reconciliation
{items.map(item => (
<Badge key={item.id}>{item.name}</Badge>
))}
Troubleshooting
Common Issues
- Badge not showing: Check if content is provided
- Icons not aligned: Ensure proper icon size (use size-3 class)
- Interactive badges not responding: Verify event handlers and focus states
- Inconsistent spacing: Use consistent gap classes in containers
Debugging
// Add visual debugging
<Badge className="border-2 border-red-500 bg-red-100">
Debug Badge
</Badge>
// Check variant application
console.log('Badge variant:', variant) // Log current variant
Integration Examples
With Forms
function FormWithBadges() {
const [selectedTags, setSelectedTags] = useState<string[]>([])
return (
<form className="space-y-4">
<div>
<label>Selected Categories</label>
<div className="flex flex-wrap gap-2 mt-2">
{selectedTags.map(tag => (
<Badge key={tag} variant="secondary">
{tag}
<button onClick={() => removeTag(tag)}>
<XIcon className="size-3" />
</button>
</Badge>
))}
</div>
</div>
</form>
)
}
With Data Tables
function DataTableWithBadges({ data }) {
return (
<table>
{data.map(row => (
<tr key={row.id}>
<td>{row.name}</td>
<td>
<Badge variant={row.status === 'active' ? 'default' : 'secondary'}>
{row.status}
</Badge>
</td>
</tr>
))}
</table>
)
}
SEO Considerations
- Semantic HTML: Badges maintain proper semantic structure
- Content Indexing: Badge text content is indexed by search engines
- Structured Data: Consider schema markup for product badges
- Performance: Lightweight component doesn't impact Core Web Vitals
- Accessibility: Proper ARIA attributes improve SEO rankings