Skip to content
Drivn
6 min read

Drivn vs shadcn/ui — Scroll Area Compared

Compare Drivn Scroll Area vs shadcn/ui — a CSS-styled native scroller versus a Radix custom scrollbar. Both copy-paste, one ships zero runtime deps.

Drivn's Scroll Area and shadcn/ui's Scroll Area both solve the same problem — a scrollable region whose scrollbar matches the design instead of the operating system default — but they reach it through different machinery. shadcn/ui wraps @radix-ui/react-scroll-area and ships two exports, ScrollArea and ScrollBar, where the visible scrollbar is a stack of Radix-painted <div> elements layered over a ScrollAreaViewport. Radix runs JavaScript to size and position the thumb so the bar looks pixel-identical in every browser.

Drivn ships a single file at @/components/ui/scroll-area.tsx that renders one <div>. There is no Radix, no JavaScript scrollbar, and no hooks — the component is a plain function that spreads React.HTMLAttributes<HTMLDivElement> and relies on native browser scrolling. The scrollbar is styled entirely in CSS through the scrollbar-width and scrollbar-color properties for Firefox and the ::-webkit-scrollbar pseudo-elements for Chromium and Safari, all collected in the styles.base string reproduced verbatim below.

The practical split is a CSS-styled native scroller versus a JavaScript-driven custom scrollbar. The Drivn flavor carries no 'use client' directive and renders as a server component; the shadcn flavor is a Radix client component with the package in the bundle. This page walks the runtime footprint, the scrollbar model, the orientation prop, the sizing rule, and the call site so you can pick the right flavor.

Side-by-side comparison

FeatureDrivnshadcn/ui
Underlying primitiveNative browser scrolling + CSS-styled scrollbar@radix-ui/react-scroll-area
Runtime UI dependenciescn() utility only@radix-ui/react-scroll-area
API shapeSingle component — ScrollAreaTwo exports — ScrollArea + ScrollBar
Scrollbar renderingCSS — scrollbar-width + ::-webkit-scrollbarJavaScript — Radix-painted thumb
Client componentNo — renders on the serverYes — 'use client' Radix wrapper
Orientation propvertical and horizontalvertical and horizontal
Cross-browser pixel parityBrowser-native — varies slightly by engineIdentical — Radix paints the thumb
Scroll handler at runtimeNoneJS runs on scroll + resize
LicenseMITMIT
Copy-paste install

The runtime footprint

shadcn/ui's Scroll Area is a layer over @radix-ui/react-scroll-area. The component file imports ScrollAreaPrimitive from the Radix package and re-exports ScrollAreaPrimitive.Root plus ScrollAreaPrimitive.Viewport as ScrollArea, then composes a ScrollBar from ScrollAreaPrimitive.Scrollbar, ScrollAreaPrimitive.Thumb, and ScrollAreaPrimitive.Corner. Radix supplies the JavaScript that measures the content, sizes the thumb, and keeps the custom bar in sync with the scroll position.

Drivn's Scroll Area imports React and the local cn() utility — nothing else. Every class the component uses lives in the styles object below, reproduced verbatim from the registry. styles.base sets scrollbar-width: thin and scrollbar-color for Firefox, then a run of ::-webkit-scrollbar pseudo-element rules for Chromium and Safari: a 1.5-unit-wide track, a rounded-full thumb tinted bg-border, and a hover state that darkens the thumb to bg-muted-foreground/30. No Radix, no JavaScript, no icon package. See the Scroll Area examples page for the call sites, and the Separator component for the same zero-dependency framing applied to a divider.

1// Drivn — styles object, no Radix (verbatim from registry)
2const styles = {
3 base: cn(
4 'relative [scrollbar-width:thin]',
5 '[scrollbar-color:var(--border)_transparent]',
6 '[&::-webkit-scrollbar]:w-1.5',
7 '[&::-webkit-scrollbar]:h-1.5',
8 '[&::-webkit-scrollbar-track]:bg-transparent',
9 '[&::-webkit-scrollbar-track]:rounded-full',
10 '[&::-webkit-scrollbar-thumb]:rounded-full',
11 '[&::-webkit-scrollbar-thumb]:bg-border',
12 '[&:hover::-webkit-scrollbar-thumb]:bg-muted-foreground/30',
13 '[&::-webkit-scrollbar-corner]:bg-transparent'
14 ),
15 orientations: {
16 vertical: 'overflow-y-auto overflow-x-hidden',
17 horizontal: 'overflow-x-auto overflow-y-hidden',
18 },
19}

CSS-styled scrollbar vs Radix custom scrollbar

This is the real divergence. Drivn does not draw a scrollbar at all — it lets the browser scroll natively and restyles the browser's own scrollbar with CSS. Firefox honours scrollbar-width: thin and scrollbar-color; Chromium and Safari honour the ::-webkit-scrollbar family. The result is a thin, theme-tinted bar that costs zero JavaScript and zero layout work, because the browser was going to paint a scrollbar anyway.

shadcn/ui hides the native scrollbar and paints its own. Radix renders a ScrollAreaThumb div whose height and offset are computed in JavaScript from the viewport and content sizes, updated on every scroll and resize. The payoff is pixel-identical scrollbars across every engine and full control over the thumb through a className on ScrollBar. The cost is the Radix package in the bundle, a 'use client' boundary, and a JavaScript handler running while the user scrolls. Drivn's component, by contrast, is a single <div> that spreads ...props and merges className through cn() — it has no state, so it renders fine as a server component. See the Scroll Area component reference for the full props table.

1// Drivn — the whole component is one styled <div> (verbatim)
2export function ScrollArea({
3 orientation = 'vertical',
4 className,
5 children,
6 ...props
7}: ScrollAreaProps) {
8 return (
9 <div
10 className={cn(
11 styles.base,
12 styles.orientations[orientation],
13 className
14 )}
15 {...props}
16 >
17 {children}
18 </div>
19 )
20}

The orientation prop

Both libraries expose vertical and horizontal scrolling, and both default to vertical. Drivn reads an orientation prop — 'vertical' or 'horizontal' — and uses it to pick a key from the orientations map. Vertical resolves to overflow-y-auto overflow-x-hidden, horizontal to overflow-x-auto overflow-y-hidden, so exactly one axis scrolls and the other is clipped. The map is reproduced verbatim below; there is no diagonal or both-axes mode, which keeps the contract small and predictable.

For a horizontal Scroll Area the child content has to be wider than the container — typically a flex row with w-max so the items lay out at their natural width and overflow sideways. shadcn/ui passes its orientation to the Radix ScrollBar and renders a matching horizontal Radix thumb. The end result is the same affordance; the difference stays the dependency tree and the rendering model. A horizontal Scroll Area pairs well with the Drivn Card for a swipeable row of tiles — see the Scroll Area examples for that call site.

1// Drivn — orientation picks one overflow axis (verbatim)
2orientations: {
3 vertical: 'overflow-y-auto overflow-x-hidden',
4 horizontal: 'overflow-x-auto overflow-y-hidden',
5}

Sizing — the area needs a height

A Scroll Area only scrolls when its content is taller (or wider) than the box. Neither Drivn nor shadcn/ui can infer a height for you — the component is a styled <div>, and a <div> grows to fit its content by default. To get scrolling you set a fixed height or a max-height through the className: <ScrollArea className="h-48"> caps the box at 12rem and lets anything past that scroll. This is the single most common reason a Scroll Area appears not to work — the content simply fit.

shadcn/ui has the same requirement; its Radix viewport also needs a constrained height before the custom thumb appears. Because the height lives on the className in both libraries, the sizing code is identical and a migration touches nothing here. Drivn merges that className onto the base styles through cn(), so Tailwind's height utilities, arbitrary values like h-[420px], and max-h-* classes all work. Inside a Dialog or a Card, a max-h-* on the Scroll Area keeps a long list from pushing the surrounding layout.

When each wins

Pick shadcn/ui's Scroll Area when pixel-identical scrollbars across every browser are a hard requirement — a design system that must look the same in Firefox, Chrome, and Safari down to the thumb width, or a product where the scrollbar is part of the brand. Radix's JavaScript-painted thumb gives that exact control, plus a ScrollBar element you can restyle freely. Projects already standardized on Radix pay no extra dependency cost, since the package family is likely in the bundle already.

Pick Drivn's Scroll Area when the priority is a small dependency tree and a component that renders on the server. It is one <div> with a CSS-styled native scrollbar — no Radix, no JavaScript, no 'use client' boundary, and no scroll handler running during interaction. The scrollbar still looks thin and on-theme in every modern engine; it just is not guaranteed pixel-identical between them. For most apps — a sidebar, a chat log, a horizontal card row, a long settings panel — that trade is worth it. Pair the Scroll Area with the Drivn Table for scrollable data and the Command menu for scrollable result lists. See the Scroll Area examples for ready call sites.

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

No. The Drivn Scroll Area is a plain function component — it holds no state, calls no hooks, and renders a single <div>. With no client-only API in the file, the registry source ships without a 'use client' directive, so the component renders as a server component. The shadcn/ui flavor wraps a Radix client component and therefore carries the directive. This is one of the clearest practical differences between the two.

Almost always because the area has no constrained height. A Scroll Area is a styled <div>, and a <div> grows to fit its content, so nothing overflows and nothing scrolls. Set a fixed height or a max-height through the className — for example <ScrollArea className="h-48"> or className="max-h-72". The component scrolls only once its content is taller than that box. The same rule applies to horizontal scrolling, where the content must be wider than the container.

Yes, across modern engines. Firefox honours the scrollbar-width: thin and scrollbar-color properties, while Chromium and Safari honour the ::-webkit-scrollbar pseudo-elements — the styles.base string sets both. The bar renders thin and theme-tinted everywhere, but it is not guaranteed pixel-identical between engines, since each browser paints its own scrollbar. The shadcn/ui flavor uses a Radix JavaScript-painted thumb to get exact parity instead.

Yes. Pass orientation="horizontal" and the component picks the overflow-x-auto overflow-y-hidden key from its orientations map, so the area scrolls sideways and clips the vertical axis. The child content has to be wider than the container — usually a flex row with w-max so the items keep their natural width. The default orientation is vertical, which resolves to overflow-y-auto overflow-x-hidden.

No. The component does not draw or position a scrollbar at all — it lets the browser scroll natively and restyles the browser's own scrollbar with CSS. There is no scroll handler, no thumb-sizing calculation, and no resize observer. The shadcn/ui flavor, by contrast, renders a Radix thumb whose size and offset are computed in JavaScript on every scroll and resize to keep the custom bar in sync.