Skeleton and progress
Live demo
Section titled “Live demo”The page below is the real demo.html from the kit. Use its “Toggle dark” button for the dark theme.
Current rule (v2.0.1, 2026-11-13): Determinate progress uses a named 0–100 range and one setter for semantics and scaleX fill; only completion is announced. The fill reserves 100% width. Indeterminate work uses the existing status-labelled box loader, never an invented percentage. Skeleton loading on first paint is the documented loading exception. Supersedes the retained implementation/timing claims below; see docs/current-rules.md#cr-motion-01 and motion-flow.
Loading placeholders and progress. A skeleton reserves the shape of what is coming and puts the Space Works box spinner in it; it does not shimmer. A progress bar grows to its new value instead of jumping.
Open demo.html in a browser (it links ../../tokens/tokens.css) and use “Toggle dark” to check both themes.
| File | What it is |
|---|---|
skeleton.css |
The still placeholder shape and the progress-width transition. The spinner itself is the box-open loader (../box-open/). |
demo.html |
A standalone page with the live animation in light and dark, replayable. |
react/ |
Copies of the real app files (see react/README.md for the imports to replace). |
previews/ |
Animated WebP and GIF previews, light and dark (regenerated by scripts/make-previews.mjs). |
Where the app uses it
Section titled “Where the app uses it”components/ui/Skeleton.tsxandSkeletonRowsin `components/ui/DataTable.tsx loading rows, list and card placeholders across staff and member screens.- Progress width:
features/cards/cardJobs.tsx(card job progress).components/ui/ProgressBar.tsxsets the width without a transition.
The page keeps its layout while data loads, so nothing jumps when it arrives, and the one brand-owned loading indicator tells the person something is coming. A shimmer would keep repainting an idle screen; progress that eases to its value reads as continuous.
Timing, easing and distance
Section titled “Timing, easing and distance”Current rule (v2.0.1, 2026-11-13): The retained table/snippet below records the original app copy. Use the current executable files and the complete flow recipe for decision 399; see current rules.
| Part | Value |
|---|---|
| Skeleton | no animation of its own; the box spinner inside loops 4400 ms |
| Progress fill | width 150 ms, cubic-bezier(0.4,0,0.2,1) (Tailwind default) |
Trigger
Section titled “Trigger”A skeleton is mounted while a query loads. The progress fill transitions whenever its width changes.
Reduced motion
Section titled “Reduced motion”The spinner shows the still, open logo; the progress width jumps to its value (the transition is inside no-preference).
Dark theme
Section titled “Dark theme”The surface and fill are tokens; the box spinner uses the reversed artwork in dark (see box-open).
Do and don’t
Section titled “Do and don’t”- Do size a skeleton to the content it replaces.
- Do put one box spinner in it, not a spinner on every row at once.
- Do not add shimmer sweeps or pulsing greys.
- Do not use a progress bar for unknown time (use the box loader).
Copy-paste usage
Section titled “Copy-paste usage”Current rule (v2.0.1, 2026-11-13): The retained table/snippet below records the original app copy. Use the current executable files and the complete flow recipe for decision 399; see current rules.
<link rel="stylesheet" href="../box-open/box-open.css"><link rel="stylesheet" href="skeleton.css"><div class="sw-skeleton" style="height:56px" aria-hidden="true"> <div class="sw-box loop" style="width:22px;height:20px"><!-- ../box-open/box-open-markup.html --></div></div><div class="sw-progress" role="progressbar" aria-valuenow="40"><div class="sw-progress-fill" style="width:40%"></div></div>Every other animation in the product, and the rules behind them: ../../docs/motion.md. Index of the kit: ../README.md.
Flow contract (decision 399)
Section titled “Flow contract (decision 399)”Determinate progress uses a named 0–100 range and one setter for semantics and scaleX fill; only completion is announced. The fill reserves 100% width. Indeterminate work uses the existing status-labelled box loader, never an invented percentage. Skeleton loading on first paint is the documented loading exception.
Complete rules, sequencing, keyboard and verification. The demo includes real static posters and explicit image Play/Pause controls.
Local token steps for batch 1B
Section titled “Local token steps for batch 1B”These aliases are local additions; global tokens are unchanged. Promote through the token batch when adopted.
| Local custom property | Definition |
|---|---|
--sw-local-duration-150 |
calc(var(--sw-dur-fast) * 1.25000000) |
Current determinate progress recipe
Section titled “Current determinate progress recipe”<div class="sw-progress" role="progressbar" aria-label="Loan export progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="40"> <div class="sw-progress-fill" style="--sw-progress-value:.4"></div></div><p role="status" id="export-complete"></p><script>function setProgress(value) { const bar = document.querySelector('[role=progressbar]'); const percent = Math.max(0, Math.min(100, Number.isFinite(value) ? value : 0)); bar.setAttribute('aria-valuenow', String(percent)); bar.firstElementChild.style.setProperty('--sw-progress-value', percent / 100); document.getElementById('export-complete').textContent = percent === 100 ? 'Export complete.' : '';}</script>Source
Section titled “Source”Copied from motion/skeleton/ on every build: the stylesheet, demo.html, react/ copies of the app files and the previews/.

