Skip to content
Navigation

Scroll Area

A scroll container with styled, token-coloured scrollbars. Use it when a region scrolls independently of the page — not as a wrapper around ordinary page content.

Added 14 October 2026

When to use it

Reach for a scroll area when a region has its own scroll context: a long option list beside a map, a site list inside a fixed-height panel, a table of segments in a card. It needs a bounded height (or width) from you — className="h-56", class="h-56" — otherwise there is nothing to scroll.

For the page itself, or any region that should scroll with the page, use plain overflow. A scroll area nested inside another scroll area is almost always a layout bug.

Axes

One prop on both sides. React takes orientation on the component; Angular passes it to <ng-scrollbar>:

<ScrollArea label="Segments" className="h-56 w-56 border border-border">…</ScrollArea>
<ScrollArea orientation="horizontal" label="Cities" className="max-w-md border border-border">…</ScrollArea>
<ng-scrollbar hlmScrollArea label="Segments" class="h-56 w-56 border border-border">…</ng-scrollbar>
<ng-scrollbar hlmScrollArea orientation="horizontal" label="Cities" class="max-w-md border border-border">…</ng-scrollbar>

orientation accepts vertical (default), horizontal or both in React. Do not nest a ScrollBar as a child to add an axis — children go inside the scrolling viewport, so a bar placed there scrolls away with the content. ScrollBar is exported only for consumers composing the Base UI primitive directly.

The component supplies its own rounded-md; add the border yourself when the region needs a visible edge.

Accessibility

  • Pass label. Both frameworks make the scroll region a keyboard tab stop when (and only when) it actually overflows, and expose it as role="region". Without a name it announces as an unlabelled region and a keyboard user has no idea why focus landed there or that arrow keys will scroll. [a11y]
  • The thumb is --muted-foreground, not --border. That is a deliberate deviation from shadcn: a draggable control needs 3:1 contrast (WCAG 1.4.11) and the hairline --border token is around 1.17:1. Don’t “restore” it to bg-border for looks.
  • Content inside the region must not rely on the scrollbar to be discoverable — if there is more below, the content itself should suggest it (a partially visible row beats a bar that is only there on hover).

Framework differences worth knowing

React Angular
Machinery Base UI ScrollArea primitive ngx-scrollbar (brain ships no scroll-area)
Bar visibility always present while scrollable revealed on hover / while scrolling
data-slot hooks root, viewport, scrollbar, thumb root only

The bar visibility difference is real and unreconciled — the two showcase panes will not look identical at rest. Design for the hover-revealed case if the app must work in both.

Angular consumers must have ngx-scrollbar installed. It is an optional peer, which here means “React consumers never need it, Angular consumers always do” — the ./angular bundle imports it at top level, so it is effectively required for anyone importing from @stroeer-labs/design-library/angular.

Customising the bar

The Angular half is themed through CSS custom properties (that library exposes no class surface), and they are set as utilities so you can override any of them:

<ng-scrollbar hlmScrollArea class="[--scrollbar-track-thickness:1rem]">…</ng-scrollbar>

Use tokens, never literals — [--scrollbar-thumb-color:var(--primary)], not a hex. On the React side style the thumb through ScrollBar’s className instead.

Never

  • A scroll area with no height/width constraint — it will never scroll.
  • A scroll area around the whole page.
  • A horizontal scroll area for a data table; give the table its own overflow so the header can stay put.
  • A ScrollBar child to add an axis — use orientation.