Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Scroll Area

A scroll container with custom, auto-hiding scrollbars — dual-axis, token-styled, replacing the native browser scrollbar.

Status
stable
Since
0.1.0
Accessibility pattern
focusable scrollable region

Last updated

Install

Add Scroll Area from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/scroll-area

Usage

import { ScrollArea } from "@/components/ui/scroll-area";

<ScrollArea className="h-72 w-full">
  <div>Long, overflowing content…</div>
</ScrollArea>;

Constrain the viewport with className (e.g. h-72). Without a bounded height (or width, for horizontal) the content cannot overflow and no scrollbar appears.

Anatomy

ScrollArea is a thin wrapper over Base UI's compound ScrollArea (Root → Viewport → Scrollbar → Thumb, plus a Corner for dual-axis). The flat ScrollArea composes those parts for you and renders the scrollbar(s) for the chosen orientation; ScrollBar is exported separately for custom layouts.

ScrollArea — data-slot="scroll-area" | "scroll-area-corner" | "scroll-area-viewport"
ScrollBar — data-slot="scroll-area-scrollbar" | "scroll-area-thumb"
<ScrollArea orientation="both" className="h-72 w-full">
  {/* viewport content */}
</ScrollArea>
  • ScrollArea — the root scroll container (data-slot="scroll-area"). The className sizes the scrollable viewport (data-slot="scroll-area-viewport"); orientation picks which scrollbars to render.
  • ScrollBar — one custom scrollbar track for a single axis (data-slot="scroll-area-scrollbar", data-orientation). Auto-hides when idle, fading in on hover/scroll via Base UI's data-hovering / data-scrolling state attributes. Rendered automatically; use it directly only for custom compositions.
  • scrollbarProps — optional props passed to the automatically rendered scrollbar(s), such as keepMounted for deterministic tests or a custom visibility policy.
  • The thumb (data-slot="scroll-area-thumb") is the draggable bar — bg-border rounded-full.
  • The corner (data-slot="scroll-area-corner") fills the intersection when orientation="both".

Examples

Vertical (default)

A bounded-height list. The scrollbar sits on the right edge and fades in while you hover or scroll.

Horizontal

Set orientation="horizontal" for a row that overflows sideways; constrain the width instead of the height.

Both axes

Set orientation="both" for content that overflows on both axes. Two scrollbars render plus a corner (data-slot="scroll-area-corner") filling their intersection.

Orientations

vertical (default), horizontal, and both side by side — both is the one case that adds the intersection corner and a second scrollbar.

vertical
horizontal
both

Playground

Switch the scrollbar orientation over content that overflows on the matching axis, then copy the generated JSX.

<ScrollArea className="h-40 w-56 rounded-md border" aria-label="Changesets">
  {/* overflowing content */}
</ScrollArea>

API Reference

ScrollArea

PropTypeDefaultDescription
childrenReact.ReactNodeScrollable content.
classNamestringClasses for the scroll container. Set the height/width constraints here (e.g. h-72 w-full) — without a bounded size the content cannot overflow and no scrollbar appears. The inner viewport fills this box.
orientation"both" | "horizontal" | "vertical"'vertical'Which scrollbar(s) to render. vertical (the default) and horizontal each render a single axis; both renders both plus the intersection corner for dual-axis content.
scrollbarPropsOmit<ScrollBarProps, "orientation">Props applied to the automatically rendered scrollbar(s). Useful for tests or custom visibility policy, e.g. keepMounted.

Data attributes and CSS variables on ScrollArea

AttributeValues
data-scrollable""
data-slot"scroll-area" | "scroll-area-corner" | "scroll-area-viewport"

ScrollBar

PropTypeDefaultDescription
orientation"horizontal" | "vertical"'vertical'Which axis the scrollbar controls.

Data attributes and CSS variables on ScrollBar

AttributeValues
data-slot"scroll-area-scrollbar" | "scroll-area-thumb"

Accessibility

  • The viewport is keyboard-focusable only while it can actually scroll — overflow is measured on mount and on resize, and the viewport carries data-scrollable while it is a tab stop. Keyboard users need somewhere for the arrow keys to land, but a region whose content fits would otherwise add a stop that announces nothing and does nothing. Pass aria-label or aria-labelledby to ScrollArea; the component applies it to that viewport.
  • Its focus ring is drawn inside the viewport (focus-visible:-outline-offset-2). The root clips its overflow, so an outward-offset outline would be cut in half.
  • The focused viewport uses the global :focus-visible ring (outline-ring) — never outline: none.
  • Scrollbars are pointer affordances only and stay out of the tab order; the native scroll keys remain fully functional, so the custom scrollbar never blocks keyboard access.
  • overscroll-contain keeps scroll momentum inside the area instead of chaining to the page.
KeyAction
TabMove focus to the viewport (when it can scroll).
/ Scroll vertically by a line.
/ Scroll horizontally by a line.
Page Up / Page DownScroll by a page.
Home / EndScroll to the start / end.
ContractStates tested
Behaviourdefault
Accessibilitylabeled
Visualdefault

Do / Don't

Do
Give the viewport a bounded size via className (e.g. h-72) so its content can overflow and the scrollbar appears.
Don't
Wrap whole-page content in a ScrollArea — let the document scroll natively and reserve this for bounded panels.

On this page