Skip to content

Scroll Area

Stable

A scrollable region with quiet overlay scrollbars that match the theme — native scrolling underneath, consistent chrome on top.

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/scroll-area.json

Usage

import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
<ScrollArea className="h-72 w-48 rounded-xl border">
  {/* long content */}
</ScrollArea>

Give the scroll area a fixed height (or width, for horizontal content) — it scrolls whatever overflows that box. Scrolling itself is native: wheel, trackpad momentum, touch, keyboard and scroll anchoring all behave exactly as they would on a plain overflow: auto element.

Examples

Horizontal

Content that should scroll sideways needs to be wider than the area (w-max on a flex row) and a horizontal scrollbar. Add <ScrollBar orientation="horizontal" /> as a child — the vertical one is always included and only appears when there's vertical overflow.

Both axes

Wide data — spreadsheets, timelines, large tables — can scroll in both directions. Sticky headers and first columns keep their bearings; the corner where scrollbars meet stays clear.

Fading edges

The root reports where content overflows through data-overflow-y-start and data-overflow-y-end (and -x- equivalents). Fade the edge only when there is more content in that direction — a cue that the region scrolls, which disappears once you reach the end.

<ScrollArea
  className={cn(
    "relative h-72",
    "after:absolute after:inset-x-0 after:bottom-0 after:h-10 after:bg-linear-to-t after:from-card",
    "after:opacity-0 after:transition-opacity data-overflow-y-end:after:opacity-100"
  )}
>

Scrollbars

Scrollbars overlay the content instead of taking space from it, so layouts don't shift between platforms. They stay hidden until you hover the area or scroll, then fade away; the thumb darkens on hover and can be dragged.

Guidelines

When to use

  • For bounded regions inside a layout that scroll independently: menus, side panels, chat threads, lists in cards, code.
  • When the platform's default scrollbar would clash with a dense, themed interface — especially thick, always-visible scrollbars on Windows.

When not to use

  • For the page itself. Let the document scroll natively; nested scrolling for the main content makes people fight two scroll positions.
  • To hide content that doesn't fit. If people must scroll a small box to find something important, give it more room.
  • For horizontal scrolling of primary content on desktop. People miss sideways overflow; prefer wrapping or pagination.

Show that it scrolls

Overlay scrollbars are hidden at rest, so the region itself has to suggest that there's more. Cut content mid-item at the edge, fade the overflowing edge, or show a count.

Changelog

v1.4.0 — Tokens v2

v1.3.2 — Fixes

v1.3.1 — Dialog focus

Do.The last item is cut at the edge — it obviously continues.

Changelog

v1.4.0 — Tokens v2

v1.3.2 — Fixes

Don’t.The box ends exactly between items — nothing says there's more.

One direction at a time

Prefer a single scroll axis per region. Two-axis scrolling is right for grids of data, where people expect it; elsewhere it makes content feel lost.

Accessibility

  • Keyboard scrolling. The viewport becomes a tab stop only while its content overflows. Once focused, , Page Up, Page Down, Home, End and Space scroll it natively, and a 2px inset focus ring shows where you are.
  • Name the region. A focusable scroll region with no name is announced as nothing in particular. When the content is meaningful on its own, wrap it in a labelled landmark (<section aria-label="Release history">) or give it a visible heading.
  • Scrollbars are pointer affordances. Screen reader and keyboard users scroll the viewport directly; the custom scrollbars are hidden from assistive technology like native ones.
  • Reduced motion. Scrollbar fades are instant with prefers-reduced-motion.

API reference

ScrollArea

The scroll container: root, viewport, a vertical ScrollBar and the corner. className applies to the root; children render inside the viewport. Accepts every prop of Base UI ScrollArea.Root.

PropTypeDefault
overflowEdgeThreshold

Pixels of overflow required before the data-overflow-* edge attributes are set.

number | { xStart?, xEnd?, yStart?, yEnd? }0
className

Styles the root. Set a fixed height or width here.

stringNo default

ScrollBar

A scrollbar and its thumb. Add one with orientation="horizontal" for sideways scrolling. Accepts every prop of Base UI ScrollArea.Scrollbar.

PropTypeDefault
orientation

The axis the scrollbar controls.

"vertical" | "horizontal""vertical"
keepMounted

Keeps the scrollbar in the DOM even when that axis doesn't overflow.

booleanfalse

Data attributes

Set on the root; use them to style edges and states.

AttributePresent when
data-has-overflow-x / data-has-overflow-yContent overflows on that axis.
data-overflow-x-start / data-overflow-x-endThere is more content to the left / right.
data-overflow-y-start / data-overflow-y-endThere is more content above / below.
data-scrollingThe area is being scrolled.