Primitives
Primitive

Scroll Area

Custom scrollable container with styled scrollbars

The scroll area is a container with custom-styled scrollbars and optional edge fades. Use it for any overflowing region, like a sidebar, a chat log, or a long form, where the native scrollbar would look out of place.

Preview

Installation

Usage

Examples

Fade Edges

Use fadeEdges to add a CSS-mask fade at the edges that appears only where content is scrollable.

The fade depth defaults to min(12%, 2.5rem), 12% of the container capped at 2.5rem so tall scrollers stay subtle. Tune it per instance by setting the --scroll-fade-size CSS variable on viewportClassName:

With nativeScroll, the fade eases in and out over a fixed scroll distance (6rem by default, clamped to the scroll range so short scrollers still fully reveal). Adjust it with --scroll-fade-reveal:

Native Scroll Fade

Add nativeScroll to drive the fade with CSS scroll-driven animations instead of Base UI's measured overflow, falling back to a static fade where unsupported.

With nativeScroll, the fade is driven entirely by CSS scroll-driven animations — no JavaScript scroll listeners. The browser updates the mask as you scroll.

The fade eases in and out over a fixed scroll distance rather than a percentage of the content, so the reveal feels the same whether the list is short or long.

At the top, only the bottom edge fades to hint at more content. As you scroll, the top edge fades in and both edges stay soft. At the very end, the bottom edge sharpens again.

In browsers without scroll-driven animation support, this falls back to a static fade on the configured edges.

Scroll Fade Without the Component

The same edge fade ships as a standalone CSS utility you can drop on any element with overflow, for scroll containers you didn't render with ScrollArea such as a code block, a table wrapper, or a third-party list. See Scroll Fade.

Scrollbar Gutter

Use scrollbarGutter to reserve space for the scrollbar so it doesn't shift the layout when it appears.

Without gutter

With gutter

Persistent Scrollbar

Use persistScrollbar to always show the scrollbar instead of fading it in on hover or scroll.

Horizontal Scroll

Let wide content scroll sideways instead of wrapping.

Both Scrollbars

When content overflows in both directions, both scrollbars appear automatically and the corner keeps them from intersecting.

Fill Content Height

The content wrapper is only as tall as its children, so a short flex column has no slack for mt-auto to work with and a pinned footer floats up under the nav. Set contentHeight="fill" to stretch the wrapper to at least the viewport height and give that footer somewhere to sit.

auto

fill

fill, overflowing

Children of a filled wrapper claim the height with flex-1:

Use flex-1 rather than h-full. A percentage height resolves against the wrapper's own height, which stays growable, so h-full collapses to nothing here.

The wrapper only ever grows from the viewport height, never locks to it, so long content still scrolls normally and keeps its natural size. Leave contentHeight at "auto" for lists, menus, and dialog bodies, where nothing needs to be pushed to the bottom.

Content Width

contentMinWidth decides how wide the internal content wrapper may grow. It defaults to "viewport", which keeps the wrapper at the visible width.

The alternative, "fit-content", is Base UI's own default and lets the wrapper grow to its widest child. That sounds harmless but it switches on intrinsic sizing, and anything in the subtree that physically cannot shrink reports its full width upward. A whitespace-pre code block is the usual culprit. Nesting it in its own scroll area does not help, because a scroll container still passes its content's width up to whatever is measuring it.

The result is a panel meant to scroll vertically that quietly grows a horizontal scrollbar, with the nested scroller stretched wide enough that it never overflows and so never scrolls on its own:

Under "viewport" the wrapper stays put and the code block scrolls inside its own box, which is what you want.

Sideways scrolling still works under "viewport". Children that overflow keep extending the scroll range, so anything sizing itself with w-max, a fixed grid template, or a table's own width behaves exactly as it did before:

What "viewport" gives up is the wrapper stretching across the whole scroll range. Padding on a child lands at the fold instead of the far end, so a horizontally scrolling child with its own padding loses that breathing room at the end of the scroll. Give the child an explicit width to get it back, or switch to "fit-content":

API Reference

The ScrollArea component is built on top of Base UI's ScrollArea. All Base UI props are supported. The documentation below only covers custom props and modified defaults specific to our implementation.

For the complete Base UI API, see the Base UI ScrollArea documentation.

Props

ScrollArea

Container with styled scrollbars and optional fade effects. Wraps Base UI's ScrollArea.Root.

Prop