Badge
A compact label component for categorization, status indicators, metadata display, and interactive tags. Supports 8 semantic variants, dot indicators, avatars, icons, dismissible state, and optional Framer Motion animation.
Basic
A compact inline label for categorization, status, filtering, and metadata. Supports 8 semantic variants, dot indicators, avatars, icons, dismissible mode, and optional pop animation.
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add badgepnpm dlx @work-rjkashyap/unified-ui add badgenpx @work-rjkashyap/unified-ui add badgebunx @work-rjkashyap/unified-ui add badgeIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import {
Badge,
Avatar,
Tag,
} from "@work-rjkashyap/unified-ui";Basic Usage
<Badge>Label</Badge>
<Badge variant="primary">Feature</Badge>
<Badge variant="success" dot>Online</Badge>
<Badge variant="danger" dismissible onDismiss={handleDismiss}>Critical</Badge>Variants
Eight semantic variants covering the full range of categorization and status needs.
| Variant | Background | Text Color | Use Case |
|---|---|---|---|
default | --muted | --foreground | Neutral labels, categories |
primary | --primary-muted | --primary-muted-foreground | Branded tags, active filters |
secondary | --secondary | --secondary-foreground | Subdued labels, archived items |
success | --success-muted | --success-muted-foreground | Active status, completed |
warning | --warning-muted | --warning-muted-foreground | Pending, review needed |
danger | --danger-muted | --danger-muted-foreground | Blocked, error, rejected |
info | --info-muted | --info-muted-foreground | Beta, informational, new |
outline | transparent | --foreground | Subtle categorization with border |
Sizes
| Size | Font Size | Padding |
|---|---|---|
sm | 11px | px-2 py-0.5 |
md | 12px | px-2.5 py-1 |
lg | 14px | px-3 py-1.5 |
Dot Indicator
Use the dot prop to display a colored status dot before the label. The dot color automatically matches the badge variant.
With Icon
Pass an icon via the icon prop to display it before the label text.
With Avatar
Use the avatar prop to render an avatar image or initials before the label — common in user-mention chips.
Removable / Dismissible
Set removable (or dismissible) to render a dismiss button. Provide an onRemove (or onDismiss) callback to handle dismissal. Both APIs are equivalent.
Animated
Set animated to enable a subtle pop entrance animation using the popSubtle Framer Motion preset. This is useful for badges that appear dynamically. The animation respects prefers-reduced-motion.
Disabled
Disabled badges have reduced opacity and no pointer events. Dismissible disabled badges keep the × button but it is non-functional.
Polymorphic
Use the as prop to render the badge as a different element — like a link or button.
{
/* As a link */
}
<Badge as="a" href="/category/react" variant="primary">
React
</Badge>;
{
/* As a button */
}
<Badge as="button" variant="outline" onClick={handleClick}>
Filter
</Badge>;Combined Features
Badges can combine dot, icon, avatar, and removable features together.
Tag Input Pattern
Combine dismissible badges with an input for a tag entry UI.
function TagInput() {
const [tags, setTags] = useState(["design-system", "react"]);
const [input, setInput] = useState("");
const addTag = () => {
const trimmed = input.trim().toLowerCase();
if (trimmed && !tags.includes(trimmed)) {
setTags([...tags, trimmed]);
}
setInput("");
};
return (
<div className="flex flex-wrap gap-2 p-2 border border-border rounded-md min-h-[40px]">
{tags.map((tag) => (
<Badge
key={tag}
variant="primary"
size="sm"
dismissible
animated
onDismiss={() => setTags(tags.filter((t) => t !== tag))}
>
{tag}
</Badge>
))}
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTag()}
placeholder="Add tag..."
className="flex-1 min-w-[80px] outline-none bg-transparent text-sm"
/>
</div>
);
}Filter Chips
Badges work well as interactive filter toggles in list/table UIs.
const FILTERS = ["All", "Active", "Pending", "Archived"];
const [active, setActive] = useState("All");
<div className="flex gap-2 flex-wrap">
{FILTERS.map((filter) => (
<Badge
key={filter}
as="button"
variant={active === filter ? "primary" : "default"}
size="md"
onClick={() => setActive(filter)}
className="cursor-pointer"
>
{filter}
</Badge>
))}
</div>;Migrating from Tag
The Tag component has been merged into Badge. The Tag export is still available as a backward-compatible alias that defaults to animated={true}.
// Before (Tag)
import { Tag } from "@work-rjkashyap/unified-ui";
<Tag variant="primary" dismissible onDismiss={fn}>
React
</Tag>;
// After (Badge) — equivalent
import { Badge } from "@work-rjkashyap/unified-ui";
<Badge variant="primary" dismissible onDismiss={fn} animated>
React
</Badge>;| Tag Prop | Badge Equivalent |
|---|---|
dismissible | dismissible (or removable) |
onDismiss | onDismiss (or onRemove) |
dismissLabel | dismissLabel (or removeLabel) |
avatar | avatar |
disabled | disabled |
| (always animated) | animated={true} |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "outline" | "default" | Color variant. |
size | "sm" | "md" | "lg" | "md" | Size of the badge. |
dot | boolean | false | Show a colored dot indicator before the text. |
icon | ReactNode | — | Icon to display before the text. |
avatar | ReactNode | — | Avatar element rendered before the label. |
removable | boolean | false | Show a dismiss (×) button. |
dismissible | boolean | false | Alias for removable. |
onRemove | (event: MouseEvent) => void | — | Callback when the remove button is clicked. |
onDismiss | (event?: MouseEvent) => void | — | Alias for onRemove. |
removeLabel | string | "Remove" | Accessible label for the remove/dismiss button. |
dismissLabel | string | "Remove" | Alias for removeLabel. |
disabled | boolean | false | Disables pointer events and reduces opacity. |
animated | boolean | false | Animate entrance with popSubtle Framer Motion preset. |
as | ElementType | "span" | The HTML element or component to render as. |
className | string | — | Additional CSS classes. |
children | ReactNode | — | The badge label. |
Motion
- Animated entrance —
popSubtlepreset: scales from0.92 → 1with a spring (stiffness: 400, damping: 25). Opt-in viaanimatedprop. - Respects
prefers-reduced-motion— disables animation when reduced motion is preferred.
Accessibility
Color is never the sole means of conveying information. Always pair semantic colors with a descriptive text label.
- Renders as a
<span>element by default — non-interactive. - The dot indicator is decorative (
aria-hidden="true") — the text label conveys the meaning. - The remove/dismiss button includes a configurable
aria-label(defaults to"Remove"). - Dismiss button is focusable with
tabIndex={0}and responds toEnter/Space. - All semantic variant color combinations meet WCAG AA contrast ratios on their muted backgrounds.
- When using
as="button"oras="a", the badge gains interactive semantics automatically. select-noneprevents accidental text selection on double-click.- Disabled state applies
pointer-events-noneand reduced opacity.
Design Tokens
| Token | Usage |
|---|---|
--primary-muted | Primary variant background |
--secondary | Secondary variant background |
--success-muted | Success variant background |
--warning-muted | Warning variant background |
--danger-muted | Danger variant background |
--info-muted | Info variant background |
--muted | Default variant background |
--border | Outline / secondary border |
--radius-full | Pill shape (rounded-full) |
--duration-fast | Transition speed for hover/focus |