React Badge Examples — Variants, Icons, Counts, Status
Copy-paste React Badge examples — five variants, Lucide icon pairing, notification count overlays, table status cells, and data-driven plan tiers with Drivn.
Badges are the smallest surface in a React interface — a one- or two-word label that carries outsized meaning. A New pill draws attention to a just-released feature; a Beta marks a still-unstable page; a numeric counter reveals unread notifications; a status pill tells you a deployment is live, failed, or pending. Used well, badges are a typographic signal the eye picks up before any body copy.
Drivn's Badge component ships five variants — default, secondary, success, outline, destructive — plus automatic icon spacing via inline-flex items-center gap-1.5. It is a single <span> with no sub-components, no BadgeLabel or BadgeIcon wrapper, and no runtime dependencies beyond React and Tailwind. Pass variant, pass children (text, icons, or both), and you are done.
This page collects five badge patterns you will actually ship: a variant showcase for visual reference, an icon-plus-label pattern for active and failed states, a count badge overlayed on an icon button for notifications, a status badge used inside a data table cell, and a dynamic badge driven from a server-side field. Every snippet is copy-paste and assumes you have installed Drivn via the CLI and imported Badge from @/components/ui/badge. For an engineering comparison against shadcn/ui, see Drivn vs shadcn/ui Badge.
All five variants at a glance
Drivn's Badge ships five variants, each referencing a design token so colors adapt to your theme automatically. default uses the primary accent with a soft translucent background; secondary uses the secondary token for a lower-emphasis alternative; success uses the success token for green pills that signal healthy or active states; outline drops the fill entirely and renders only a border with muted text, appropriate for tags and categories; destructive uses the destructive token for error, failure, or danger states.
The variants are a single prop away — no cva, no forwardRef, no configuration. Drop the badge inline with any text, inside a card header, alongside a table cell, or as a decorator on a list item.
1 <Badge variant="default">Default</Badge> 2 <Badge variant="secondary">Secondary</Badge> 3 <Badge variant="success">Success</Badge> 4 <Badge variant="outline">Outline</Badge> 5 <Badge variant="destructive">Destructive</Badge>
Badge with a Lucide icon
Pairing an icon with a single word gives a badge roughly double the communicative density. A checkmark next to "Active", a warning triangle next to "Failed", a clock next to "Pending" — the icon registers preattentively and the word confirms the meaning.
Drivn's Badge has gap-1.5 built into its base class, so an icon and text render with consistent spacing without any flex wrapper or gap utility at the call site. Size the icon with w-3 h-3 (12 px) to match the text-xs label, and the pair sits balanced inside the px-2.5 py-0.5 padding of the badge container. Every variant inherits the same gap, so icon layout stays uniform regardless of color.
1 import { Check, CircleAlert, Clock } from 'lucide-react' 2 import { Badge } from '@/components/ui/badge' 3 4 <Badge variant="success"> 5 <Check className="w-3 h-3" /> 6 Active 7 </Badge> 8 <Badge variant="destructive"> 9 <CircleAlert className="w-3 h-3" /> 10 Failed 11 </Badge> 12 <Badge variant="secondary"> 13 <Clock className="w-3 h-3" /> 14 Pending 15 </Badge>
Status badge in a data table cell
Data tables that list orders, deployments, users, or invoices almost always want a status column with color-coded pills. Put a Badge in the cell renderer with the variant mapped to the status value — success for healthy or completed, destructive for failed or error, outline for neutral draft states, secondary for pending or in-progress.
The data table component's cell renderer receives the row data, so the mapping logic lives in a small helper object that returns the correct variant for each status string. Because the Badge uses text-xs by default, the row height stays aligned with surrounding cells without any vertical-alignment tweak. The pattern scales identically whether the table has ten rows or ten thousand — one component, one mapping, one pill per cell.
1 type Status = 'active' | 'pending' | 'failed' | 'archived' 2 3 const variantFor: Record<Status, 'success' | 'secondary' | 'destructive' | 'outline'> = { 4 active: 'success', 5 pending: 'secondary', 6 failed: 'destructive', 7 archived: 'outline', 8 } 9 10 // In your column definition 11 { 12 accessorKey: 'status', 13 header: 'Status', 14 cell: ({ row }) => { 15 const status = row.original.status as Status 16 return <Badge variant={variantFor[status]}>{status}</Badge> 17 }, 18 }
Render badges from a server field
In real applications, badge content usually comes from a database row or API response — a user's plan tier, a feature's release state, the current deployment ring. Define a small mapping object so the label-and-variant transformation lives in one place, then pass the server value through it at render time.
For lists of many categorical tags — blog post categories, product filters, user skills — pair the outline variant with the same mapping pattern to render each tag consistently. If the list has an overflow cap, slice the array, render visible badges, and show a +N counter for the remainder using the same Badge component with a subdued variant. Because the mapping is a plain object, adding a new tier or tag is a one-line edit, and TypeScript's inference keeps the variant prop type aligned automatically.
1 const tiers = { 2 free: { label: 'Free', variant: 'outline' as const }, 3 pro: { label: 'Pro', variant: 'default' as const }, 4 enterprise: { label: 'Enterprise', variant: 'success' as const }, 5 } 6 7 export function UserPlanBadge({ 8 plan, 9 }: { plan: keyof typeof tiers }) { 10 const { label, variant } = tiers[plan] 11 return <Badge variant={variant}>{label}</Badge> 12 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
Five: default, secondary, success, outline, and destructive. Each variant maps to a Tailwind class string in the component's internal styles.variants object and references a CSS design token (primary, secondary, success, border, destructive). Because the colors come from tokens, switching your app's theme updates every badge instantly without touching individual call sites. Pass the variant name as a prop — <Badge variant="success"> — and TypeScript autocompletes the options.
Drop the icon component as a child next to your label text. The Badge base class is inline-flex items-center gap-1.5, so icons and text align and space automatically — no wrapper div, no manual gap class at the call site. Size the icon to w-3 h-3 (12 px) to match the text-xs label, and the pair fits balanced inside the badge's px-2.5 py-0.5 padding. Any icon library works — Lucide, Heroicons, or inline SVGs.
Not with a built-in asChild prop — Drivn's Badge always renders as a <span>. To make it clickable, wrap it in a Next.js <Link>, a native <button>, or the Drivn Button component. The span inherits click events naturally when nested. If you do this often enough that the wrapping feels repetitive, open src/components/ui/badge.tsx after install and change the span to an <a> tag or add a conditional render based on an href prop you introduce yourself.
Open src/components/ui/badge.tsx and add a new key to the styles.variants object — for example "info": "bg-info/15 text-info border border-info/20". The variant prop type is derived via keyof typeof styles.variants, so TypeScript autocompletes your new variant at every call site immediately. If you want the new color to also be a design token, add a matching --color-info variable in src/styles/globals.scss so dark and light themes both resolve it correctly.
Wrap both the button and the badge in a relative inline-flex container. Absolutely position the badge to the top-right corner with absolute -top-1 -right-1 and trim the padding with className="px-1.5 py-0" so single-digit counts fit cleanly. Use the destructive variant for unread counts — the red accent is a conventional attention-needed signal that most users parse without reading. For counts over 99, render 99+ as the badge child instead of the raw number.