Experience System
Components

Scroll Area

Styled scrollbars for tight layouts—use sparingly when native scrolling is not enough.

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20

ScrollArea wraps content that overflows and shows Experience System scrollbars. The viewport can receive keyboard focus so people can move through long content without a mouse. Vertical scrolling is built in; add a horizontal scrollbar only when you need sideways overflow. Fixed height and width on ScrollArea define the viewport; the inner block's height drives vertical scrolling.

Installation

The components are exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Use the following composition to build a ScrollArea:

ScrollArea
└── ScrollBar (optional, e.g. for horizontal overflow)

ScrollArea renders the viewport, a default vertical ScrollBar, and a corner element internally—so the minimal usage is just ScrollArea with content inside. Add <ScrollBar orientation="horizontal" /> as a child when you also need horizontal scrolling. Behavior follows Radix Scroll Area.

Usage

Prefer native scrolling in general—it is usually more accessible and more consistent with platform behavior. ScrollArea is only preferred when the UI wins over UX in that trade-off: tight context menus, compact UIs, and similar surfaces where custom scrollbars match the design.

import { ScrollArea, ScrollBar } from '@by/experience-system';

These are client components ('use client'). In the Next.js App Router, use them inside a client component or a dynamic import. Put ThemeProvider above the tree so reading direction flows into ScrollArea when you do not set dir yourself. Name the region for assistive technology with aria-label, aria-labelledby, or aria-describedby.

<ScrollArea
  aria-label="Messages"
  className="h-80 min-h-0 rounded-md border border-neutral-alpha-3"
>
  <ul className="divide-y divide-neutral-alpha-3 p-2">{/* long list */}</ul>
</ScrollArea>

Examples

Compact panel (menu-style)

ScrollArea is only preferred when the UI wins over UX in that trade-off: tight context menus, compact UIs, and similar surfaces where custom scrollbars match the design.

Horizontal

A wide row with whitespace-nowrap and w-max, plus <ScrollBar orientation="horizontal" />.

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9

API Reference

Pieces map to Radix UI Scroll Area primitives. The tables below mirror the upstream API in full; the scrollbarBehavior variant, viewport tabIndex, role="region", and aria-* proxying are Experience System additions.

ScrollArea

ScrollArea is the Radix ScrollArea.Root with the Experience System viewport, a vertical ScrollBar, and a corner rendered internally. Set aria-label / aria-labelledby / aria-describedby to name the region; these are mirrored onto the viewport so screen readers describe the focusable scroll container too.

PropTypeDefault
asChildbooleanfalse
typeenum"hover" ("auto" | "always" | "scroll" | "hover")
scrollHideDelaynumber600
direnumFrom ThemeProvider
scrollbarBehaviorenum"increasing" (experience system: "increasing" | "fixed")
Data attributeValues
[data-slot]"scroll-area" (experience system)
[role]"region" (experience system)

The internal viewport carries [data-slot="scroll-area-viewport"], role="group", and tabIndex={0} so keyboard users can move focus into the scrollable content.

ScrollBar

ScrollBar is the Radix ScrollArea.Scrollbar plus a thumb. The default ScrollArea already renders a vertical ScrollBar; add <ScrollBar orientation="horizontal" /> as a child when the content also overflows horizontally.

PropTypeDefault
asChildbooleanfalse
forceMountbooleanNo default value
orientation"horizontal" | "vertical""vertical"
scrollbarBehaviorenumInherits from ScrollArea (experience system: "increasing" | "fixed")
Data attributeValues
[data-slot]"scroll-area-scrollbar" (experience system)
[data-state]"visible" | "hidden"
[data-orientation]"horizontal" | "vertical"
[role]"scrollbar"

The thumb is rendered internally with [data-slot="scroll-area-thumb"].

Accessibility

Native scrolling is preferred over the ScrollArea component because it offers better accessibility and lets users rely on their system-level preferences and behaviors. Refer to the Usage section for specific cases where ScrollArea is appropriate.

The viewport is focusable and sits in a group; your aria-* props label the region. Scrollbars expose role="scrollbar". Even with ScrollArea, keep primary flows usable with native scrolling where you can, and add a clear label when the scrollable region is not already described by a nearby heading.

Keyboard interactions

KeyDescription
TabMoves focus into the viewport (which is tabIndex={0}) so the scrollable region can be reached without a pointer.
ArrowUp / ArrowDownScrolls vertically in small increments.
ArrowLeft / ArrowRightScrolls horizontally in small increments (when a horizontal ScrollBar is present).
PageUp / PageDownScrolls by one viewport.
Home / EndJumps to the start or end of the scroll range.

Source in the repo: packages/experience-system/src/components/ScrollArea/ScrollArea.tsx and variants.ts. Agent-oriented contracts: packages/experience-system/src/components/ScrollArea/ScrollArea.instructions.md.