Scroll area
A scroll container with a slim 40% tint thumb that reads on both canvases, in place of the browser's scrollbar inside small panels. Native scrolling, keyboard and touch are kept. Source: src/components/ui/scroll-area.tsx.
Vertical
Give the root a fixed height. The thumb appears on hover and while scrolling; scroll the list to see it.
Horizontal
ScrollArea always mounts a vertical bar. For a sideways row, put a ScrollBar orientation="horizontal" inside the children and give the row w-max.
Reaching the viewport
viewportRef and viewportProps expose the element that actually scrolls. Here onScroll drives a readout and the button scrolls to the end, as the thread view does.
0% scrolled
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| viewportRef | Ref<HTMLDivElement> | — | ScrollArea. A ref to the scrolling viewport, for measuring or scrollTo. |
| viewportProps | ComponentProps<typeof ScrollArea.Viewport> | — | ScrollArea. Spread onto the viewport: onScroll, tabIndex, aria-*, and a className merged after the primitive's. |
| type | "hover" | "scroll" | "auto" | "always" | "hover" | ScrollArea (Radix). When the bar shows: on hover, while scrolling, on overflow, or always. |
| orientation | "vertical" | "horizontal" | "vertical" | ScrollBar. ScrollArea mounts a vertical bar itself; add a horizontal one as a child. |
| ...props | ComponentProps<typeof ScrollArea.Root> | — | Native attributes pass through; className merges last. Give the root a height (or max-h) so it can overflow. |
Best practices
When to use
- Small, bounded panels inside a page: a list in a popover, a log, a sidebar section.
- Never for the page itself. The document scrolls with the browser's own bar.
Behavior
- The root needs a height or max-height; without one it grows with its content and never scrolls.
- Hint at more content: let the last row be cut off by the edge rather than ending flush.
- Respect reduced motion when you scroll programmatically: use behavior "auto" instead of "smooth".
Accessibility
- If nothing inside is focusable, pass tabIndex: 0 and an aria-label through viewportProps so keyboard users can scroll it. The viewport shows the focus ring.
- Don't trap content that matters only behind a scroll; key actions stay outside the scroller.
Content
- Lists, logs and rows. Long prose reads better at full height on the page.