ScrollArea

A custom scrollable container with styled, OS-consistent scrollbars. Built on @base-ui/react/scroll-area. Supports both vertical and horizontal scrolling.

Anatomy

<ScrollArea> is the root. It wraps a viewport and injects a styled <ScrollBar> automatically. The internal ScrollBar can also be used standalone for a horizontal scrollbar.

Examples

Tag List

Message Log

Horizontal Scroll

Set w-max on the inner content and constrain the container width to enable horizontal scrolling.

Design Guidelines

Do

  • Set an explicit height. Without a fixed height the scroll area collapses to fit its content and scrolling never activates.
  • Use for overflow within a layout region. Sidebars, log panels, tag pickers, and message feeds are good candidates.
  • Prefer scrollable containers over page scroll for panels. Isolating scroll to a region keeps the rest of the layout stable.

Don't

  • Don't wrap the whole page. Page-level scrolling should use the native browser scroll, not a custom ScrollArea.
  • Don't use for short lists. If all items fit without scrolling, the custom scrollbar is unnecessary visual noise.
  • Don't forget keyboard access. The viewport is focusable — users should be able to scroll with arrow keys once it receives focus.

Developer Reference

Accessibility

  • The viewport receives focus-visible ring styles — keyboard users can focus it and scroll with arrow keys.
  • Custom scrollbars are hidden from the accessibility tree via aria-hidden; native scroll semantics are preserved on the viewport.
  • Use ScrollBar directly with orientation="horizontal" when you need both axes independently styled.

Usage

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

// Vertical scroll
<ScrollArea className="h-48 w-full rounded-lg border p-4">
  {items.map((item) => (
    <div key={item.id}>{item.label}</div>
  ))}
</ScrollArea>

// Horizontal scroll
<ScrollArea className="w-full rounded-lg border">
  <div className="flex w-max gap-4 p-4">
    {items.map((item) => (
      <div key={item.id} className="w-32 shrink-0">{item.label}</div>
    ))}
  </div>
</ScrollArea>