Pixel Loader <mv-pixel-loader>
Dot-matrix thinking indicators on a tiny pixel grid (3×3 to 9×9) in currentColor: snake, orbit, concentric ripple, scan, spiral, breathing, sparkle or diagonal wave, with an optional label crossed by a brighter band. The script only builds the grid; every motion is a CSS keyframe.
| Category | Feedback |
|---|---|
| Type | Web Component (<mv-pixel-loader>) |
| Status | stable |
| Keywords | loader, spinner, pixel, dot-matrix, grid, ai, thinking, status, shimmer, css |
When to use
- An AI feature needs a distinctive thinking indicator with a shimmering label like Thinking
- A loading state should feel techy and branded, with pattern choices such as snake, ripple or scan
- A lightweight CSS-only status indicator is needed inside a chat bubble or toolbar
Avoid when
- A neutral, conventional loader fits the product better, such as in a button or form → use Spinner instead
- A voice assistant needs a presence that reacts to audio and conversation states → use Thinking Orb instead
- The loading content has a predictable layout that could be previewed → use Skeleton instead
Install
node scripts/add.mjs pixel-loader --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["pixel-loader"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/pixel-loader/pixel-loader.js, components/pixel-loader/pixel-loader.css.
Usage
Canonical markup, to start from and customize with attributes, data-* and CSS variables:
<div id="mv-pxl-demo">
<style>
#mv-pxl-demo { display: grid; gap: 1.5rem; width: 100%; max-width: 44rem; }
#mv-pxl-demo .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: .75rem; margin: 0; padding: 0; list-style: none; }
#mv-pxl-demo .grid li { display: grid; justify-items: center; gap: .875rem; padding: 1.25rem .75rem 1rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); }
#mv-pxl-demo .grid code { font: 500 .75rem var(--mv-font-mono); color: var(--mv-fg-muted); }
#mv-pxl-demo .chat { display: grid; gap: .875rem; padding: 1rem 1.125rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xl); background: var(--mv-bg-subtle); font-size: .875rem; }
#mv-pxl-demo .ask { justify-self: end; max-width: 80%; padding: .625rem .875rem; border-radius: var(--mv-radius-lg) var(--mv-radius-lg) var(--mv-radius-xs) var(--mv-radius-lg); background: var(--mv-accent); color: var(--mv-fg-on-accent); }
#mv-pxl-demo .steps { display: flex; flex-wrap: wrap; gap: 1.25rem 1.75rem; padding-top: .75rem; border-top: 1px solid var(--mv-border); }
</style>
<ul class="grid" aria-label="Pixel loader variants">
<li><mv-pixel-loader variant="snake" size="7" data-size="lg"></mv-pixel-loader><code>snake</code></li>
<li><mv-pixel-loader variant="orbit" size="7" data-size="lg"></mv-pixel-loader><code>orbit</code></li>
<li><mv-pixel-loader variant="ripple" size="7" data-size="lg"></mv-pixel-loader><code>ripple</code></li>
<li><mv-pixel-loader variant="scan" size="7" data-size="lg"></mv-pixel-loader><code>scan</code></li>
<li><mv-pixel-loader variant="spiral" size="7" data-size="lg"></mv-pixel-loader><code>spiral</code></li>
<li><mv-pixel-loader variant="breathing" size="7" data-size="lg"></mv-pixel-loader><code>breathing</code></li>
<li><mv-pixel-loader variant="sparkle" size="7" data-size="lg"></mv-pixel-loader><code>sparkle</code></li>
<li><mv-pixel-loader variant="wave" size="7" data-size="lg"></mv-pixel-loader><code>wave</code></li>
</ul>
<div class="chat">
<div class="ask">Which flights from Toronto to Lisbon land before noon on October 12?</div>
<mv-pixel-loader variant="orbit" label="Thinking…" style="--mv-pixel-loader-color: var(--mv-accent)"></mv-pixel-loader>
<div class="steps">
<mv-pixel-loader variant="scan" label="Comparing 42 fares"></mv-pixel-loader>
<mv-pixel-loader variant="wave" label="Checking layovers"></mv-pixel-loader>
<mv-pixel-loader variant="sparkle" data-size="sm" label="Drafting the answer"></mv-pixel-loader>
</div>
</div>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
variant | snake | orbit | ripple | scan | spiral | breathing | sparkle | wave | snake | Animated pattern. |
size | number | 5 | Grid side, from 3 to 9 cells. |
label | string | Visible label crossed by a brighter band ("Thinking…"). Without a label, strings.loading ("Loading…") is still read by screen readers. | |
speed | number | 1 | Speed multiplier. |
data-size | sm | lg | xl | Pixel size (3, 6 or 10 px; 4 px by default). |
Properties
| Name | Type | Description |
|---|---|---|
strings | Partial<{ loading: string }> | Overrides for the default text: loading ("Loading…"), read when no label is set. English defaults. |
CSS classes
| Name | Description |
|---|---|
mv-pixel-loader-grid / -cell / -label | Generated parts; each cell carries its moment in the cycle in --_t (idle cells have data-idle). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-pixel-loader-color | var(--mv-fg) | Pixel color (otherwise currentColor via color). |
--mv-pixel-loader-cell | 4px | Size of one pixel; the gap follows it unless set. |
--mv-pixel-loader-gap | 0.375 × cell | Space between pixels. |
--mv-pixel-loader-off | 0.14 | Opacity of an unlit pixel. |
--mv-pixel-loader-radius | 1px | Pixel corner radius (50% for round dots). |
--mv-pixel-loader-label-color / -shine | Label color and the color of its moving band. |
Accessibility
role="status" (polite live region) with a text label, visible or visually hidden; the grid is aria-hidden. Reduced motion (OS setting or data-motion="reduce"): the keyframes stop and the grid shows a still frame of its pattern, and the label band stops. Forced colors: pixels in CanvasText, plain label text.