Unified UI

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.

DefaultPrimarySecondaryActivePendingBlockedBetaOutline

Installation

Install the component via the CLI in one command.

npx @work-rjkashyap/unified-ui add badge
pnpm dlx @work-rjkashyap/unified-ui add badge
npx @work-rjkashyap/unified-ui add badge
bunx @work-rjkashyap/unified-ui add badge

If 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-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

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.

DefaultPrimarySecondarySuccessWarningDangerInfoOutline
VariantBackgroundText ColorUse Case
default--muted--foregroundNeutral labels, categories
primary--primary-muted--primary-muted-foregroundBranded tags, active filters
secondary--secondary--secondary-foregroundSubdued labels, archived items
success--success-muted--success-muted-foregroundActive status, completed
warning--warning-muted--warning-muted-foregroundPending, review needed
danger--danger-muted--danger-muted-foregroundBlocked, error, rejected
info--info-muted--info-muted-foregroundBeta, informational, new
outlinetransparent--foregroundSubtle categorization with border

Sizes

SmallMediumLarge
SizeFont SizePadding
sm11pxpx-2 py-0.5
md12pxpx-2.5 py-1
lg14pxpx-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.

OnlineOfflineAwayIdleActive

With Icon

Pass an icon via the icon prop to display it before the label text.

ProPublishedPrivatemainFeatured

With Avatar

Use the avatar prop to render an avatar image or initials before the label — common in user-mention chips.

RK@rjkashyapAK@akumar

Removable / Dismissible

Set removable (or dismissible) to render a dismiss button. Provide an onRemove (or onDismiss) callback to handle dismissal. Both APIs are equivalent.

ReactTypeScriptTailwindDraft

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.

LiveProCritical

Disabled

Disabled badges have reduced opacity and no pointer events. Dismissible disabled badges keep the × button but it is non-functional.

DisabledCan't Remove

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.

ActiveCritical AlertFeatured TagR@rjkashyap

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 PropBadge Equivalent
dismissibledismissible (or removable)
onDismissonDismiss (or onRemove)
dismissLabeldismissLabel (or removeLabel)
avataravatar
disableddisabled
(always animated)animated={true}

Props

PropTypeDefaultDescription
variant"default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "outline""default"Color variant.
size"sm" | "md" | "lg""md"Size of the badge.
dotbooleanfalseShow a colored dot indicator before the text.
iconReactNodeIcon to display before the text.
avatarReactNodeAvatar element rendered before the label.
removablebooleanfalseShow a dismiss (×) button.
dismissiblebooleanfalseAlias for removable.
onRemove(event: MouseEvent) => voidCallback when the remove button is clicked.
onDismiss(event?: MouseEvent) => voidAlias for onRemove.
removeLabelstring"Remove"Accessible label for the remove/dismiss button.
dismissLabelstring"Remove"Alias for removeLabel.
disabledbooleanfalseDisables pointer events and reduces opacity.
animatedbooleanfalseAnimate entrance with popSubtle Framer Motion preset.
asElementType"span"The HTML element or component to render as.
classNamestringAdditional CSS classes.
childrenReactNodeThe badge label.

Motion

  • Animated entrancepopSubtle preset: scales from 0.92 → 1 with a spring (stiffness: 400, damping: 25). Opt-in via animated prop.
  • 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 to Enter / Space.
  • All semantic variant color combinations meet WCAG AA contrast ratios on their muted backgrounds.
  • When using as="button" or as="a", the badge gains interactive semantics automatically.
  • select-none prevents accidental text selection on double-click.
  • Disabled state applies pointer-events-none and reduced opacity.

Design Tokens

TokenUsage
--primary-mutedPrimary variant background
--secondarySecondary variant background
--success-mutedSuccess variant background
--warning-mutedWarning variant background
--danger-mutedDanger variant background
--info-mutedInfo variant background
--mutedDefault variant background
--borderOutline / secondary border
--radius-fullPill shape (rounded-full)
--duration-fastTransition speed for hover/focus

On this page