Scroll Area
A scrolling region with thin scrollbars that follow the theme. Fixed or content heights, vertical, horizontal, or both bars, and a bordered table frame for wide data grids.
Sizes
Each size with the variant and orientation it is used with. The bars appear only on axes that overflow.
Data grid
variant="table" with orientation="both". The table label names the region, and the header stays pinned while rows scroll.
Horizontal
orientation="horizontal" for a single row of cards. Shift + wheel, a trackpad swipe, or arrow keys on the focused region scroll it.
States
Loading and error content keep the table frame. Empty content does not overflow, so no bars draw and the region is skipped by Tab.
Usage
import { ScrollArea } from "@tulipfarm/sprout/scroll-area";
<ScrollArea size="sm" viewportLabel="Recent activity">
{records.map((record) => <p key={record.id}>{record.title}</p>)}
</ScrollArea>
<ScrollArea size="md" orientation="both" viewportLabel="Wide report">
<div className="w-160">…</div>
</ScrollArea>
<ScrollArea
variant="table"
orientation="both"
viewportLabel="Bulb plantings"
data-slot="table-container"
>
<table aria-label="Bulb plantings">…</table>
</ScrollArea>size is "auto" (default, follows content or yourclassName), "sm" (10rem tall), or "md"(16rem tall). orientation is "vertical" (default),"horizontal", or "both". Each bar appears only while its axis overflows, and the corner fills the gap when both show.
variant="table" adds a 1px input-tone border, rounded corners, and the surface color. It caps the viewport at 60dvhand gives it role="region", named by viewportLabel. A sticky thead inside it stays pinned while rows scroll. Pass the table's own aria-label as viewportLabel. Other root props, such as data-slot, pass through to the root.
ScrollBar
import { ScrollBar } from "@tulipfarm/sprout/scroll-area";
<ScrollBar orientation="horizontal" className="my-bar" />ScrollArea draws its bars for you. ScrollBar is the same 10px track and pill thumb, exported for custom Base UI scroll area compositions. It takes Base UI scrollbar props, and classNamemay be a state function.
Accessibility
Scrolling stays native, so wheel, trackpad, touch, and keyboard all work. When content overflows, the viewport joins the Tab order and shows a focus ring; arrow keys, Page Up, Page Down, Home, and End then scroll it. When nothing overflows it leaves the Tab order. The bars are hidden from screen readers. Thumb color changes take 120ms and stop when reduced motion is requested.
Theme tokens
Thumbs use --sprout-muted at 40%, and 60% on hover or while scrolling. The table frame uses --sprout-input and --sprout-surface. Focus uses --sprout-focus.