Skip to content

PlScrollArea

A bounded box that scrolls, with the library's own scrollbar in it. The reason to use it over overflow: auto is the bar: a platform scrollbar is either an overlay that vanishes or a strip of grey furniture, and neither belongs beside a translucent sheet.

React
tsx
import { PlScrollArea } from 'plass-ui';

<PlScrollArea height={200} label="Release notes">
  <ul>…</ul>
</PlScrollArea>;
dart
import 'package:plass_ui/plass_ui.dart';

PlScrollArea(
  height: 200,
  label: 'Release notes',
  child: Column(children: notes),
);

Props

PropTypeDefaultDescription
orientation'vertical' | 'horizontal' | 'both''vertical'Which axes may scroll. both draws a lane on each edge and a corner where they meet
height · maxHeightnumber | stringA fixed height, or a ceiling it shrinks under. A vertical scroll area has to be bounded by something
width · maxWidthnumber | stringThe same pair for a horizontal area
scrollbars'auto' | 'always''auto'When the lanes are drawn. auto is while the pointer is over the box or the content is moving. Either way the content loses no width
labelstringA name for the region. A scrollable box is a tab stop even when nothing inside it is focusable, and a landing point with no name is announced as nothing
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Thickness of the scrollbars and the corner the box is cut to
classNames{ viewport, scrollbar, thumb }Classes on the parts a className does not reach
childrenReactNodeWhat scrolls
PropTypeDefaultDescription
child * WidgetWhat scrolls
orientationPlScrollAreaAxisPlScrollAreaAxis.verticalWhich axes may scroll. both draws a lane on each edge and a corner where they meet
height · maxHeightdouble?A fixed height, or a ceiling it shrinks under. A vertical scroll area has to be bounded by something
width · maxWidthdouble?The same pair for a horizontal area
scrollbarsPlScrollbarsPlScrollbars.autoWhen the lanes are drawn. auto is while the pointer is over the box or the content is moving. Either way the content loses no width
labelString?A name for the region. A scrollable box is a tab stop even when nothing inside it is focusable, and a landing point with no name is announced as nothing
sizesharedPlassSizePlassSize.mdThickness of the scrollbars and the corner the box is cut to

What the shared axes mean across the library is in prop conventions.

Bound it, or nothing scrolls

A vertical scroll area has to be bounded by something, or there is nothing for the content to overflow and the box simply grows to fit. height is that something, and it is a prop rather than a class or an enclosing box because it is the one measurement without which the component does nothing at all.

maxHeight is the other shape of the same answer: a ceiling rather than a size, for a panel that should shrink to short content and only start scrolling once there is too much. width and maxWidth are the pair for a horizontal area.

A number is pixels and a string is any CSS length, so height={200} and height="40vh" both work.

PlScrollArea or PlScrollZone

Two components, one fact (content that runs off the end of its box), and two different answers.

PlScrollAreaKeeps the bar, and makes it the library's own. For a panel of content, where a reader wants to know how far through they are.
PlScrollZoneTakes the bar away, fades the end that still has something behind it, and adds a pair of buttons. For a strip (a row of tabs, chips, filters) where a bar under one line of labels is heavier than the labels.

There is deliberately no fade here. A fade says "there is more"; the bar says that and how much and where you are. Two signals for one fact, one measured and one not, is one more than the box needs.

Axes

orientation is vertical by default, horizontal for a row, and both for a grid that runs off two edges, where a lane is drawn along each and a corner fills the join.

React

both is two scrollables, one nested inside the other, and each bar answers only its own. A widget that let a horizontal bar move when the page scrolled down would be reporting the wrong axis.

scrollbars

auto draws the lane while the pointer is over the box or the content is moving, and nothing otherwise. That is what a reader is used to and it is the default.

always holds it open, and it is the right choice more often than it looks: for a panel whose whole point is that there is more below, a bar that only appears on hover is a signal nobody standing back from the screen ever sees. Turning it on costs the content no width either way. The lane is overlaid, not laid out.

Examples

A dialog body that scrolls while the header stays

The ordinary case. Bound the middle and leave the two ends where they are.

tsx
<PlModal title="Terms">
  <PlScrollArea maxHeight="60vh" label="Terms of service">
    <div className="pe-3">{terms}</div>
  </PlScrollArea>
</PlModal>

A sidebar of its own

tsx
<PlScrollArea height="100%" label="Projects" size="sm">
  <PlList>…</PlList>
</PlScrollArea>

Notes

  • The thumb is --plass-track, the same neutral ink a slider's rail and a progress groove are cut in. One material for every channel in the library.
  • The lane is overlaid, so showing or hiding it never reflows what is underneath.
  • The box is cut to the size step of the house radius, and the content is clipped to it.
  • The viewport is overscroll-contain: reaching the bottom of the panel does not start scrolling the page behind it.
  • Base UI owns the behaviour. The overlay measurement, the thumb's size and position, the drag, and making the viewport a tab stop exactly while there is something to scroll.
  • classNames reaches the parts a className does not: viewport, scrollbar, thumb.
  • Built on RawScrollbar from package:flutter/widgets.dart. The framework's own Scrollbar lives in material.dart, which this package does not import.

Accessibility

  • A scrollable box is a tab stop when nothing inside it is focusable, because somebody using a keyboard has to be able to scroll it. That is handled for you, and it is the reason label matters: a landing point with no name is announced as nothing at all.
  • With a label the box becomes a named region. Without one it claims no landmark, deliberately: an unnamed region is something a screen reader lists as "region" and nothing else, which is worse than no landmark at all.
  • The scrollbar is not the only way to move: the arrow keys, Page Down and the wheel all work on the box itself.

Released under the MIT License