Drivn vs shadcn/ui — Table Compared
A data Table is one of the few shadcn components with no Radix under it — so this comparison is about API shape, built-in variants, and how the source reads.
A data Table is one of the few components where shadcn/ui does not reach for Radix — both Drivn and shadcn render plain semantic <table> elements with no headless primitive underneath. That makes this comparison less about bundle size and more about API ergonomics: how you import the pieces, how you switch visual styles, and how the source reads when you open it. Drivn ships a single file at packages/drivn/src/registry/components/table.ts that exports one Table object with seven sub-components attached via Object.assign — Table.Header, Table.Body, Table.Row, Table.Head, Table.Cell, Table.Footer, and Table.Caption.
shadcn/ui splits the same surface into seven separately named exports — Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableCaption — each its own component with inline cn() calls. You import all seven by name and compose them flat. Neither approach pulls a runtime UI dependency; both lean only on a cn() class-merge helper, and both render server-side with no 'use client' directive.
The practical differences are the dot-notation single import, Drivn's built-in variant prop for striped and bordered styles, and the const styles object that keeps every class in one place. This page walks each difference with verbatim source, then covers where an existing shadcn project should stay put.
Side-by-side comparison
| Feature | Drivn | shadcn/ui |
|---|---|---|
| Install command | npx drivn add table | npx shadcn@latest add table |
| Runtime UI dependencies | cn() utility only — zero npm UI packages | cn() utility only — Table is not Radix-backed |
| API shape | Dot notation — Table.Header, Table.Cell | Named exports — TableHeader, TableCell |
| Import statement | One name: import { Table } | Up to seven names imported flat |
| Built-in variants | default / striped / bordered via variant prop | None — add classes manually |
| Striped rows | variant="striped" | Manual [&_tr:nth-child(even)] classes |
| Cell alignment | align prop → CSS attribute selector | className="text-center" per cell |
| Styling pattern | Single const styles object | Inline cn() per sub-component |
| Client component | No — renders pure server-safe markup | No — renders pure server-safe markup |
| License | MIT | MIT |
One import, dot notation — Table.Header not TableHeader
Drivn attaches every table part to the root via Object.assign(TableRoot, { Caption, Header, Body, Footer, Row, Head, Cell }), so a single import { Table } from "@/components/ui/table" gives you the whole set. You write Table.Header, Table.Row, Table.Head, and Table.Cell — the structure reads like the HTML it renders, and your import block stays one line no matter how complex the table grows.
shadcn/ui exports each part separately, so the same table needs import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@/components/ui/table" — six or seven names to keep in sync as you add a caption or footer. The flat naming is a matter of taste, but the dot notation means one import line and a component tree that mirrors <thead> / <tbody> / <tr> / <th> / <td> without the Table prefix repeated on every tag. See the full API on the Table reference.
1 import { Table } from "@/components/ui/table" 2 3 export default function Page() { 4 return ( 5 <Table> 6 <Table.Header> 7 <Table.Row> 8 <Table.Head>Name</Table.Head> 9 <Table.Head>Role</Table.Head> 10 <Table.Head>Status</Table.Head> 11 </Table.Row> 12 </Table.Header> 13 <Table.Body> 14 <Table.Row> 15 <Table.Cell>Alice</Table.Cell> 16 <Table.Cell>Engineer</Table.Cell> 17 <Table.Cell>Active</Table.Cell> 18 </Table.Row> 19 <Table.Row> 20 <Table.Cell>Bob</Table.Cell> 21 <Table.Cell>Designer</Table.Cell> 22 <Table.Cell>Active</Table.Cell> 23 </Table.Row> 24 </Table.Body> 25 </Table> 26 ) 27 }
Built-in striped and bordered variants
Drivn bakes three visual styles into a variant prop — default, striped, and bordered — typed as keyof typeof styles.variants so your editor autocompletes the three options and rejects typos. Switching a plain table to zebra striping is <Table variant="striped">; adding cell borders is <Table variant="bordered">. The classes that drive each variant live in the styles.variants object, applied to the root <table> and cascading to tbody rows via Tailwind arbitrary selectors.
shadcn/ui ships no variant prop on its table. To stripe rows you add an [&_tr:nth-child(even)] background utility to the TableBody className yourself; to border cells you append border utilities to every TableHead and TableCell. It is the same Tailwind under the hood, but Drivn pre-packages the common cases behind one type-safe prop so you do not re-derive the arbitrary selectors for each table you build.
1 // packages/drivn/src/registry/components/table.ts — verbatim 2 variants: { 3 default: '', 4 striped: '[&_tbody_tr:nth-child(even)]:bg-muted/30', 5 bordered: cn( 6 '[&_th]:border [&_td]:border', 7 '[&_th]:border-border [&_td]:border-border', 8 '[&_tbody_tr:nth-child(even)]:bg-muted/30' 9 ), 10 },
Cell alignment via a native attribute selector
Both Table.Head and Table.Cell accept an align prop, but Drivn wires it through the native HTML align attribute rather than a className. The styles.head and styles.cell entries include [&[align=center]]:text-center and [&[align=right]]:text-right — Tailwind arbitrary attribute selectors that match the rendered align="center" or align="right" on the <th> / <td>. So <Table.Cell align="center">$29</Table.Cell> centres the cell with zero extra classes, and the alignment travels with the semantic attribute.
shadcn/ui leaves alignment to you — you pass className="text-center" on each TableCell you want centred. The rendered output is identical, but Drivn's attribute-driven approach keeps the alignment declarative and out of the className soup, which matters when a pricing or comparison table has a dozen right-aligned number columns. The verbatim head and cell style entries show how the selectors map onto the attribute.
1 // packages/drivn/src/registry/components/table.ts — verbatim 2 head: cn( 3 'px-4 py-2.5 text-left font-semibold', 4 'text-muted-foreground', 5 '[&[align=center]]:text-center', 6 '[&[align=right]]:text-right' 7 ), 8 cell: cn( 9 'px-4 py-2.5 text-foreground', 10 '[&[align=center]]:text-center', 11 '[&[align=right]]:text-right' 12 ),
When an existing shadcn table should stay put
The honest case for shadcn/ui here is momentum and the TanStack Table ecosystem. shadcn's docs pair its table primitives with @tanstack/react-table for sorting, filtering, pagination, and column visibility — a data table recipe that thousands of projects have copied verbatim. If your table is already that TanStack-driven shadcn data table, rewriting it onto Drivn buys you dot notation and a variant prop but none of the headless logic, so the migration is rarely worth it.
The second case is a codebase already standardised on shadcn's flat naming — switching one component to dot notation means two conventions living in the same @/components/ui folder. For a fresh build, though, where you want one import, built-in striped and bordered styles, and a const styles object you can read top to bottom, the Drivn Table is the cleaner start. Pair it with Badge for status pills and Pagination for page controls and you have a full data surface without a single runtime UI dependency.
1 # Drivn — one import, built-in variants 2 npx drivn add table 3 4 # shadcn/ui — seven named exports, no variant prop 5 npx shadcn@latest add table
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
No. The Drivn Table is a single file at packages/drivn/src/registry/components/table.ts that imports React and the local cn() utility from @/utils/cn — nothing else. It renders plain semantic table, thead, tbody, tr, th, and td elements. Notably, shadcn/ui's Table is also dependency-free here — the table is one of the few shadcn components that does not wrap a Radix primitive, so neither library adds a runtime UI package for this component.
Drivn uses dot notation — one import of { Table } gives you Table.Header, Table.Body, Table.Row, Table.Head, Table.Cell, Table.Footer, and Table.Caption attached to the root via Object.assign. shadcn/ui exports each part as a separate name — TableHeader, TableBody, TableRow, and so on — which you import flat. Same rendered markup, but Drivn keeps the import to one line and the tree mirrors the underlying HTML structure.
Yes. Pass variant="striped" on the Table root and the styles.variants.striped entry applies [&_tbody_tr:nth-child(even)]:bg-muted/30, tinting every even body row. There is also variant="bordered" for cell borders plus striping. shadcn/ui has no variant prop, so the same zebra effect there means adding the nth-child arbitrary selector to the TableBody className by hand each time.
Yes. The Table source has no 'use client' directive and no React hooks — it is pure presentational markup, so it renders on the server with zero client JavaScript for the table itself. You only need a client component when you add interactivity like column sorting, and even then only the interactive wrapper needs 'use client', not the Table primitives.
Pass align="right" on the Table.Head and Table.Cell for that column. Drivn forwards it as the native HTML align attribute, and the head and cell style entries include [&[align=right]]:text-right, a Tailwind attribute selector that right-aligns the text. This keeps alignment declarative and tied to the semantic attribute rather than scattered className="text-right" strings on every numeric cell.
Yes. Because the Drivn Table is plain markup with no internal state, you can drive it with @tanstack/react-table the same way shadcn does — call useReactTable for the headless model, then map getHeaderGroups and getRowModel onto Table.Header, Table.Row, Table.Head, and Table.Cell. Drivn gives you the presentation layer and dot-notation API; TanStack supplies the sorting, filtering, and pagination logic on top.

