Skip to content

PlProgressCircular

A ring that fills. The one to use where there is no room for a bar, inside a table row, beside a field, at the end of a line of text.

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

<PlProgressCircular label="Syncing" value={68} showValue />;
dart
import 'package:plass_ui/plass_ui.dart';

PlProgressCircular(label: const Text('Syncing'), value: 68, showValue: true);

Props

PropTypeDefaultDescription
valuenumber | nullnullHow far along, between min and max. null is the indeterminate case: something is happening and nobody knows how much of it is left
minnumber0The bottom of the range
maxnumber100The top of the range
labelReactNodeA name for what is loading. Read out with the value by a screen reader
showValuebooleanfalseShows the value as text beside the shape. A percentage of the range unless format says otherwise
formatIntl.NumberFormatOptionsHow 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'Diameter of the ring. Sits just under the control ladder at every step
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. It becomes the gradient of the filled part
PropTypeDefaultDescription
valuedouble?nullHow far along, between min and max. null is the indeterminate case: something is happening and nobody knows how much of it is left
mindouble0The bottom of the range
maxdouble100The top of the range
labelWidget?A name for what is loading. Read out with the value by a screen reader
showValueboolfalseShows the value as text beside the shape. A percentage of the range unless format says otherwise
formatValueString 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
sizesharedPlassSizePlassSize.mdDiameter of the ring. Sits just under the control ladder at every step
colorsharedPlassColorPlassColor.primarySemantic colour role. It becomes the gradient of the filled part

The table is PlProgressLinear's, and only size means something different: on a bar it is thickness, on a ring it is diameter. That is the claim the indicators make, one component in three shapes, and it is why they share a props table rather than three that would drift.

Every native <div> attribute passes straight through. color is excluded because it collides with the color in the table above, and children because a ring holds nothing.

formatValue is a function where React takes an options object, for the reason PlProgressLinear gives.

The arc

An SVG stroke cannot be given a CSS gradient, so the ring builds a <linearGradient> of its own out of the same two stops the bar's fill is made of, at the same 135°.

A stroke takes a Shader rather than a decoration, so the sweep the rest of the package gets from PlassCssGradient is asked for directly. The one place in the library a shader is built by hand.

Either way it is worth the extra work: a flat ring beside a swept bar is two materials for one idea.

The track under it is --plass-track, the same neutral ink the bar's groove is, so a ring and a bar on one screen are cut into the same surface.

The value label

Not inside it. A number in the middle of a dial is the picture everyone has of this component, and it works at two of the five sizes: at xs the ring is fourteen pixels across and there is nowhere for "40%" to go. Beside it, every size reads.

Examples

value

null, the default, is the indeterminate case. The ring then draws a fixed quarter-arc and turns, which is the one place the library moves something on its own, and the exception is the same one the button's spinner already has: an indeterminate indicator that holds still is a decoration.

With a value the ring holds still and the gap closes instead. Both are one dash pattern on one circle.

React

size

Diameter, on a ladder that sits just under the control ladder at every step, a md ring is 20px inside a 40px control, so a ring dropped into a button, a field or a table row never makes the row taller than it already was.

React

color

React

In a row

The size ladder is what this is for: an xs ring in a table cell is fourteen pixels, and the row is the height it was already going to be.

React

Accessibility

  • Base UI renders a role="progressbar" and keeps aria-valuenow, aria-valuemin and aria-valuemax in step with the props.
  • An indeterminate ring reports no value at all rather than zero, which is what tells a screen reader to announce indeterminate progress.
  • The <svg> is aria-hidden: it is the drawing, and everything it says is already in the role and the value.
  • aria-valuetext is the same string showValue draws. Without format that is a percentage of the range, not of 100.
  • Under prefers-reduced-motion the ring is slowed to where it stops reading as motion rather than stopped, for the reason it turns at all.
  • The ring is one merged semantics node carrying SemanticsRole.progressBar and its value, so the label and the ring are read together.
  • With no value the role is SemanticsRole.loadingSpinner and there is no value at all, which is what tells the platform to announce indeterminate progress.
  • The drawn percentage is behind ExcludeSemantics: the same string is already the node's value.
  • With MediaQuery.disableAnimations the ring is slowed rather than stopped.

Differences from the React build

ReactFlutterWhy
format: Intl.NumberFormatOptionsformatValue: 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.
an <svg> with a <linearGradient>a CustomPainter with a ui.Gradient shaderSame two stops, same 135°; a stroke takes a shader rather than a decoration.
className, style, native attributesThere is no class list and no style attribute to pass through.

Released under the MIT License