PlSkeleton
The shape of something that has not loaded yet. It reserves the space the real thing will take, which is the whole job. A spinner cannot do that.
import { PlSkeleton } from 'plass-ui';
<PlSkeleton lines={3} label="Loading the article" />;
<PlSkeleton shape="circle" />;
<PlSkeleton shape="rect" height={120} />;import 'package:plass_ui/plass_ui.dart';
const PlSkeleton(lines: 3, label: 'Loading the article');
const PlSkeleton(shape: PlSkeletonShape.circle);
const PlSkeleton(shape: PlSkeletonShape.rect, height: 120);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| shape | 'line' | 'rect' | 'circle' | 'line' | What the placeholder is standing in for. line is a run of text, rect a block, circle an avatar. Each is sized off the ladder the real component uses |
| lines | number | 1 | How many lines to draw for shape="line". The last one is drawn short, the way the last line of a paragraph is, so a block of them reads as prose rather than as a barcode |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The scale of the thing being stood in for: the type scale for a line, the diameter for a circle, the default block height for a rect |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'secondary' | Colour family. Worth leaving at secondary: a placeholder that carries a semantic colour is saying something about content that has not arrived yet |
| width | number | string | — | An explicit width. Numbers are pixels |
| height | number | string | — | An explicit height. Numbers are pixels |
| animated | boolean | true | The travelling highlight. Turn it off for a page holding dozens, or where motion becomes noise. A reduced-motion preference already swaps the sweep for a colour pulse, so this is not the accessibility switch |
| label | string | — | What a screen reader is told. Without it the placeholder is aria-hidden, because a dozen boxes each announcing themselves is worse than silence. Give the *one* that stands for the whole region a label and it becomes a live status |
| render | useRender.RenderProp | — | Renders something other than a div |
| Prop | Type | Default | Description |
|---|---|---|---|
| shape | PlSkeletonShape | PlSkeletonShape.line | What the placeholder is standing in for. line is a run of text, rect a block, circle an avatar. Each is sized off the ladder the real component uses |
| lines | int | 1 | How many lines to draw for shape="line". The last one is drawn short, the way the last line of a paragraph is, so a block of them reads as prose rather than as a barcode |
| sizeshared | PlassSize | PlassSize.md | The scale of the thing being stood in for: the type scale for a line, the diameter for a circle, the default block height for a rect |
| colorshared | PlassColor | PlassColor.secondary | Colour family. Worth leaving at secondary: a placeholder that carries a semantic colour is saying something about content that has not arrived yet |
| width | double? | — | An explicit width. Numbers are pixels |
| height | double? | — | An explicit height. Numbers are pixels |
| animated | bool | true | The travelling highlight. Turn it off for a page holding dozens, or where motion becomes noise. A reduced-motion preference already swaps the sweep for a colour pulse, so this is not the accessibility switch |
| label | String? | — | What a screen reader is told. Without it the placeholder stays out of the semantics tree; give the one that stands for the whole region a label and it becomes a live region with that name |
Every native <div> attribute passes straight through. color is excluded from the pass-through because it is a Plass prop here.
width and height are doubles, logical pixels, as everywhere else in the package.
There is no variant, no elevation and no density. A skeleton is deliberately not made of glass: every other sheet in the library is translucent over a blurred backdrop because it is a thing sitting on the page, and a skeleton is the opposite, the shape of something that is not there yet. So it is a flat tint and nothing else, which also keeps a page of thirty placeholders from asking for thirty backdrop filters.
What the shared axes (size color) mean across the library is in prop conventions.
Examples
shape
The three shapes are the three things a layout is made of (a run of text, a block and a circle), and each is sized off the ladder the real component uses. A md line is as tall as md type, and a md circle is exactly a PlAvatar at md.
lines draws a stack of bars rather than one striped box, so the gaps between them are real gaps: text has leading. The last one is drawn short, the way the last line of a paragraph is.
Standing in for the real thing
The point is that nothing moves when the content arrives. A card that grows by 200px when its image loads has shifted everything below it while somebody was reading.
animated
The travelling highlight is on by default. Turn it off for a page holding dozens of placeholders, or where the wait is expected to be long enough that motion becomes noise.
This is not the accessibility switch: a reduced-motion preference already replaces the sweep with a colour pulse without being asked.
size
Accessibility
- Unlabelled, a placeholder is
aria-hiddenand says nothing. A dozen boxes each announcing themselves is worse than silence. - Give the one skeleton that stands for the whole region a
label, and it becomes arole="status"witharia-busy, one announcement for one wait. - Under
prefers-reduced-motionthe highlight stops travelling and the placeholder pulses in colour instead. It is not stopped outright, because a skeleton that holds still is indistinguishable from an empty box that finished loading with nothing in it.
- Unlabelled, a placeholder is excluded from the semantics tree and says nothing. A dozen boxes each announcing themselves is worse than silence.
- Give the one skeleton that stands for the whole region a
label, and it becomes a live region with that name, one announcement for one wait. - When the platform has animations turned off (
MediaQuery.disableAnimations) the highlight stops travelling and the placeholder pulses in colour instead. It is not stopped outright, because a skeleton that holds still is indistinguishable from an empty box that finished loading with nothing in it.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
role="status" + aria-busy | a named live region | Flutter has liveRegion and no busy. The name is what carries the wait. |
prefers-reduced-motion | MediaQuery.disableAnimations | The platform's own signal. |
width/height as a CSS length | double | Logical pixels. A fraction of the parent is a FractionallySizedBox around the placeholder. |
render | — | Flutter has no polymorphic element. |
className, style | — | There is no class list and no style attribute to pass through. |
The sweep is drawn differently and looks the same: CSS animates a 60%-wide pseudo-element across the box, and here the same three-stop gradient is slid across the box by a GradientTransform. One widget instead of two, and no second box to lay out per placeholder.