Scroll area
Native scrolling with a minimal scrollbar, edges that fade only where there’s more to see, and an optional peek that cuts the last item in half.
pnpm dlx shadcn@latest add https://hextaui.com/r/scroll-area.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react class-variance-authority cnCopy and paste the following code into your project.
components/ui/scroll-area.tsx Update the import paths to match your project setup.
Give the scroll area a fixed height or width. Content beyond it scrolls with the native scroll behavior, and a thin scrollbar appears while you hover or scroll.
Peek
Both lists have the same height. With peek, on the right, the box trims itself so the last visible item is cut partway through, which shows there is more to scroll before anyone tries. It never trims the box below half its height.
Text
Edges fade only where there is more content to scroll to. At the top only the bottom fades, and the fade grows in as you scroll away from an edge.
Start at the bottom
viewportRef gives you the scrolling element. Set its scrollTop to start a chat at the latest message, so only the top edge fades.
Horizontal
Set scrollbars="horizontal" and give the content w-max so it can grow past the box.
Both axes
scrollbars="both" shows both scrollbars and a corner where they meet, and fades all four edges.
Dynamic content
Peek and the fades update as items are added or removed. With too few items to scroll, the box keeps its height and nothing fades.
On a colored surface
The fade is a mask on the content, not an overlay color, so it works on any background.
Inside a sheet
Inside a flex column, wrap it in an element with min-h-0 flex-1 and give the scroll area h-full so it fills the remaining space.
Without fade
Turn the edge fades off with fade={false}.
Right to left
Content starts at the right edge, the scrollbar and fades follow the reading direction, and the direction is picked up from the page.
The viewport joins the tab order only when it has something to scroll. Once focused, it scrolls with the browser’s native keys.
| Key | Action |
|---|---|
| Tab | Focuses the viewport when its content overflows. |
| ↑↓ | Scrolls vertically. |
| ←→ | Scrolls horizontally. |
| Page UpPage DownSpace | Scrolls by a page. |
| HomeEnd | Jumps to the start or end. |
- Give the scroll area an
aria-labelwhen its purpose isn’t clear from the surrounding content, like the “Messages” label in the chat example. - The focus ring is drawn on the scroll area itself, so it stays visible above the faded edges.
Built on the Base UI scroll area. ScrollArea renders the root, viewport, content and scrollbars together.
| Prop | Type | Default |
|---|---|---|
scrollbars | "vertical" | "horizontal" | "both" | "vertical" |
fadeFade the edges that have more content. | boolean | true |
peekTrim the height so the last visible item is cut partway. Mark items with data-scroll-area-item to choose which elements count. | boolean | false |
viewportRefThe element that scrolls. | Ref<HTMLDivElement> | – |
overflowEdgeThresholdPixels to scroll before an edge counts as overflowing. | number | { xStart, xEnd, yStart, yEnd } | 0 |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="scroll-area" | The root. |
data-slot="scroll-area-viewport" | The element that scrolls. |
data-slot="scroll-area-content" | Wraps your content inside the viewport. |
data-peek | Present when peek is on. |
data-peeking | Present while peek is trimming the height. |
data-scrolling | Present while the user scrolls. |
data-has-overflow-x | Present when content is wider than the viewport. |
data-has-overflow-y | Present when content is taller than the viewport. |
data-overflow-y-start | Present when there is more content above. Matching -y-end, -x-start and -x-end attributes exist for the other edges. |
--scroll-area-fade | Size of the edge fade. Defaults to 2.5rem. |
--scroll-area-overflow-y-start | Distance from the top edge in pixels. Matching -y-end, -x-start and -x-end variables exist. |
Rendered for you by ScrollArea. Export it only if you compose the Base UI parts yourself.
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "vertical" |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="scroll-area-scrollbar" | The scrollbar track. |
data-slot="scroll-area-thumb" | The draggable thumb. |
data-orientation | vertical or horizontal. |
data-hovering | Present while the pointer is over the area. |
data-scrolling | Present while the user scrolls. |
--scroll-area-thumb-height | The thumb’s height. |
--scroll-area-thumb-width | The thumb’s width. |