Skip to content
UI

Scroll Area

A scrolling region with a thin scrollbar that looks the same on every platform.

@zenoxai/ui/components/scroll-areaBase UI Markdown
function ScrollAreaDemo() {
  const domains = Array.from(
    { length: 30 },
    (_, index) => `acme-${String(index + 1).padStart(2, "0")}.example.com`
  )

  return (
    <ScrollArea className="h-72 w-64 rounded-md border border-border">
      <div className="p-4">
        <div className="mb-3 text-sm font-medium text-text-primary">
          Monitored domains
        </div>
        {domains.map((domain) => (
          <div key={domain}>
            <div className="py-1.5 font-mono text-sm text-text-secondary">
              {domain}
            </div>
            <Separator />
          </div>
        ))}
      </div>
    </ScrollArea>
  )
}

Usage

import { ScrollArea } from "@zenoxai/ui/components/scroll-area"
<ScrollArea className="h-72 rounded-md border border-border">
  {content}
</ScrollArea>

Use a scroll area for a region with a fixed size inside a page: a list in a card, a log panel, a popover with many options. Give it a height (or a width, for horizontal content); without one there is nothing to scroll. The page itself scrolls natively and needs no scroll area.

ScrollArea renders a vertical scrollbar. The scrollbar appears only when the content overflows.

Examples

Horizontal

Add <ScrollBar orientation="horizontal" /> as the last child for content wider than the area. Give the content a width it cannot shrink below, such as w-max.

function ScrollAreaHorizontal() {
  const ports = [
    { port: 22, service: "SSH" },
    { port: 80, service: "HTTP" },
    { port: 443, service: "HTTPS" },
    { port: 3306, service: "MySQL" },
    { port: 3389, service: "RDP" },
    { port: 5432, service: "PostgreSQL" },
    { port: 6379, service: "Redis" },
    { port: 8080, service: "HTTP proxy" },
    { port: 9200, service: "Elasticsearch" },
  ]

  return (
    <ScrollArea className="w-full max-w-md rounded-md border border-border">
      <div className="flex w-max gap-3 p-4">
        {ports.map(({ port, service }) => (
          <div
            key={port}
            className="flex w-28 shrink-0 flex-col gap-1 rounded-md bg-surface-muted p-3"
          >
            <span className="font-mono text-sm font-medium text-text-primary">
              {port}/tcp
            </span>
            <span className="text-xs text-text-muted">{service}</span>
          </div>
        ))}
      </div>
      <ScrollBar orientation="horizontal" />
    </ScrollArea>
  )
}

Scrollbar on hover

While the content overflows, the scrollbar stays visible. On a panel that is always on screen, fade it in on hover and while scrolling with scrollbarClassName and the data-hovering and data-scrolling attributes.

function ScrollAreaFadingScrollbar() {
  const events = Array.from(
    { length: 20 },
    (_, index) =>
      `10:${String(59 - index).padStart(2, "0")} Login from 203.0.113.${index + 10}`
  )

  return (
    <ScrollArea
      className="h-56 w-72 rounded-md border border-border"
      scrollbarClassName="opacity-0 transition-opacity data-hovering:opacity-100 data-scrolling:opacity-100"
    >
      <ul className="flex flex-col gap-2 p-4 font-mono text-sm text-text-secondary">
        {events.map((event) => (
          <li key={event}>{event}</li>
        ))}
      </ul>
    </ScrollArea>
  )
}

Accessibility

  • The scrolling element takes keyboard focus when its content overflows, so keyboard users can scroll it with the arrow keys, Page Up and Page Down. It shows a focus ring.
  • Scrolling stays native: wheel, touch, find-in-page and screen readers work as in any overflowing element.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

ScrollArea

ScrollArea is a scrolling region with a thin themed scrollbar in place of the browser's, for a panel of fixed size such as a sidebar list or a popover body. Let the page itself scroll natively.

PropTypeDefault
overflowEdgeThresholdnumber | Partial<{ xStart: number; xEnd: number; yStart: number; yEnd: number; }>

The threshold in pixels that must be passed before the overflow edge attributes are applied. Accepts a single number for all edges or an object to configure them individually.

0
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ScrollAreaRootState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
scrollbarClassNamestring

Classes for the scrollbar track. Use it to size the bar or to fade it in on `data-hovering` / `data-scrolling` — the primitive keeps the track visible at all times, which reads as clutter on a permanently mounted surface.

-

ScrollBar

ScrollBar is a scrollbar track and thumb. ScrollArea renders a vertical one; add a ScrollBar with `orientation="horizontal"` inside it for sideways scrolling.

PropTypeDefault
keepMountedboolean

Whether to keep the HTML element in the DOM when the viewport isn't scrollable.

false
orientation"horizontal" | "vertical"

Whether the scrollbar controls vertical or horizontal scroll.

vertical
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ScrollAreaScrollbarState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-