Scroll Area
The ScrollArea component offers a flexible, customizable container with scrollbars for overflowing content. Use it to display content that exceeds the visible area, supporting both horizontal and vertical scrolling.
Variations
Directions
The ScrollArea component supports the direction prop to set the text direction. It accepts ltr (left-to-right) and rtl (right-to-left).
Scroll events
The ScrollArea component provides several event handlers to respond to scroll actions:
onScrollTop: Triggered when the scroll area is scrolled to the top.onScrollBottom: Triggered when the scroll area is scrolled to the bottom.onScrollLeft: Triggered when the scroll area is scrolled to the left.onScrollRight: Triggered when the scroll area is scrolled to the right.
Each handler receives an IntersectionObserverEntry describing the position and visibility of the edge sentinel.
rootMargin and threshold customize when the events fire. Use rootMargin to adjust the offset around the scroll area, and threshold to define how much of the target should be visible before triggering the scroll event.
Installation
import { ScrollArea } from '@harnessio/ui/components';
//...
return ( <ScrollArea className="w-[300px] h-[200px] border"> <div className="p-cn-md space-y-cn-md w-[700px]"> <p>Content that exceeds the viewport height and width.</p> <p>More content...</p> <p>Even more content...</p> </div> </ScrollArea>);Anatomy
ScrollArea wraps your content in a scrolling viewport. Give the viewport a fixed size (via className) and let the inner content overflow — the component handles the scrollbars and, optionally, edge-detection callbacks.
<ScrollArea className="w-[300px] h-[200px]"> {/* overflowing content */}</ScrollArea>- The outer element is the scroll viewport;
classNamestyles it andclassNameContentstyles the inner content wrapper. - Edge callbacks (
onScrollTop/onScrollBottom/onScrollLeft/onScrollRight) are backed byIntersectionObserversentinels placed at each edge; a sentinel is only rendered when its callback is provided. - Pass
preserveScrollPositionwith astorageKeyto persist and restore the vertical scroll position across mounts.
Best practices
Do
- Constrain the viewport with an explicit width/height so there’s something to scroll within.
- Use the edge callbacks for infinite-scroll or “load more” behavior —
onScrollBottomis the usual hook for fetching the next page. - Tune
rootMargin/thresholdwhen you want to fetch slightly before the user actually reaches the edge.
Don’t
- Don’t attach an edge callback you don’t use — the sentinel and observer are only created for callbacks you pass.
- Don’t rely on it for the whole-page scroll; it’s for bounded, overflowing regions.
Accessibility
Focus & states
ScrollArearenders plain scrollable containers (it isn’t built on a Radix scroll primitive) and doesn’t add ARIA roles. Native keyboard scrolling applies once the region is focused.
Known limitations
- Because no
roleor label is applied, give the region an accessible name in surrounding markup if its purpose isn’t clear from context, and make sure keyboard users can reach the scrollable content.
Properties
ScrollArea
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| direction | false | 'ltr' | 'rtl' | |
| className | false | string | |
| classNameContent | false | string | |
| onScrollTop | false | (entry: IntersectionObserverEntry) => void | |
| onScrollBottom | false | (entry: IntersectionObserverEntry) => void | |
| onScrollLeft | false | (entry: IntersectionObserverEntry) => void | |
| onScrollRight | false | (entry: IntersectionObserverEntry) => void | |
| rootMargin | false | '0px' | string | { top?: string; right?: string; bottom?: string; left?: string; } |
| threshold | false | 0.1 | number | number[] |
| preserveScrollPosition | false | false | boolean |
| storageKey | false | string |
The useScrollArea hook is also exported for advanced cases where you need to drive the viewport programmatically.