Skip to content
Drivn
5 min read

How to Add a Badge to a React App

Step-by-step guide to adding a copy-and-own Badge to any React app with the Drivn CLI β€” five variants, icon support, and zero runtime dependencies.

A badge starts life in most codebases as an inline <span> with seven Tailwind classes copied from the nearest file that already had one. Then a second status appears and the classes fork: green for active, red for failed, gray for drafts. Within a month the same pill exists in five files with five slightly different paddings, and changing the corner radius means a project-wide search. None of that is a hard problem β€” it is a component that was never extracted.

Drivn's Badge is that extraction, done once and owned by you. The CLI writes a single .tsx file of about thirty lines into your project: a styles object holding the pill shape and five color variants, a three-prop interface, and a <span> that merges everything through cn. There is no runtime package, no Radix primitive, and no 'use client' directive β€” it renders anywhere React renders, including server components and static pages.

This guide walks through adding the Badge to a React app you already have: confirm the prerequisites, install with the CLI, render the five variants, put icons and counts inside, then extend the styles object with a variant of your own. The same steps work in Vite, Next.js, Remix, or any React setup with Tailwind. App Router–specific guidance lives in the Next.js Badge guide, and the Badge examples page shows finished patterns to copy.

Prerequisites

The Badge assumes three things about your project. Tailwind CSS v4 must be processing your styles, because every visual decision in the component is a Tailwind class. TypeScript must be configured β€” the file ships as .tsx and the variant prop type is derived with keyof typeof, so TypeScript is a requirement of the stack, not a preference. And the @/ import alias must resolve to your source directory, since the component imports the cn helper from @/utils/cn. Projects created with create-next-app, npm create vite, or npx drivn@latest create satisfy all three out of the box; for a hand-rolled setup, the installation page lists the minimal tsconfig.json and token config. The five variants read the primary, secondary, success, and destructive color tokens, so confirm your theme defines them.

Step 1 β€” Install the Badge via the CLI

From your project root, run the CLI and let it write the component. It asks once where components live β€” src/components/ui/ by default β€” and drops badge.tsx there. Your package.json does not change: the Badge imports React and the local cn helper and nothing else, not even an icon package. That makes it one of the safest first components to adopt Drivn with β€” roughly thirty lines you can read top to bottom in a minute. The CLI docs cover flags for custom directories and adding several components in one command. After install the file is yours: upgrades never rewrite it, so commit the pristine version before customizing to keep a clean baseline.

1# from your React project root
2npx drivn add badge

Step 2 β€” Render the five variants

Import Badge from your UI directory and pass one of five variant values: default, secondary, success, outline, or destructive. Each maps to a Tailwind string in the component's styles.variants object, and four of the five share one recipe β€” the token color at 15% opacity behind full-strength text, with a soft border of the same hue β€” while outline drops the tinted background for a quiet, metadata-style label. The prop is optional and falls back to default. Because the type is keyof typeof styles.variants, your editor autocompletes the five names and the compiler rejects a typo before anything renders. The full prop table lives in the Badge docs.

1import { Badge } from '@/components/ui/badge'
2
3<Badge variant="default">Default</Badge>
4<Badge variant="secondary">Secondary</Badge>
5<Badge variant="success">Success</Badge>
6<Badge variant="outline">Outline</Badge>
7<Badge variant="destructive">Destructive</Badge>

Step 3 β€” Put icons and counts inside

The children prop takes any React node, and the base styles already include inline-flex items-center gap-1.5, so an icon and a label align and space themselves with no extra classes at the call site. Render a lucide-react icon at around w-3 h-3 so it sits proportionally inside the text-xs pill, and keep the icon decorative β€” the text carries the meaning for screen readers. Counts need nothing special: a number is a valid child, and secondary is the conventional variant for neutral tallies like unread messages. For data-driven status columns, map each status string to a variant name in a small as const record and index into it at render time β€” the derived prop type checks every entry.

1import { Badge } from '@/components/ui/badge'
2import { Check, AlertTriangle } from 'lucide-react'
3
4<Badge variant="success">
5 <Check className="w-3 h-3" />
6 Deployed
7</Badge>
8<Badge variant="destructive">
9 <AlertTriangle className="w-3 h-3" />
10 Build failing
11</Badge>
12<Badge variant="secondary">12 unread</Badge>

Step 4 β€” Add a variant of your own

Everything visual lives in the styles object at the top of badge.tsx, so extending the Badge is a string edit in a file you own. The base entry holds the pill shape β€” inline flex, px-2.5 py-0.5 padding, rounded-full β€” and variants holds the five color recipes. To add a warning variant, insert one more key following the same opacity pattern with your warning token; the variant prop type updates itself because it is derived from the object, and every call site autocompletes the new name immediately. To reshape all badges at once β€” square corners, larger text β€” edit base. A rebrand through the theming page recolors every variant without touching this file, since the recipes read tokens, not hex values.

1// src/components/ui/badge.tsx β€” the styles object
2const styles = {
3 base: cn(
4 'inline-flex items-center gap-1.5 px-2.5 py-0.5',
5 'text-xs font-semibold rounded-full'
6 ),
7 variants: {
8 default: 'bg-primary/15 text-primary-light border border-primary/20',
9 secondary: 'bg-secondary/15 text-secondary border border-secondary/30',
10 success: 'bg-success/15 text-success border border-success/20',
11 outline: 'border border-border text-muted-foreground',
12 destructive: 'bg-destructive/15 text-destructive border border-destructive/20',
13 },
14}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

Follow Drivn updates
New components, improvements, and guides every release.
Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star β†’

Frequently asked questions

Yes. Nothing in the component is framework-specific β€” no router import, no Next.js API, no server feature. Any React project with TypeScript, Tailwind CSS v4, and a resolving @/ path alias can run npx drivn add badge and use the identical file. The install steps above are the same regardless of bundler or framework.

Not directly β€” the Badge renders a <span> and its interface exposes only variant, className, and children, so it is presentational by design. Wrap it in a <button> or a link when you need click behavior, which also keeps the semantics right for keyboards and screen readers. Since you own the source, converting it to render a button element permanently is also a small, type-checked edit.

Compose it at the call site: wrap the icon in a relative container and pass positioning classes to the Badge through className, such as absolute -top-2 -right-2. Because the component merges your classes last through cn, call-site positioning always wins without editing the source. Use the secondary or destructive variant depending on whether the count is informational or urgent.

The component ships as a .tsx file and its variant prop is typed as keyof typeof styles.variants β€” the type is derived from the styles object itself, which is what makes added variants autocomplete instantly. That derivation only exists in TypeScript, so the Drivn registry treats TypeScript as a hard prerequisite across every component, not an optional layer.