Scroll Area

A box with content that scrolls inside it, with thin scrollbars that look the same in every browser.

import {
  ScrollArea,
  ScrollAreaContent,

When to use it

Use a scroll area for a part of a page that has its own height and holds more than fits: a list of releases in a side panel, a log, a row of categories, the options inside a panel. The rest of the page stays where it is.

Do not wrap the whole page in one. The browser already scrolls the page well, and a scroll area inside another scroll area is hard to use, especially on a phone. Long lists of options in a Select or a Combobox already scroll by themselves.

Import

import {
  ScrollArea,
  ScrollAreaViewport,
  ScrollAreaContent,
  ScrollAreaScrollbar,
  ScrollAreaThumb,
  ScrollAreaCorner,
} from "@hel/ui/scroll-area"

Anatomy

ScrollArea is the box, and it needs a height (or a width, for a horizontal one). The ScrollAreaViewport is what scrolls, with the ScrollAreaContent inside it. Each scrollbar is a ScrollAreaScrollbar with a ScrollAreaThumb in it, which is the part people drag.

<ScrollArea className="h-64 w-72">
  <ScrollAreaViewport>
    <ScrollAreaContent>...</ScrollAreaContent>
  </ScrollAreaViewport>
  <ScrollAreaScrollbar>
    <ScrollAreaThumb />
  </ScrollAreaScrollbar>
</ScrollArea>

The scrollbars stay out of sight until someone hovers the box or scrolls, so they do not take room from the content. The viewport can be focused with the keyboard, so people who cannot use a mouse can scroll it with the arrow keys and Page Up and Page Down.

Examples

Horizontal

Use orientation="horizontal" on the scrollbar, and make the content as wide as it needs to be, with w-max on the row inside. It suits a row of filters or chips that is too long for a narrow screen.

import { Badge } from "@hel/ui/badge"
import { Flex } from "@hel/ui/flex"
import {

Both directions

When the content is wider and taller than the box, add one scrollbar for each direction, and a ScrollAreaCorner for the small square where they meet. A log or a block of code is the usual case.

import {
  ScrollArea,
  ScrollAreaContent,

Plain and fill

The default box has a border, a background and rounded corners. variant="plain" removes them, so the scroll area can sit inside a card or a panel that already has its own. fill makes the box take the height that is left in a parent that is a flex column or has a fixed height, which is how a side panel gets a list that scrolls below its header.

Props

ScrollArea

PropTypeDefault
variant"default" | "plain""default"
fillbooleanfalse

It also takes every prop of the Base UI scroll area.

ScrollAreaScrollbar

PropTypeDefault
orientation"vertical" | "horizontal""vertical"
keepMountedbooleanfalse