Scroll Area
Augments native scroll functionality for custom, cross-browser styling.
Installation
Install the following dependencies:
Set up the token layer and the StyleX compiler, if you have not already. See
Installation, and paste states.ts from the same page,
which is where the viewport's focus ring comes from.
Copy and paste the following code into your project.
Update the import paths to match your project setup.
Usage
Size and frame the root through xstyle. The viewport fills it and inherits
its rounding, so a border and a radius on the root clip the content and shape
the focus ring at once. ScrollArea renders the viewport, a vertical
ScrollBar and the corner itself.
The scrolling element is a real scroll container with the native bar hidden,
so the wheel, the keyboard and touch all work as they do on any overflow
box. A scrollbar is mounted only while its axis overflows.
Horizontal
Add a ScrollBar with orientation="horizontal" for content wider than the
frame. Give the content a max-content width or it wraps instead of
overflowing.
API reference
Pass render to replace a part's element or compose it with another component.
Every part wraps the Base UI ScrollArea part of the same name and accepts
its props. See the Base UI reference.
Both parts also take xstyle and className.
ScrollArea
| Prop | Type | Default |
|---|---|---|
viewportProps | ScrollAreaPrimitive.Viewport.Props & { xstyle?, className? } | — |
overflowEdgeThreshold | number | Partial<{ xStart; xEnd; yStart; yEnd }> | 0 |
xstyle | StyleXStyles | — |
className | string | — |
ScrollArea passes viewportProps to the element that scrolls. Put a ref there to scroll
it from code, an onScroll to watch it, or className="scroll-fade-y" to
fade its edges with the mask from interop.css.
ScrollBar
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "vertical" |
keepMounted | boolean | false |
scrollAreaStyles
The style maps behind the component. Spread them onto any element.
| Key | What it is |
|---|---|
viewport | Fills the root, inherits its radius, plus the ring |
scrollbar | The track: flex, 1px gutter, no touch or selection |
orientations | vertical and horizontal track geometry |
thumb | The rounded border-coloured grip |
viewport is two styles, the scroller's own box and states.focusRing from
theme/states.ts. The frame is on the root, so the viewport has no border of
its own and only the ring's halo shows.
Data attributes
| Attribute | On | Effect |
|---|---|---|
data-slot="scroll-area" | the root | Targets the frame from a parent |
data-slot="scroll-area-viewport" | the scroller | Targets the element that scrolls |
data-slot="scroll-area-content" | the content box | Base UI watches its size to move the thumb |
data-slot="scroll-area-scrollbar", data-orientation | a track | Targets one bar; Base UI writes the orientation |
data-slot="scroll-area-thumb" | a grip | Targets the thumb |
data-slot="scroll-area-corner" | the corner | Present only while both bars are |
data-has-overflow-x, data-has-overflow-y | root, viewport, tracks | Base UI writes them while an axis overflows |
data-overflow-x-start, -x-end, -y-start, -y-end | root, viewport, tracks | Base UI writes them while content is hidden past that edge |
data-scrolling, data-hovering | tracks | Base UI writes them during a scroll and while hovered |