Drivn vs shadcn/ui — Avatar Component Compared
Side-by-side comparison of Drivn and shadcn/ui React Avatar — API shape, image fallback, runtime dependencies, size variants, and initials rendering.
An avatar renders a profile picture and degrades gracefully when that picture is missing. Drivn and shadcn/ui both hand you the source file rather than a package import, so the comparison is not about bundle size or release cadence — it is about the shape of the code you inherit and maintain.
shadcn/ui builds its Avatar on @radix-ui/react-avatar. Three exports — Avatar, AvatarImage, and AvatarFallback — compose at every call site, and the Radix layer owns image loading state. When the URL 404s or the network drops, Radix swaps the image slot for the fallback slot on its own. That behavior is free, and it costs one runtime dependency.
Drivn's Avatar is roughly forty lines with nothing underneath it but React and Tailwind. One component, four props: src, alt, fallback, and size. The fallback is a conditional render — when src is truthy an <img> fills the container with object-cover; when it is not, a <span> prints fallback?.slice(0, 2).toUpperCase() over a bg-muted circle. Four named sizes live in the styles.sizes object; shadcn ships none and expects a className.
What follows are the differences that actually change your code: API surface, what happens on a broken image, size variants, fallback content, and how each library behaves once you start customizing.
Side-by-side comparison
| Feature | Drivn | shadcn/ui |
|---|---|---|
| Runtime UI dependencies | None (React + Tailwind) | @radix-ui/react-avatar |
| API shape | Single component + props | 3 named exports (Avatar, AvatarImage, AvatarFallback) |
| Fallback trigger | Conditional render (src absent) | Image onError event (Radix managed) |
| Auto-fallback on 404 | ||
| Built-in size variants | 4 (sm, md, lg, xl) | None (className-driven) |
| Initials truncation | Auto (first 2 chars, uppercased) | Manual (children of AvatarFallback) |
| Fallback surface | bg-muted token + font-medium text-foreground | bg-muted, children-styled |
| Source size | ≈43 lines, no primitive | Wrapper + Radix primitive |
| License | MIT | MIT |
| Copy-paste install |
API side-by-side
shadcn/ui's Avatar is three components composed inside each other: an Avatar root, an AvatarImage for the photo, and an AvatarFallback for the initials or placeholder. You write all three every time you render an avatar — for a comment thread with fifty rows, that is 150 component calls plus any icon or text inside AvatarFallback. Drivn's Avatar is a single component: you pass src, alt, fallback, and size. One call, four props, done.
The tradeoff is composability versus ergonomics. shadcn's slot model lets you put any JSX inside the fallback without prop surgery; Drivn's prop model keeps the call site clean at the cost of a one-line edit to the component file when you want a custom fallback other than initials. For most applications — comment threads, user tables, account headers — the initials fallback is all you need, so the prop API saves repetitive markup throughout the codebase.
1 // shadcn/ui — three imports, nested slots 2 import { 3 Avatar, 4 AvatarImage, 5 AvatarFallback, 6 } from '@/components/ui/avatar' 7 8 <Avatar> 9 <AvatarImage src="/user.jpg" alt="Jane Doe" /> 10 <AvatarFallback>JD</AvatarFallback> 11 </Avatar> 12 13 // Drivn — one import, prop API 14 import { Avatar } from '@/components/ui/avatar' 15 16 <Avatar 17 src="/user.jpg" 18 alt="Jane Doe" 19 fallback="JD" 20 size="md" 21 />
Image fallback behavior
The most meaningful difference between the two libraries is what happens when the image fails to load. shadcn/ui's Radix primitive listens to the browser's onError event on the underlying <img> element. When the image returns a 404, the network is unavailable, or the URL is malformed, Radix transitions from the image slot to the fallback slot automatically, without any prop change. You always see either the image or the fallback, never a broken image icon.
Drivn's Avatar uses a conditional render keyed on whether src is truthy. If src is an empty string, undefined, or null, the fallback renders immediately. If src has a value — even a URL that returns 404 — the <img> element renders and the browser handles the error normally. In practice a broken image icon appears if the URL resolves to nothing. The fix is to validate image URLs before passing them as src, or add a one-line onError handler inside avatar.tsx after install. Because the source lives in your repo, you own that change permanently without waiting for a library release.
Built-in size variants
shadcn/ui's Avatar has no built-in sizes. The root container defaults to h-10 w-10 (40 px), and any other size requires overriding the className at the call site. Drivn ships four named sizes — sm (32 px), md (40 px, default), lg (48 px), and xl (64 px) — as entries in the const styles.sizes object. Passing size="lg" to a comment author avatar and size="sm" to a data table cell requires no className at the call site.
Adding a custom size to Drivn is one line in avatar.tsx: add a key to styles.sizes with a width and height Tailwind class, and the keyof typeof styles.sizes type picks it up immediately. The theming guide describes how to add a CSS custom property if your brand needs a size that the four defaults do not cover. For teams building tables, lists, and headers that render the same Avatar at different scales, the built-in size variants eliminate a className-per-callsite pattern that accumulates quickly.
Initials and fallback content
shadcn/ui's AvatarFallback renders whatever children you pass — two letters, a Lucide icon, an inline SVG. You own the content, and you write it at every call site. Drivn moves that decision into a prop: pass a string as fallback and the component renders fallback?.slice(0, 2).toUpperCase() inside a <span className="font-medium text-foreground">. Passing "Jane Doe" renders JA; passing "JD" renders JD. The circle behind it is bg-muted, inherited from styles.base, so the fallback picks up your theme's neutral surface in both light and dark mode without a className at the call site.
Swapping in a non-initials fallback — an icon, a silhouette, a question mark — is a one-line edit to the else branch in avatar.tsx, and it lands everywhere at once. With shadcn you pass the icon as children and edit nothing, but you repeat it per instance. That is the real trade: the slot model wins when fallback content varies row to row, and the prop model wins when it never does — which, in comment threads, member lists, and account headers, is most of the time. The avatar examples page shows both shapes in context.
Customization
Both libraries put the Avatar source directly in your repo, so both invite the same category of customizations. The shape of the customization differs. shadcn adds sizing and shape overrides via className at the call site. Drivn's const styles object is a plain record of Tailwind strings — styles.sizes.sm, styles.sizes.md, styles.base — all indexed by keyof typeof types. Adding a new size or changing the base border radius is a literal string edit.
For design token integration, Drivn's Avatar leans on two tokens: bg-muted on the container and text-foreground on the initials span. Both map to CSS custom properties in globals.scss, so rebranding the fallback surface is a token edit that propagates to every Avatar instance automatically. The theming docs cover which tokens are involved. For adding a ring, a border, or a status dot overlay, the Avatar's outer div has relative and overflow-hidden applied — stack a positioned <span> as a sibling wrapped in a relative container. The avatar examples page shows a working status dot pattern.
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 Avatar uses a conditional render — it renders <img> when src is truthy and the initials span when it is not. A 404 URL is still truthy, so the browser renders a broken image. To add auto-fallback behavior, open src/components/ui/avatar.tsx after install and add onError={(e) => { (e.target as HTMLImageElement).style.display = "none" }} to the <img> element, then conditionally show the fallback span. Because the source lives in your repo, this is a permanent one-time edit.
Edit src/components/ui/avatar.tsx and replace the fallback <span> with your icon. The component uses a simple conditional render — {src ? <img ... /> : <span>...</span>} — so the else branch is the fallback content. Swap the span for <UserRound className="w-1/2 h-1/2 text-foreground" /> or any other icon component. The bg-muted background from the outer container applies automatically.
Yes — wrap the Avatar in a relative container and add a positioned sibling element. A common pattern is <div className="relative inline-flex"><Avatar .../><span className="absolute bottom-0 right-0 w-3 h-3 bg-green-500 rounded-full border-2 border-background" /></div>. The outer div positions the dot relative to the avatar circle; the border-background class gives a ring effect that matches both light and dark themes automatically.
Open src/components/ui/avatar.tsx after install and add a key to the styles.sizes object — for example "2xl": "w-20 h-20 text-xl". The size prop type is derived from keyof typeof styles.sizes, so TypeScript autocompletes the new size at every call site immediately with no other change. The new variant is available across the entire codebase as soon as you save the file.

