Drivn vs shadcn/ui — Breadcrumb Compared
Side-by-side comparison of Drivn and shadcn/ui React Breadcrumb — API surface, runtime deps, auto-separator injection, and accessibility defaults.
Breadcrumbs are one of the quietest components in a design system: a horizontal row of links that tells a user where they are in a site hierarchy. The interaction surface is small, but the API design exposes a lot about how a component library handles composition. Drivn's Breadcrumb and shadcn/ui's Breadcrumb both ship as editable TypeScript source you copy into your repo, yet they take opposite approaches to how the pieces fit together.
shadcn/ui exposes seven separate named exports — Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, and BreadcrumbEllipsis. You import each piece, nest them explicitly, and place a <BreadcrumbSeparator /> between every item manually. The BreadcrumbLink uses @radix-ui/react-slot for its asChild prop, so rendering the link as a Next.js <Link> stays a one-prop change.
Drivn ships a single root with dot notation: Breadcrumb, Breadcrumb.Item, Breadcrumb.Page, Breadcrumb.Separator, Breadcrumb.Ellipsis. Separators are injected automatically between siblings at render time, so you only type the items. Zero runtime UI dependencies — no Radix Slot, no cva, just React, Tailwind, and two Lucide icons for the default separator and ellipsis.
This page walks through every difference with real code on both sides: import shape, separator handling, runtime deps, accessibility defaults, and what Drivn does not have.
Side-by-side comparison
| Feature | Drivn | shadcn/ui |
|---|---|---|
| Runtime UI dependencies | None (React + Tailwind) | @radix-ui/react-slot |
| API style | Dot notation (1 import) | 7 named exports |
| Separator between items | Auto-injected | Manual per position |
| Custom separator | separator prop on root | Per-child override |
| asChild / polymorphic link | ||
| Container element | nav > ol > li | nav > ol > li |
| aria-current on current page | ||
| Ellipsis component | ||
| Typical LOC for 3-level breadcrumb | ~5 | ~12 |
| License | MIT | MIT |
Import shape: seven exports vs dot notation
shadcn/ui's Breadcrumb is a tree of co-dependent exports. A typical usage pulls Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, and BreadcrumbSeparator from the same file, then nests them in a specific order. The import statement alone runs past eighty characters, and refactoring a route means editing six places that all reference the same component family.
Drivn's Breadcrumb uses one named import. Every sub-component hangs off the root via Object.assign, so TypeScript autocompletes Breadcrumb.Item, Breadcrumb.Page, and Breadcrumb.Separator after you type the dot. The same hierarchy is visible in the JSX, but the import and the parent-child relationship live in a single place. Smaller diffs on refactor, less import churn, and no risk of importing BreadcrumbLink from the wrong file in a monorepo with multiple component libraries.
1 // shadcn/ui — seven exports for one component family 2 import { 3 Breadcrumb, 4 BreadcrumbList, 5 BreadcrumbItem, 6 BreadcrumbLink, 7 BreadcrumbPage, 8 BreadcrumbSeparator, 9 } from '@/components/ui/breadcrumb' 10 11 <Breadcrumb> 12 <BreadcrumbList> 13 <BreadcrumbItem> 14 <BreadcrumbLink href="/">Home</BreadcrumbLink> 15 </BreadcrumbItem> 16 <BreadcrumbSeparator /> 17 <BreadcrumbItem> 18 <BreadcrumbPage>Docs</BreadcrumbPage> 19 </BreadcrumbItem> 20 </BreadcrumbList> 21 </Breadcrumb> 22 23 // Drivn — one import, dot notation 24 import { Breadcrumb } from '@/components/ui/breadcrumb' 25 26 <Breadcrumb> 27 <Breadcrumb.Item href="/">Home</Breadcrumb.Item> 28 <Breadcrumb.Page>Docs</Breadcrumb.Page> 29 </Breadcrumb>
Automatic separator injection
In shadcn/ui, every separator is typed manually. After every BreadcrumbItem except the last, you place a <BreadcrumbSeparator />. A three-level breadcrumb needs two separator tags; a five-level breadcrumb needs four. The separator is part of the markup, and the JSX tree grows linearly with depth.
Drivn's Breadcrumb injects separators at render time. The root uses React.Children.toArray(children).flatMap() to iterate over children and inserts a Breadcrumb.Separator before every item past the first. You write only the items. A three-level breadcrumb has three JSX children, not five; a five-level breadcrumb has five, not nine. The JSX stays focused on the trail itself, while separator strategy is handled once in the root component and never repeated at call sites.
1 // Drivn — write items only, separators auto-inject 2 <Breadcrumb> 3 <Breadcrumb.Item href="/">Home</Breadcrumb.Item> 4 <Breadcrumb.Item href="/docs">Docs</Breadcrumb.Item> 5 <Breadcrumb.Item href="/docs/components"> 6 Components 7 </Breadcrumb.Item> 8 <Breadcrumb.Page>Breadcrumb</Breadcrumb.Page> 9 </Breadcrumb>
Runtime dependencies and bundle footprint
shadcn/ui's Breadcrumb pulls @radix-ui/react-slot for the asChild prop on BreadcrumbLink. Slot is a small primitive but it is still a runtime dependency your bundler resolves and your package.json records. Across a full shadcn install that uses many components with asChild, Slot becomes one of the most frequently-imported pieces.
Drivn's Breadcrumb has zero runtime UI dependencies. The source imports React, the local cn() utility, and two Lucide icons — ChevronRight for the default separator and MoreHorizontal for the ellipsis. The total component is around eighty lines. If you need to render the link as a Next.js <Link>, you edit the local Item function in src/components/ui/breadcrumb.tsx after install and swap the <a> for a <Link>. For most breadcrumb patterns — static site nav, docs trees, settings pages — that is a one-line edit you make once.
Custom separators
Both libraries ship a ChevronRight as the default separator. Replacing it is where the APIs diverge. shadcn/ui's BreadcrumbSeparator renders whatever children you pass — a chevron, a slash, a dot — but because every separator in the tree is a separate JSX node, you pass the same child into each one or wrap the separator in your own component that always injects the same glyph.
Drivn's Breadcrumb root accepts a separator prop. Pass <span>/</span> once on the root and every auto-injected separator uses the slash. The prop is typed as React.ReactNode, so any JSX works — an icon from a different set, a styled character, a branded element. Changing the trail's glyph across a whole app is a single prop override at the root layout.
1 // One prop controls every separator in the list 2 <Breadcrumb separator={<span>/</span>}> 3 <Breadcrumb.Item href="/">Home</Breadcrumb.Item> 4 <Breadcrumb.Item href="/settings">Settings</Breadcrumb.Item> 5 <Breadcrumb.Page>Profile</Breadcrumb.Page> 6 </Breadcrumb>
Accessibility defaults
Both libraries get the core accessibility right. The root renders as a <nav> with aria-label="Breadcrumb", the list uses <ol> for ordered semantics, each link sits inside an <li>, and the current page has aria-current="page" so screen readers announce it distinctly from the links.
Drivn's Breadcrumb also marks separators as aria-hidden="true" with role="presentation" so assistive tech ignores the visual dividers. The ellipsis component wraps its icon with an <span className="sr-only">More pages</span> so users navigating with a screen reader hear a meaningful label instead of silence. shadcn/ui's Breadcrumb handles the same patterns, and the two implementations pass the same axe and Lighthouse checks. The accessibility story is a wash — both are correct by default. The difference lives elsewhere, in composition surface and runtime footprint.
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
Migrate if you want to drop @radix-ui/react-slot from your dependency tree, prefer a single dot-notation import over seven separate named exports, or like the automatic separator injection that removes redundant JSX. Stay on shadcn if you rely on the asChild prop to polymorphically render BreadcrumbLink as a framework router link, or your team has standardized on shadcn's multi-export pattern across every component. Both libraries use the same semantic HTML and accessibility defaults, so migrated call sites read nearly identically.
The root component calls React.Children.toArray(children).flatMap() over its children and, for every child past the first, prepends a Breadcrumb.Separator element before it. The separator passes the root-level separator prop as its children, so the glyph is consistent across the whole list. You write only the items and the current page inside <Breadcrumb>; a three-level breadcrumb is three JSX children instead of the five shadcn needs with manual separator placement.
Yes, but the cleanest path is to edit the local component rather than wrap. After running drivn add breadcrumb, open src/components/ui/breadcrumb.tsx and change the <a> tag inside the Item function to a <Link> from next/link. The href prop and the spread {...props} transfer cleanly because LinkProps extends the same anchor attribute types. Every breadcrumb in the app then uses client-side routing with no call-site changes.
Pass a separator prop to the root component. The prop is typed as React.ReactNode, so you can pass a slash (<span>/</span>), an icon from any library (<ArrowRight className="w-3 h-3" />), or a styled element with custom tokens. The root hands the node to every auto-injected Breadcrumb.Separator at render time, so one override applies everywhere. This differs from shadcn/ui, which requires you to either pass children into every BreadcrumbSeparator or wrap it in a custom component.
Yes. The root renders as <nav aria-label="Breadcrumb"> with an <ol> list for ordered semantics. Each item is an <li> with an <a> child; the current page renders as <li aria-current="page"> with a non-interactive <span> so assistive tech announces it as the location. The auto-injected separators have role="presentation" and aria-hidden="true", and the Breadcrumb.Ellipsis includes a visually hidden "More pages" label for screen readers. The accessibility profile matches shadcn/ui's implementation.