App Loading Screen
A full-screen loading shell — four rows of six segmented bars that pulse in a staggered, repeating wave under an uppercase, letter-spaced label. Built for the moment right before your app boots, when a generic spinner would feel low-rent and the bars need to read as "your product is loading" rather than "any spinner".
Two modes: an indeterminate cycling wave (the default), or a determinate fill driven by a progress percentage when you know the ETA.
Customize
Mode
indeterminate
Progress
50%
Installation
npx shadcn@latest add https://craftbits.dev/r/app-loading-screen.jsonUsage
import { AppLoadingScreen } from "@craft-bits/core";
<AppLoadingScreen label="Loading" />With a known progress percentage:
<AppLoadingScreen label="Loading session" progress={42} />Understanding the component
- Four rows of six segments. A 4 × 6 grid of thin bars (
h-1.5) inside a fixed-width column (w-48). The grid is the same DNA as the source's lesson-progress bars, but flattened to a single accent tone so the loader reads as a single visual signature instead of a track-by-track recap. - Staggered wave. Each segment has a local delay of
rowIndex × 0.06 + segmentIndex × 0.06so the wave reads left-to-right, top-to-bottom. The cycle duration is2.4swith a repeat delay matching the total stagger window, so the wave wraps cleanly. - Glow pulse. Each active segment carries a
4pxblur layer that ramps to0.3opacity at mid-cycle, then fades. The glow is suppressed under reduced motion and in determinate mode (where there's nothing to "pulse"). - Determinate fill. When
progressis provided, the cycling animation stops and a proportional number of cells (rounded) is lit and held. Filled left-to-right, top-to-bottom across all 24 cells. - Reduced motion. When
prefers-reduced-motion: reduceis set, the cycling wave is suppressed and the bars render as a static muted grid (indeterminate) or a static filled subset (determinate). No JS branching beyond the early-return guard — the entire animated layer is skipped. - Label is the wordmark slot. The label sits above the bars in uppercase, letter-spaced muted typography. Default is
"Loading"— pass your product name to make the screen feel like part of your product instead of a generic spinner.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | 'Loading' | Uppercase, letter-spaced caption rendered above the bars. Used as the accessible name for screen readers. |
progress | number | undefined | Completion percentage in [0, 100]. When provided, the loader switches to determinate mode (proportional fill, no cycling). |
className | string | — | Merged onto the root <div> via cn(). |
Accessibility
- Renders as a
<div role="status" aria-busy="true" aria-live="polite">witharia-labelderived from thelabelprop, so screen readers announce both the loading state and its caption. - In determinate mode the root additionally exposes
aria-valuemin={0}/aria-valuemax={100}/aria-valuenow={Math.round(progress)}for fine-grained progress reporting. - All segment elements are
aria-hidden="true"— assistive tech reads the status's accessible name, not the bar count. prefers-reduced-motion: reduceshort-circuits the cycling wave and glow pulse — the bars render as a static muted grid (or a static filled subset in determinate mode).- Color contrast: bars use
--cb-accentover--cb-bg-muted. Both pass WCAG AA against the default surface in light and dark themes.
Credits
- Extracted from:
algoflashcards(src/platform/ui/AppLoadingScreen.tsx). Generalised: the source rendered four track-colored rows (sapphire / emerald / amber / amethyst) keyed to the project's skill-tree taxonomy and an "AlgoRecall" wordmark; the library version replaces both with a single accent tone and alabelslot so the loader is reusable across products.