Scroll Area

Native scrolling with consistent, custom scrollbars.

Preview

Use Scroll Area for constrained content where a custom scrollbar improves consistency. Wheel, touch, and keyboard scrolling remain native.

Installation

pnpm dlx shadcn@latest add http://127.0.0.1:3017/r/scroll-area.json

Installs ScrollArea and ScrollBar with Base UI. Uses your existing cn utility and semantic tokens. Scroll Fade is an optional separate item. See the registry guide for endpoint details.

Usage

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

<ScrollArea
  className="h-48 rounded-md border"
  viewportProps={{ role: 'region', 'aria-label': 'Project activity' }}
>
  {/* Content taller than the viewport */}
</ScrollArea>

Constrain the root's height for vertical overflow or width for horizontal overflow. Put content padding on an inner element so scrollbar tracks stay at the outer edges. Base UI makes the viewport keyboard-focusable when content overflows.

Composition

ScrollArea
├── Viewport (internal, configured through viewportProps)
│   └── Content (internal)
│       └── Your content
├── ScrollBar (vertical, horizontal, or both, internal)
│   └── Thumb (internal)
└── Corner (internal)

The wrapper assembles the parts. Root props and refs reach the outer container; viewportProps reaches the scrolling element, including its ref, accessible name, scroll handlers, and Base UI render prop. orientation chooses which scrollbars are rendered; content dimensions determine actual overflow.

ScrollBar is also exported for custom Base UI Scroll Area compositions. Do not add another ScrollBar as a content child of this wrapper; its configured bars are already supplied outside the viewport.

Horizontal scrolling

<ScrollArea orientation="horizontal" className="max-w-sm" viewportProps={{ 'aria-label': 'Project stages' }}>
  <div className="flex w-max gap-8 p-4">{/* Stages */}</div>
</ScrollArea>

Use orientation="both" when both custom scrollbars are needed. Base UI handles the intersection corner and hides bars when the corresponding axis does not overflow.

With Scroll Fade

import { ScrollFade } from '@/components/ui/scroll-fade';

<ScrollArea
  className="h-40 rounded-md border"
  viewportProps={{
    render: <ScrollFade orientation="vertical" />,
    role: 'region',
    'aria-label': 'Project activity',
  }}
>
  <div className="p-4">{/* Long content */}</div>
</ScrollArea>

Use this composition in a Client Component. It renders one scrolling element shared by Scroll Area and Scroll Fade. The mask applies to the viewport while the scrollbar remains outside it. Do not wrap the viewport in a separately scrolling ScrollFade. See Scroll Fade for installation and behavior.

Appearance and accessibility

  • Scrollbar tracks are 10px wide, with 2px padding and a rounded muted-foreground thumb. Hover increases thumb emphasis. No default background, border, or shadow is added to the root.
  • The viewport uses Migoy's solid 2px focus outline, inset to remain visible within constrained layouts.
  • Give each scrolling region a useful accessible name through viewportProps. Add role="region" when a landmark is helpful.
  • Custom scrollbar dragging is provided by Base UI. Keep wheel, touch, and keyboard access available; never require dragging.
  • Reduced-motion preferences disable thumb color transitions.
  • Use Base UI's DirectionProvider when configuring RTL scrollbar placement; dir alone does not configure the primitive's direction context.

API

PropDefaultPurpose
orientationverticalRender vertical, horizontal, or both bars
viewportProps—Native viewport props, ref, and Base UI render composition
Root props—Includes className, children, ref, and Base UI root options
ScrollBar orientationverticalAxis for a custom primitive composition

Based on shadcn Scroll Area and Base UI Scroll Area.

On this page