Skip to content
Drivn
5 min read

Drivn vs shadcn/ui — Button Component Compared

Side-by-side comparison of the Drivn and shadcn/ui React Button components — runtime deps, bundle size, API style, variants, and accessibility.

Choosing a Button component sounds trivial until you count what rides along with it. Both Drivn and shadcn/ui hand you editable source instead of an opaque npm import, so the code lands in your repo and you own every line. The split shows up underneath the JSX: shadcn/ui's Button leans on Radix's Slot primitive for asChild, class-variance-authority for its variant map, and clsx for class merging. Drivn's Button skips all three — it is React and Tailwind, with every variant living in a plain const styles object typed by keyof typeof.

That single design decision ripples outward. Drivn ships fewer runtime modules for your bundler to resolve, a smaller per-component footprint, and a variant API with one less layer of indirection. shadcn/ui trades that leanness for a far larger ecosystem — pre-built blocks, admin dashboards, community themes, and asChild polymorphism that Drivn does not attempt.

This page compares the two on the dimensions that actually change what you ship: import and variant API, bundle footprint, the customization path, the accessibility model, and the real cost of migrating button call sites from one to the other. Every claim is backed by code on both sides so the decision rests on your stack, not on a GitHub star count or a longer sponsor list on the homepage.

Side-by-side comparison

FeatureDrivnshadcn/ui
Runtime UI dependenciesNone (React + Tailwind)@radix-ui/react-slot, cva, clsx
Variant APIconst styles + keyof typeofclass-variance-authority (cva)
Icon propleftIcon / rightIcon (component)children (JSX element)
Loading prop
Polymorphic (asChild)
AccessibilityNative button elementRadix Slot + native
LicenseMITMIT
Copy-paste install
TypeScriptTyped via keyof stylesTyped via VariantProps<typeof>
Next.js App RouterWorks out of the boxWorks out of the box

API side-by-side

The two buttons converge on the same usage site: <Button variant="secondary">Click</Button>. Underneath, shadcn's Button uses cva to define variants and asChild to swap the rendered element via Slot. Drivn defines variants as a plain object indexed by variant and size, and renders a native <button> (see the Button docs).

For most buttons this is indistinguishable. The divergence shows up when you want to pass an icon or a loading state — Drivn accepts leftIcon={Plus} and loading={true} as first-class props, whereas shadcn expects you to embed icons and loading spinners as children.

1// shadcn/ui
2<Button variant="secondary">
3 <Plus className="mr-2 h-4 w-4" /> New item
4</Button>
5
6// Drivn
7<Button variant="secondary" leftIcon={Plus}>
8 New item
9</Button>

Bundle size and runtime deps

Add up shadcn/ui's Button imports and you get @radix-ui/react-slot (~1.2 kB gzipped), class-variance-authority (~0.8 kB), and clsx (~0.3 kB) before a single button renders. Drivn's Button imports React and your local cn() helper — nothing else. On a screen with one button the gap is noise; on a real page wired up with tabs, a dialog, and a handful of other primitives, the same skipped dependencies compound because every Drivn component avoids them.

Neither approach changes what sits in your node_modules — both copy source into your repo. What differs is the module graph Next.js walks when it tree-shakes the route. Drivn pulls fewer modules into the final bundle because it imports fewer modules in the first place, and there is no shared primitive that quietly reappears in every chunk.

Customization

shadcn recommends extending variants via cva's cn() merge or by editing the buttonVariants function. Drivn recommends editing the styles object directly — add a key to variants, add a key to sizes, or change a class. Because the types read from keyof typeof styles, autocomplete updates on save.

For teams that prefer the cva pattern, shadcn will feel familiar. For teams that want fewer layers between "render a button" and the final markup, Drivn removes one level of indirection. Neither approach is objectively better — the question is which mental model matches your team.

What Drivn does not have

Drivn is younger and smaller than shadcn/ui. The ecosystem around shadcn — blocks, examples, community themes, third-party integrations — is larger by orders of magnitude. If you need a ready-made admin dashboard or marketing template built on your component library today, shadcn has more starting points.

Drivn also does not support asChild / polymorphic rendering. If your design system relies heavily on rendering the same props as different underlying elements (e.g., a button that is really a link), shadcn's Radix Slot pattern is more ergonomic. In Drivn you render a link and style it like a button, which some teams prefer for clarity.

Migrating a Button from shadcn to Drivn

Rename <Button asChild><Link /></Button> to <Link className={buttonClasses}>. Replace icon children with leftIcon / rightIcon props. Remove cva imports; Drivn does not need them. The surrounding <form>, <Dialog>, or <Tabs> markup stays the same — only the Button changes. See the installation guide for the CLI command.

For teams that use shadcn's buttonVariants() helper to apply button styles to other elements (typically <Link>), the Drivn equivalent is to extract the styles object from the Button source and re-use it via cn(). Since the Button source now lives in your repo, the extraction is a one-time refactor. Most migrations touch 20-50 button call sites and take under an hour per app. Test deep-interaction flows (form submits, confirm dialogs) carefully — the loading prop replacement saves more code than it breaks.

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

Only if you have concrete reasons — smaller bundle, preference for the dot notation API, or wanting to drop Radix from your dependency graph. If shadcn works well for your team and you are using its asChild pattern heavily, staying put is often the right call. Drivn is a better fit for teams starting fresh or replacing a handful of components at a time.

Yes. Drivn's Button renders a real <button> element with correct type, disabled, and aria-busy handling. Keyboard focus, native Enter/Space activation, and form submission work because the browser implements them on native elements. Drivn does not use Radix for accessibility — it relies on HTML semantics and adds ARIA only where needed.

Technically yes — both install into src/components/ui/ and both are copy-paste. You can gradually replace one with the other. In practice, maintaining two parallel component libraries creates style inconsistencies and doubles your bundle. Pick one and stick with it for the component families you care about.

Yes. The Button, like most Drivn primitives, works as a Server Component import unless it uses state or browser APIs. Components that need hooks are marked with "use client" at the top of the file — you can see which is which by opening the source in your repo. No wrapper or boundary configuration is needed.

The large shadcn ecosystem: third-party blocks, templates, community themes, and pre-built integrations. Drivn's library is smaller and newer. You also lose asChild polymorphism, which some teams use heavily for rendering links and menu items. For a greenfield project or a small surface area, the trade is usually acceptable.