Scroll Area
A scroll container with propel's overlay scrollbar that reveals on hover or scroll, built on Base UI ScrollArea.
Show codeHide code
Installation
import { ScrollArea } from "@makeplane/propel/components/scroll-area";Usage
import { ScrollArea } from "@makeplane/propel/components/scroll-area";
const ACTIVITY = Array.from(
{ length: 24 },
(_, i) => `Astra updated the project roadmap — item ${i + 1}`,
);
export default function BasicDemo() {
// ScrollArea fills its parent, so it needs a height-constrained flex column to
// bound and scroll within. Inline styles keep the demo self-contained.
return (
<div style={{ display: "flex", flexDirection: "column", height: "16rem", width: "18rem" }}>
<ScrollArea orientation="vertical">
<div
style={{ display: "flex", flexDirection: "column", gap: "0.5rem", padding: "0.75rem" }}
>
{ACTIVITY.map((line) => (
<p key={line}>{line}</p>
))}
</div>
</ScrollArea>
</div>
);
}ScrollArea fills its parent (h-full w-full): place it in a flex column or any parent with a definite height (or a max-h cap) and the viewport scrolls once the content overflows.
Children render inside the viewport. Base UI’s optional ScrollArea.Content wrapper is not part of the ready-made — add it yourself only if you need that slot.
Examples
Horizontal
Show codeHide code
Both axes
Show codeHide code
Inset
variant="inset" reserves the scrollbar track as its own lane on each rendered axis (like scrollbar-gutter: stable), so the thumb never covers content and the content never shifts when it starts or stops overflowing. The lane is reserved even when nothing overflows. The default overlay floats the thumb over the content’s edge.
Show codeHide code
Always visible
Show codeHide code
Sizes
The size prop sets the scrollbar gutter (same pad as native scrollbar-*): sm (8 px / 2 → 4 px thumb), md (10 / 2 → 6, the default), lg (12 / 2 → 8).
Show codeHide code
Native scrollbars
Propel also ships opt-in utilities that re-skin the browser’s own scrollbar: scrollbar-sm, scrollbar-md, and scrollbar-lg (from @makeplane/propel/styles). Put the class on any overflow element — no ScrollArea required.
| Utility | Track | Inset | Visible thumb |
|---|---|---|---|
scrollbar-sm |
8px | 2px | 4px |
scrollbar-md |
10px | 2px | 6px |
scrollbar-lg |
12px | 2px | 8px |
Same size ladder as ScrollArea’s size prop. Thumb colors use --scrollbar-thumb* (hidden at rest, stronger on container hover / thumb hover / drag).
Use native utilities when you can’t wrap the region in ScrollArea (a textarea, or a third-party element that owns its own overflow) and can accept browser limits (Firefox: thinner control; no custom inset / per-thumb states).
Use ScrollArea for everything else, so the thumb looks and behaves the same in every browser (including Firefox and touch), with controlled show/hide (visibility) and both-axis rails + corner. Pick the variant by what sits under the edge:
insetfor panes whose content runs to the edge: settings and admin pages, lists with full-bleed row fills or right-aligned values, anything that previously used a nativescrollbar-*gutter.overlay(default) for compact floating surfaces (menus, popovers, comboboxes) where a reserved lane would waste width and the content already has padding.
<div className="scrollbar-sm max-h-96 overflow-y-auto">{/* … */}</div>API Reference
ScrollArea
A scroll container with propel's scrollbar, built on Base UI ScrollArea. Use it to wrap any
overflowing content (menus, panels, long lists). It fills its parent: inside a flex column it
grows to fill the column (flex-1), and inside any other parent with a definite height it takes
that height (h-full). Constrain the parent's height (or width), and the viewport scrolls when
the content overflows. variant picks whether the scrollbar overlays the content or keeps its
own lane.