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.
import { PlScrollArea } from 'plass-ui';
<PlScrollArea height={200} label="Release notes">
<ul>…</ul>
</PlScrollArea>;import 'package:plass_ui/plass_ui.dart';
PlScrollArea(
height: 200,
label: 'Release notes',
child: Column(children: notes),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | 'vertical' | 'horizontal' | 'both' | 'vertical' | Which axes may scroll. both draws a lane on each edge and a corner where they meet |
| height · maxHeight | number | string | — | A fixed height, or a ceiling it shrinks under. A vertical scroll area has to be bounded by something |
| width · maxWidth | number | string | — | The 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 |
| label | string | — | 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 |
| 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 |
| children | ReactNode | — | What scrolls |
| Prop | Type | Default | Description |
|---|---|---|---|
| child * | Widget | — | What scrolls |
| orientation | PlScrollAreaAxis | PlScrollAreaAxis.vertical | Which axes may scroll. both draws a lane on each edge and a corner where they meet |
| height · maxHeight | double? | — | A fixed height, or a ceiling it shrinks under. A vertical scroll area has to be bounded by something |
| width · maxWidth | double? | — | The same pair for a horizontal area |
| scrollbars | PlScrollbars | PlScrollbars.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 |
| label | String? | — | 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 |
| sizeshared | PlassSize | PlassSize.md | Thickness 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.
PlScrollArea | Keeps 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. |
PlScrollZone | Takes 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.
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.
<PlModal title="Terms">
<PlScrollArea maxHeight="60vh" label="Terms of service">
<div className="pe-3">{terms}</div>
</PlScrollArea>
</PlModal>A sidebar of its own
<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
sizestep 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.
classNamesreaches the parts aclassNamedoes not:viewport,scrollbar,thumb.
- Built on
RawScrollbarfrompackage:flutter/widgets.dart. The framework's ownScrollbarlives inmaterial.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
labelmatters: a landing point with no name is announced as nothing at all. - With a
labelthe 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.