Skip to content
Drivn
5 min read

Drivn vs shadcn/ui — Badge Component Compared

Side-by-side comparison of Drivn and shadcn/ui React Badge components — runtime deps, variant count, icon support, cva dependency, and status color tokens.

Both Drivn and shadcn/ui hand you the Badge as source code rather than a package import, so the decision is not about lock-in — it is about what the thirty lines you inherit actually contain. Badges carry short status labels: a "Beta" pill in a nav, a plan tier in a pricing card, an unread count in a notification tray, a green "Active" cell in a subscriptions table. The surface is small, which makes the implementation differences unusually easy to inspect end to end.

shadcn/ui builds its Badge on class-variance-authority and clsx, wraps content in a Radix Slot so asChild can swap the rendered tag, and defines four variants: default, secondary, destructive, and outline. The file is yours after install, but cva, clsx, and @radix-ui/react-slot stay in package.json for as long as the project lives.

Drivn's Badge carries no runtime UI dependencies. It renders one <span> whose base class is inline-flex items-center gap-1.5 px-2.5 py-0.5 text-xs font-semibold rounded-full, so an icon dropped beside the label spaces and centers itself. Five variants ship — the four shadcn names plus success — and each maps to a design token instead of a hardcoded color.

The sections below put both APIs, variant tables, icon handling, and customization paths side by side, using code taken from the real source on each side.

Side-by-side comparison

FeatureDrivnshadcn/ui
Runtime UI dependenciesNone (React + Tailwind)cva + clsx + Radix Slot
Variant count5 (default, secondary, success, outline, destructive)4 (default, secondary, destructive, outline)
Success variant built in
Container element<span>Slot (span by default)
Icon spacingBuilt-in (gap-1.5)Manual gap per call site
Variant APIconst styles + keyof typeofclass-variance-authority (cva)
asChild / polymorphic
Estimated bundle≈0.6 kB≈2 kB (with cva + Slot)
LicenseMITMIT
Copy-paste install

API side-by-side

shadcn/ui's Badge is a single named export paired with a badgeVariants helper powered by class-variance-authority. You import Badge (and optionally badgeVariants when other components want the same class set), pass a variant prop, and the component applies a cva-generated className. Drivn's Badge is also a single export — just Badge — but the variants live in a plain object literal and the component body is a one-line render.

Both libraries accept variant, className, and children. Both render inline by default. The difference is what ships alongside — shadcn's cva and clsx runtime versus Drivn's zero-dependency path that relies only on React, Tailwind, and the local cn() utility. Migrating a shadcn codebase to Drivn usually means keeping every call site unchanged and deleting two dependency entries from package.json.

1// shadcn/ui — cva-backed variants
2import { Badge } from '@/components/ui/badge'
3
4<Badge variant="default">New</Badge>
5<Badge variant="destructive">Failed</Badge>
6
7// Drivn — plain styles object, same surface
8import { Badge } from '@/components/ui/badge'
9
10<Badge variant="default">New</Badge>
11<Badge variant="destructive">Failed</Badge>

Five variants vs four — the success slot

shadcn/ui's Badge defines four variants: default, secondary, destructive, and outline. A success state — the green pill that signals plan active, invite accepted, or deployment healthy — is a near-universal UI need, but shadcn leaves you to add it as a cva entry, which means editing badgeVariants and threading the new key through TypeScript.

Drivn ships success as a first-class variant. The Badge component's styles.variants.success references the --color-success design token, so the success badge matches your app's global success color without any hardcoded hex. The other four variants (default, secondary, outline, destructive) line up one-to-one with shadcn, so mental migration is direct: variant="destructive" in shadcn becomes variant="destructive" in Drivn.

1const styles = {
2 base: cn(
3 'inline-flex items-center gap-1.5 px-2.5 py-0.5',
4 'text-xs font-semibold rounded-full'
5 ),
6 variants: {
7 default: 'bg-primary/15 text-primary border border-primary/20',
8 secondary: 'bg-secondary/15 text-secondary border border-secondary/30',
9 success: 'bg-success/15 text-success border border-success/20',
10 outline: 'border border-border text-muted-foreground',
11 destructive: 'bg-destructive/15 text-destructive border border-destructive/20',
12 },
13}

Icon support and inline layout

A badge rarely appears alone — it pairs with a glyph: a check for active, a triangle for failed, a star for premium. shadcn/ui's Badge passes children straight through to its container with no built-in gap, so an icon placed next to text sits flush against it. Every call site compensates on its own, with a wrapper element or a gap-1 utility, and those one-off fragments drift apart as the codebase grows.

Drivn puts inline-flex items-center gap-1.5 directly in the Badge base class, alongside px-2.5 py-0.5 text-xs font-semibold rounded-full. That single declaration covers horizontal spacing and vertical centering for every variant, so a lucide-react icon becomes a plain child — no wrapper, no per-instance class. Size the icon to w-3 h-3 so it reads correctly against the text-xs label. In a table with forty status cells, that is forty places you never think about alignment, and forty places that stay identical when the badge padding changes later.

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

Customization and design tokens

Both libraries put the Badge file in your project, so both invite the same category of edits. The shape of those edits differs. shadcn uses cva — to add a variant you extend badgeVariants with another key under variants.variant, and the TypeScript union updates through cva's generic inference.

Drivn uses a plain record: styles.variants is a string-keyed object, the variant prop type is keyof typeof styles.variants, and adding a variant is a one-line addition to the object. TypeScript picks it up immediately. No cva call, no generic dance, no extra import.

For design tokens, Drivn's variant strings reference CSS custom properties — bg-primary/15, text-success, border-destructive/20 — so a rebrand flows through the theming layer without touching any call sites. Changing --color-success in globals.css updates every success badge in the app at once. The installation page covers how tokens are wired.

What Drivn does not have

Drivn's Badge does not expose an asChild prop or a Slot primitive. shadcn's Badge (in current releases) wraps its content in @radix-ui/react-slot, so asChild lets the Badge inherit any child element's tag — convenient when you want the badge to render as a link (<a>) while keeping the badge styling. Drivn's Badge is always a <span>. To make a clickable badge, wrap it in a <Link>, a button, or a native anchor at the call site.

The badgeVariants helper shadcn exports is also missing from Drivn. In shadcn projects you sometimes apply badge styling to non-badge elements by calling badgeVariants() for the className string. Drivn's styles object is internal to the component file. If you want that class set elsewhere, either export it explicitly or duplicate the string.

Outside those two gaps, Drivn's Badge is a functional superset of shadcn's: one extra variant, automatic icon spacing, fewer dependencies, and a shorter source file to read end-to-end.

Get started

Install Drivn in one command

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

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

Migrate if you want to drop cva and clsx from your dependency tree, need a built-in success variant, or prefer the automatic icon spacing. Stay on shadcn if you rely on asChild to render badges as links or buttons, reuse badgeVariants() on non-badge elements, or have already standardized your team's component library around cva-driven variants. For new projects, Drivn's zero-dependency approach ships fewer bytes and fewer moving parts, while the API maps one-to-one on every shared variant name.

Five: default, secondary, success, outline, and destructive. Each maps to a Tailwind class string in the styles.variants object and references a CSS design token — --color-primary, --color-secondary, --color-success, --color-border, and --color-destructive — so badge colors respect your theme without hardcoded hex values. The success variant is the notable addition versus shadcn/ui, which ships four variants and expects you to add success manually via class-variance-authority.

Yes. The Badge base class includes inline-flex items-center gap-1.5, so dropping an icon beside text applies consistent spacing and vertical centering automatically — no flex container, no manual gap utility at the call site. Size the icon to w-3 h-3 (12 px) so it matches the text-xs badge label. Every variant inherits the same gap, so icon layout stays uniform regardless of color.

shadcn's design centers on a shared variant system across every component — button, badge, toggle, and others all use cva to declare variants, defaults, and compound variant rules with the same pattern, which is consistent across the library. Drivn's design prioritizes zero runtime UI dependencies and a smaller mental model — a plain styles.variants object indexed by keyof typeof covers the same need with native TypeScript inference and no external package. For simple components like Badge the tradeoff is bundle weight versus shared-pattern uniformity.