Scroll Area
A scrolling region with Koala's own scrollbar: an ink pill that overlays the content, shows while you are over it, fades rather than blinks, and looks the same on every OS and theme. The scrolling itself stays native.
Installation#
Usage#
import { ScrollArea } from "@/components/ui/scroll-area"
export function Example() {
return (
<ScrollArea className="h-72">
{/* anything taller than 288px */}
</ScrollArea>
)
}Give the root a height (h-72, max-h-96, or flex-1 min-h-0 in a flex column) and the viewport fills it. className styles the root; the viewport inherits its radius, so a rounded frame clips the content and the focus ring to its corners.
Edge fade#
fade dissolves the edge that still has content beyond it, so the region says there is more before anyone hovers it. It is the scroll-fade utility, pure CSS: the edge you are scrolling toward fades, and the one you reach sharpens.
Horizontal#
orientation="horizontal" draws the bar along the bottom and lets the content grow to its own width (w-max on the row). The photos sit 8px inside the frame, on 8px corners under its 16px ones, each with the thin image outline.
Drag with the mouse#
draggable lets a mouse pull the content the way a finger already does: press, drag, let go and it glides on. For a strip of cards or photos, where people reach for the content rather than the bar. A press that barely moves is still a click, and a real drag swallows the click it ends on, so a card link never opens by accident. Touch and pen keep their native scroll, and the glide is skipped under reduced motion.
Both axes#
orientation="both" draws both bars and a corner where they meet, for content wider and taller than its frame: a year of figures, a schedule, a canvas.
When the bar shows#
type is Radix's: hover (the default) shows the bar while the pointer is over the region, scroll only while it is scrolling, and always whenever the content overflows. However it appears, it fades in and out, and it thickens a step when you reach for it, the way a native overlay bar does.
The viewport#
viewportRef is the element that actually scrolls: read scrollTop, call scrollTo, or observe it. Here it drives a button that appears once you are away from the top and takes you back smoothly.
Focus rings#
The viewport carries scroll-p-2: when Tab (or a focus()) scrolls a control into view, the browser stops 8px short of the edge rather than flush against it, so the control's focus ring (2px on a 2px offset) is never cut by the viewport. It changes where focus and scrollIntoView land, never the layout, and Chromium 102 honors it. The first and last controls sit against the content's own edge, which scroll padding cannot move, so give the content a little padding (p-2 here). To change the amount, target the viewport: [&>[data-slot=scroll-area-viewport]]:scroll-p-4.
API reference#
ScrollArea
The root, viewport, bars and corner in one, over Radix ScrollArea.Root; give it a height and the viewport fills it. Forwards the Radix root props and accepts className (on the root), merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | "vertical" | "horizontal" | "both" | "vertical" | Which way the content scrolls, and so which bars draw. A vertical-only area unwraps Radix's table box, so truncate works inside. |
| fade | boolean | false | Fades the edge that still has content beyond it (the scroll-fade utility; vertical edges when both). |
| draggable | boolean | false | Lets a mouse pull the content, with a glide on release. A press that barely moves is still a click; touch and pen scroll natively. |
| viewportRef | React.Ref<HTMLDivElement> | - | A ref to the scrolling element itself, to read scrollTop or call scrollTo. |
| type | "hover" | "scroll" | "always" | "auto" | "hover" | When the bar shows: while hovered, while scrolling, always, or whenever the content overflows. |
| scrollHideDelay | number | 600 | Milliseconds before the bar fades after the pointer leaves or scrolling stops. |
| dir | "ltr" | "rtl" | - | Reading direction, which sets the side of the vertical bar. |
| Data attribute | Description |
|---|---|
| data-state | visible or hidden, on each scrollbar; it fades between them. |
| data-orientation | vertical or horizontal, on each scrollbar. |
| data-overflowing | On the viewport with draggable, while the content overflows (shows the grab cursor). |
| data-dragging | On the viewport with draggable, while a mouse pull is live. |