Badge
A compact label component for status indicators, categories, and metadata. Supports 3 variants, 6 color schemes, dot indicators, icons, dismissible behavior, and animated enter/exit transitions powered by Framer Motion.
Live Preview
Interact with the badge component in real time. Adjust variant, color, size, and features to see changes instantly.
1<Badge2variant="subtle"3color="primary"4size="md"5>6Badge Label7</Badge>Installation
Install the Aidash Components package using your preferred package manager.
$ pnpm add @aidash/componentsImport
Import the Badge and BadgeGroup components into your file.
import { Badge, BadgeGroup } from '@/components/aidash/badge';Usage
The simplest way to use Badge with default props.
1<Badge>Default</Badge>2<Badge color="success">Success</Badge>3<Badge variant="solid" color="primary">Solid</Badge>Variants
Three visual variants for different emphasis levels and contexts.
solid
outline
subtle
1{/* Solid — high emphasis */}2<Badge variant="solid" color="primary">Primary</Badge>3<Badge variant="solid" color="success">Success</Badge>4 5{/* Outline — medium emphasis */}6<Badge variant="outline" color="warning">Warning</Badge>7<Badge variant="outline" color="error">Error</Badge>8 9{/* Subtle — low emphasis (default) */}10<Badge variant="subtle" color="accent">Accent</Badge>11<Badge variant="subtle" color="neutral">Neutral</Badge>subtle for inline metadata, outline for secondary status, and solid when the badge needs to stand out clearly.Sizes
Three sizes to fit different layout contexts and text hierarchies.
1<Badge size="sm">Small</Badge>2<Badge size="md">Medium</Badge>3<Badge size="lg">Large</Badge>Colors
Six semantic color schemes for status, categories, and emphasis.
1<Badge color="primary">Primary</Badge>2<Badge color="accent">Accent</Badge>3<Badge color="success">Success</Badge>4<Badge color="warning">Warning</Badge>5<Badge color="error">Error</Badge>6<Badge color="neutral">Neutral</Badge>Features
Advanced capabilities including dot indicators, icons, dismissible behavior, and grouping.
Dot Indicator
1<Badge dot color="success">Online</Badge>2<Badge dot color="warning">Away</Badge>3<Badge dot color="error">Busy</Badge>4<Badge dot color="neutral">Offline</Badge>Dismissible
1<Badge dismissible color="primary">React</Badge>2<Badge dismissible color="accent">TypeScript</Badge>3 4{/* With callback */}5<Badge dismissible onDismiss={() => console.log('dismissed')}>6Removable7</Badge>With Icon
1<Badge icon={<StarIcon />} color="warning" variant="solid">Featured</Badge>2<Badge icon={<CheckIcon />} color="success">Verified</Badge>3<Badge icon={<AlertIcon />} color="error" variant="outline">Urgent</Badge>BadgeGroup
Gap: sm
Gap: md (default)
Gap: lg
1<BadgeGroup gap="sm">2<Badge color="primary" size="sm">React</Badge>3<Badge color="accent" size="sm">Next.js</Badge>4<Badge color="success" size="sm">TypeScript</Badge>5</BadgeGroup>6 7<BadgeGroup>8<Badge color="primary">React</Badge>9<Badge color="accent">Next.js</Badge>10<Badge color="success">TypeScript</Badge>11</BadgeGroup>Accessibility
Built-in accessibility features for inclusive user experiences.
Semantic Markup
Renders as a <span> element, appropriate for inline labeling. Screen readers will read the badge text content naturally within the document flow.
Dismiss Button
The dismiss button includes aria-label="Dismiss" for screen readers. It is keyboard accessible with visible focus-visible ring styling.
Color Contrast
All color and variant combinations are designed to meet WCAG AA contrast requirements. The solid variant uses white text on dark backgrounds; subtle and outline variants use appropriately contrasting text colors.
Motion Preferences
Animations are powered by Framer Motion which respects the prefers-reduced-motion media query. Users who prefer reduced motion will see instant transitions.
1{/* Badge text is read naturally by screen readers */}2<Badge color="success" dot>Active</Badge>3 4{/* Dismissible badges have accessible dismiss buttons */}5<Badge dismissible onDismiss={() => removeTag('react')}>6React7</Badge>8 9{/* Use aria-label on parent for additional context */}10<div aria-label="Project tags">11<BadgeGroup>12 <Badge>Frontend</Badge>13 <Badge>TypeScript</Badge>14</BadgeGroup>15</div>API Reference
Complete list of props accepted by Badge and BadgeGroup.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Badge label content |
| variant | 'solid' | 'outline' | 'subtle' | 'subtle' | Visual style of the badge |
| color | 'primary' | 'accent' | 'success' | 'warning' | 'error' | 'neutral' | 'primary' | Color scheme of the badge |
| size | 'sm' | 'md' | 'lg' | 'md' | Size of the badge |
| dismissible | boolean | false | Show a dismiss button to remove the badge |
| onDismiss | () => void | — | Callback when the dismiss button is clicked |
| icon | ReactNode | — | Icon rendered before the label |
| dot | boolean | false | Show a colored dot indicator before the label |
| className | string | '' | Additional CSS classes |
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | BadgeGroup content (Badge elements) |
| gap | 'sm' | 'md' | 'lg' | 'md' | Gap between badges |
| className | string | '' | Additional CSS classes |
AnimatePresence internally for smooth exit animations when dismissed. Each Badge manages its own visibility state, so dismissed badges animate out automatically.Examples
Realistic production examples showing badges in context.
Status Indicators
1<div className="flex items-center justify-between">2<span>API Gateway</span>3<Badge dot color="success" size="sm">Operational</Badge>4</div>5<div className="flex items-center justify-between">6<span>Database Cluster</span>7<Badge dot color="warning" size="sm">Degraded</Badge>8</div>Notification Counts
1<div className="flex items-center gap-2">2<span>Messages</span>3<Badge variant="solid" color="primary" size="sm">12</Badge>4</div>5<div className="flex items-center gap-2">6<span>Alerts</span>7<Badge variant="solid" color="error" size="sm">3</Badge>8</div>Tag Lists
Project Tags
1<BadgeGroup>2<Badge icon={<TagIcon />} color="primary" dismissible>Frontend</Badge>3<Badge icon={<TagIcon />} color="accent" dismissible>Design System</Badge>4<Badge icon={<TagIcon />} color="success" dismissible>Open Source</Badge>5<Badge icon={<TagIcon />} color="warning" dismissible>In Progress</Badge>6</BadgeGroup>User Roles
Admin User
admin@example.com
Editor User
editor@example.com
Viewer User
viewer@example.com
1<Badge icon={<ShieldIcon />} variant="solid" color="primary" size="sm">2Admin3</Badge>4<Badge variant="outline" color="success" size="sm">Editor</Badge>5<Badge variant="subtle" color="neutral" size="sm">Viewer</Badge>Best Practices
Guidelines for using badges effectively in your interface.
- Use consistent color semantics (green for success, red for error, etc.)
- Keep badge labels short and scannable (1-2 words)
- Use dot indicators for real-time status (online/offline)
- Use BadgeGroup to wrap multiple related badges for proper spacing
- Use subtle variant for metadata that should not draw too much attention
- Provide onDismiss callback for dismissible badges to update state
- Use badges for long text or full sentences
- Mix different badge sizes within the same group
- Use success color for non-positive statuses or error color for non-critical items
- Overload a view with too many solid badges competing for attention
- Use badges as interactive elements (use buttons instead)
- Forget to handle the onDismiss callback when using dismissible badges
Source Code
Full source code for the Badge and BadgeGroup components.
'use client';
import { motion, AnimatePresence } from 'framer-motion';
import { useState, type ReactNode } from 'react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Cancel01Icon } from '@hugeicons/core-free-icons';
export type BadgeVariant = 'solid' | 'outline' | 'subtle';
export type BadgeColor = 'primary' | 'accent' | 'success' | 'warning' | 'error' | 'neutral';
export type BadgeSize = 'sm' | 'md' | 'lg';
export interface BadgeProps {
children: ReactNode;
variant?: BadgeVariant;
color?: BadgeColor;
size?: BadgeSize;
dismissible?: boolean;
onDismiss?: () => void;
icon?: ReactNode;
dot?: boolean;
className?: string;
}
const colorMap: Record<BadgeColor, Record<BadgeVariant, string>> = {
primary: {
solid: 'bg-brand-500 text-white',
outline: 'bg-transparent border border-brand-500 text-brand-500',
subtle: 'bg-brand-500/10 text-brand-500',
},
accent: {
solid: 'bg-brand-300 text-white',
outline: 'bg-transparent border border-brand-300 text-brand-400',
subtle: 'bg-brand-300/10 text-brand-400',
},
success: {
solid: 'bg-success-600 text-white dark:bg-success-500',
outline: 'bg-transparent border border-success-500 text-success-600 dark:text-success-400',
subtle: 'bg-success-50 text-success-700 dark:bg-success-500/10 dark:text-success-400',
},
warning: {
solid: 'bg-warn-500 text-white',
outline: 'bg-transparent border border-warn-500 text-warn-600 dark:text-warn-400',
subtle: 'bg-warn-50 text-warn-700 dark:bg-warn-500/10 dark:text-warn-400',
},
error: {
solid: 'bg-danger-600 text-white dark:bg-danger-500',
outline: 'bg-transparent border border-danger-500 text-danger-600 dark:text-danger-400',
subtle: 'bg-danger-50 text-danger-700 dark:bg-danger-500/10 dark:text-danger-400',
},
neutral: {
solid: 'bg-neutral-500 text-white',
outline: 'bg-transparent border border-(--border) text-(--text-muted)',
subtle: 'bg-neutral-100 text-neutral-500 dark:bg-neutral-500/10 dark:text-neutral-400',
},
};
const sizeStyles: Record<BadgeSize, string> = {
sm: 'text-[10px] px-2 py-0.5 gap-1',
md: 'text-[10px] px-2.5 py-0.5 gap-1.5',
lg: 'text-xs px-3 py-1 gap-1.5',
};
const dotSizes: Record<BadgeSize, string> = {
sm: 'w-1.5 h-1.5',
md: 'w-2 h-2',
lg: 'w-2.5 h-2.5',
};
const dotColors: Record<BadgeColor, string> = {
primary: 'bg-brand-500',
accent: 'bg-brand-300',
success: 'bg-success-500',
warning: 'bg-warn-500',
error: 'bg-danger-500',
neutral: 'bg-neutral-400',
};
export function Badge({
children,
variant = 'subtle',
color = 'primary',
size = 'md',
dismissible = false,
onDismiss,
icon,
dot = false,
className = '',
}: BadgeProps) {
const [visible, setVisible] = useState(true);
const handleDismiss = () => {
setVisible(false);
onDismiss?.();
};
return (
<AnimatePresence>
{visible && (
<motion.span
initial={{ opacity: 0, scale: 0.85 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.85 }}
transition={{ duration: 0.2, ease: [0.2, 0, 0, 1] }}
className={`inline-flex items-center font-medium rounded-lg whitespace-nowrap capitalize ${colorMap[color][variant]} ${sizeStyles[size]} ${className}`}
>
{dot && (
<span aria-hidden="true" className={`rounded-full shrink-0 ${dotSizes[size]} ${variant === 'solid' ? 'bg-white/70' : dotColors[color]}`} />
)}
{icon && <span aria-hidden="true" className="shrink-0 [&>svg]:w-3.5 [&>svg]:h-3.5">{icon}</span>}
{children}
{dismissible && (
<button
type="button"
onClick={handleDismiss}
className="shrink-0 ml-0.5 rounded p-0.5 hover:bg-black/10 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-current"
aria-label="Dismiss"
>
<HugeiconsIcon icon={Cancel01Icon} size={10} aria-hidden="true" />
</button>
)}
</motion.span>
)}
</AnimatePresence>
);
}
export interface BadgeGroupProps {
children: ReactNode;
gap?: 'sm' | 'md' | 'lg';
className?: string;
}
const gapStyles: Record<string, string> = {
sm: 'gap-1',
md: 'gap-2',
lg: 'gap-3',
};
export function BadgeGroup({ children, gap = 'md', className = '' }: BadgeGroupProps) {
return (
<div className={`flex flex-wrap items-center ${gapStyles[gap]} ${className}`}>
{children}
</div>
);
}Related
Other components that work well alongside Badge.