Loader
ProposedA 3 by 3 pixel grid for work that takes longer than a spinner should: routing a batch, re-scoring a book. Label pulses, the timer counts in mono.
@varick/ui/components/loaderAdded 2 Sept 2026
Routing submissions0.0s
Usage
import { Loader } from "@varick/ui/components/loader";
export function Example() {
return <Loader label="Routing submissions" />;
}Variants
Patterns
Drive
Dots
Orbit
<Loader variant="drive" label="Routing submissions" />
<Loader variant="dots" label="Re-scoring the book" />
<Loader variant="orbit" label="Waiting on HubSpot" />Grid alone, inside other components
Syncing 12 connectors
<LoaderGrid /> Syncing 12 connectorsAPI reference
Every component also accepts className and the native props of its root element.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | 'Routing submissions' | What is happening, as a present participle. |
| variant | 'drive' | 'dots' | 'orbit' | 'drive' | Drive and Dots run a chevron wavefront; Orbit laps the perimeter. |
| timer | boolean | true | The elapsed counter, in tenths of a second, then minutes. |
Accessibility
- role="status": the label is announced once, the timer is not re-announced every tick.
- Reduced motion freezes the grid to its dim state and stops the label pulse; the timer still counts.
Styles
Proposed CSS in Varick tokens. Nothing in the app renders this yet.
.loader-grid { display: grid; grid-template-columns: repeat(3, 4px); gap: 1.5px; }
.loader-grid span { width: 4px; height: 4px; background: var(--fg-high); border-radius: 4px; opacity: 0.15; animation: pixel-on 650ms ease-in-out var(--delay) infinite; }
@keyframes pixel-on { 0%, 100% { opacity: 0.15; } 50% { opacity: 1; } }
.pulse-label { font-size: 13px; font-weight: 500; animation: label-pulse 1.4s ease-in-out infinite; }
@keyframes label-pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }