PlProgressLinear
A bar that fills. The one indicator that can show how much is left at a glance, because length is the one quantity a reader can compare without counting.
import { PlProgressLinear } from 'plass-ui';
<PlProgressLinear label="Uploading" value={62} showValue />;import 'package:plass_ui/plass_ui.dart';
PlProgressLinear(label: const Text('Uploading'), value: 62, showValue: true);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | null | null | How far along, between min and max. null is the indeterminate case: something is happening and nobody knows how much of it is left |
| min | number | 0 | The bottom of the range |
| max | number | 100 | The top of the range |
| label | ReactNode | — | A name for what is loading. Read out with the value by a screen reader |
| showValue | boolean | false | Shows the value as text beside the shape. A percentage of the range unless format says otherwise |
| format | Intl.NumberFormatOptions | — | How to format the value when it is shown — Intl.NumberFormat options, so bytes and currencies work as well as plain numbers |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Thickness of the groove. Nothing else on a bar has a size |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. It becomes the gradient of the filled part |
| Prop | Type | Default | Description |
|---|---|---|---|
| value | double? | null | How far along, between min and max. null is the indeterminate case: something is happening and nobody knows how much of it is left |
| min | double | 0 | The bottom of the range |
| max | double | 100 | The top of the range |
| label | Widget? | — | A name for what is loading. Read out with the value by a screen reader |
| showValue | bool | false | Shows the value as text beside the shape. A percentage of the range unless format says otherwise |
| formatValue | String Function(double value)? | — | How to write the value, as a function rather than React's options object: there is no Intl.NumberFormat in the framework, and pulling package:intl in to provide one would be a dependency decision made on the consumer's behalf |
| sizeshared | PlassSize | PlassSize.md | Thickness of the groove. Nothing else on a bar has a size |
| colorshared | PlassColor | PlassColor.primary | Semantic colour role. It becomes the gradient of the filled part |
Every native <div> attribute passes straight through. color is excluded because it collides with the color in the table above, and children because a bar holds nothing.
formatValue is a function where React takes an options object, and it is the one prop that could not cross: there is no Intl.NumberFormat in the framework to hand options to, and a package that pulled package:intl in to provide one would be making a dependency decision on its consumer's behalf. Whatever formats numbers in the app already can format this one.
There is no variant, no density and no elevation. An indicator is one material, it has nothing to pad, and it is cut into the surface it sits on the way a groove is, and a groove does not float.
What the shared axes mean across the library is in prop conventions.
Composition
The groove is --plass-track, the same neutral ink a PlSlider's rail and a PlSwitch's off state are cut in, so a form with a slider, a switch and a progress bar in it is made of one material rather than three.
The segment over it is the family's gradient, which means the filled part of the run is exactly the material the button that submits the form is made of. It is also why the movement is on width: a gradient cannot be transitioned, and a length can.
Both the groove and the segment are fully rounded, and that is the one place the house rule about pills does not apply. The rule protects the flat run along a control's edge that a line of text sits on; at six pixels tall there is no flat run left to protect, and a square-ended bar reads as a rendering fault rather than as a cut edge.
Examples
value
null, the default, is the indeterminate case: something is happening and nobody knows how much of it is left. A bar with no value sweeps rather than sitting empty, because an empty bar is a claim that no progress has been made.
A value outside min…max is clamped rather than drawn: value usually arrives from a division somewhere, and a bar that renders 140% wide because one request finished twice is a worse bug than a bar that sits full.
size
Thickness only. A bar is not a control you can put a label inside, and at md it wants to be the weight of a rule between two paragraphs rather than a quarter of a button, so these are PlSlider's rail thicknesses, deliberately: a rail and a bar are the same channel, one of which you drag and one of which you watch.
color
showValue and format
Without format the value is written as a percentage of min…max, which is the only formatting that holds for a range nobody described. "3%" for step 3 of 4 is worse than saying nothing.
With it, the number goes straight to Intl.NumberFormat, so bytes, currencies and units all work and the value keeps whatever meaning the caller gave it.
Accessibility
- Base UI renders a
role="progressbar"and keepsaria-valuenow,aria-valueminandaria-valuemaxin step with the props. - An indeterminate bar reports no value at all rather than zero, which is what tells a screen reader to announce indeterminate progress.
aria-valuetextis the same stringshowValuedraws, so what is heard and what is read are one sentence. Withoutformatthat is a percentage of the range, not of 100.labelnames what is loading. A bar with no label is a bar a screen reader can only describe as a number.- Under
prefers-reduced-motionthe segment stops travelling, fills the groove and breathes instead. It is not stopped: an indeterminate indicator that holds still says the opposite of what it is for.
- The bar is one merged semantics node carrying
SemanticsRole.progressBarand its value, so the label and the bar are read together rather than as a name floating beside an unnamed indicator. - With no value the role is
SemanticsRole.loadingSpinnerand there is no value at all, which is what tells the platform to announce indeterminate progress rather than zero. - The drawn percentage is behind
ExcludeSemantics: the same string is already the node's value, and it should be heard once. - With
MediaQuery.disableAnimationsthe segment stops travelling, fills the groove and breathes instead, the same stand-in, on the same axis.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
format: Intl.NumberFormatOptions | formatValue: String Function(double) | There is no Intl.NumberFormat in the framework, and pulling package:intl in to provide one would be a dependency decision made on the consumer's behalf. |
label: ReactNode, and min/max/value are number | Widget? and double | Dart's own names for the same things. |
the segment travels on inset-inline-start | it travels on a directional Alignment | Neither is a transform, and both run the other way under RTL without being told. |
className, style, native attributes | — | There is no class list and no style attribute to pass through. |