Skip to content
Drivn logoDrivn
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.

Drivn's Badge and shadcn/ui's Badge both ship as editable TypeScript source rather than an npm module. They solve the same UI problem — short status labels like "New", "Beta", notification counts, plan tiers, and tags — but the implementations diverge on variant count, icon affordance, and which runtime dependencies ride along with the copy-paste install.

shadcn/ui's Badge uses class-variance-authority plus clsx for variant plumbing, wraps its content in a Radix Slot primitive for polymorphic composition, and ships four variants: default, secondary, destructive, and outline. The component source lives in your repo after install, but every shadcn project also ships cva and clsx as permanent runtime dependencies.

Drivn's Badge is roughly thirty lines of React plus Tailwind with zero runtime UI dependencies. It ships five variants out of the box — default, secondary, success, outline, destructive — including a success variant that shadcn leaves for you to add. The container is a single <span> with inline-flex items-center gap-1.5, so passing a Lucide icon as a child renders correctly alongside text without any prop surgery or sub-component.

This comparison walks through every difference with real code from both sides: API shape, variant count, icon support, customization surface, and accessibility defaults. You pick on engineering tradeoffs, not homepage claims.

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-light 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

Badges often pair a small icon with a single word: a checkmark for active, a warning triangle for errors, a star for premium. shadcn/ui's Badge renders children without built-in spacing — dropping an icon next to text produces zero gap, so every usage site either wraps children in a flex container or adds a gap-1 utility manually.

Drivn's Badge uses inline-flex items-center gap-1.5 on its base container. Drop a lucide-react icon beside text and the gap applies automatically, with vertical centering and consistent spacing across every variant. No wrapper, no className gymnastics, no per-call-site gap class. Across a codebase with forty badge instances — status cells in tables, tier pills in pricing cards, counters in a notification tray — the built-in gap eliminates forty repeated fragments.

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.scss 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.