Scanner
React + Tailwind
import { useMemo } from 'react';
const css = `
@keyframes loader-scanner-sweep {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(48px); }
}
.loader-scanner {
position: relative;
display: inline-flex;
flex-direction: column;
gap: 6px;
padding: 10px 12px 8px;
border-radius: 8px;
background: light-dark(#fbfcfd, #121417);
box-shadow: inset 0 0 0 1px light-dark(#dbdde1, #2e3035);
}
.loader-scanner__code { display: flex; align-items: stretch; height: 48px; gap: 2px; }
.loader-scanner__bar { background: light-dark(#5f626a, #a7abb2); }
.loader-scanner__digits {
display: flex;
justify-content: space-between;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 8px;
letter-spacing: 0.08em;
color: light-dark(#5f626a, #a7abb2);
}
.loader-scanner__beam {
position: absolute;
left: 6px;
right: 6px;
top: 10px;
height: 2px;
margin-top: -1px;
border-radius: 2px;
background: light-dark(#373a40, #f1f2f3);
box-shadow: 0 0 8px 2px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 60%, transparent), 0 0 24px 6px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 18%, transparent);
animation: loader-scanner-sweep 1.8s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
/* A band of light following the beam, lighting the bars it crosses. */
.loader-scanner__glow {
position: absolute;
left: 12px;
right: 12px;
top: 10px;
height: 48px;
pointer-events: none;
background: linear-gradient(transparent, color-mix(in oklab, light-dark(#373a40, #f1f2f3) 30%, transparent), transparent) 0 0 / 100% 16px no-repeat;
animation: loader-scanner-glow 1.8s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes loader-scanner-glow {
0%, 100% { background-position: 0 -8px; }
50% { background-position: 0 40px; }
}
@media (prefers-reduced-motion: reduce) {
.loader-scanner__beam, .loader-scanner__glow { animation-duration: 6s; }
}
`;
export function ScannerLoader() {
// A seeded barcode: bar widths 1–3px with matching gaps.
const bars = useMemo(() => {
let seed = 9;
const rand = () => ((seed = (seed * 16807) % 2147483647) / 2147483647);
return Array.from({ length: 30 }, () => 1 + Math.floor(rand() * 3));
}, []);
return (
<>
<style>
{css}
</style>
<div className="loader-scanner" role="status" aria-label="Loading">
<span className="loader-scanner__code" aria-hidden="true">
{bars.map((w, i) => (
<span key={i} className="loader-scanner__bar" style={{ width: w }} />
))}
</span>
<span className="loader-scanner__digits" aria-hidden="true">
<span>4</span>
<span>820194</span>
<span>730551</span>
<span>2</span>
</span>
<span className="loader-scanner__glow" aria-hidden="true" />
<span className="loader-scanner__beam" aria-hidden="true" />
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-scanner" role="status" aria-label="Loading">
<span class="loader-scanner__code" aria-hidden="true">
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:2px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:3px"></span>
<span class="loader-scanner__bar" style="width:1px"></span>
</span>
<span class="loader-scanner__digits" aria-hidden="true">
<span>4</span>
<span>820194</span>
<span>730551</span>
<span>2</span>
</span>
<span class="loader-scanner__glow" aria-hidden="true"></span>
<span class="loader-scanner__beam" aria-hidden="true"></span>
</div>CSS
@keyframes loader-scanner-sweep {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(48px); }
}
.loader-scanner {
position: relative;
display: inline-flex;
flex-direction: column;
gap: 6px;
padding: 10px 12px 8px;
border-radius: 8px;
background: light-dark(#fbfcfd, #121417);
box-shadow: inset 0 0 0 1px light-dark(#dbdde1, #2e3035);
}
.loader-scanner__code { display: flex; align-items: stretch; height: 48px; gap: 2px; }
.loader-scanner__bar { background: light-dark(#5f626a, #a7abb2); }
.loader-scanner__digits {
display: flex;
justify-content: space-between;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 8px;
letter-spacing: 0.08em;
color: light-dark(#5f626a, #a7abb2);
}
.loader-scanner__beam {
position: absolute;
left: 6px;
right: 6px;
top: 10px;
height: 2px;
margin-top: -1px;
border-radius: 2px;
background: light-dark(#373a40, #f1f2f3);
box-shadow: 0 0 8px 2px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 60%, transparent), 0 0 24px 6px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 18%, transparent);
animation: loader-scanner-sweep 1.8s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
/* A band of light following the beam, lighting the bars it crosses. */
.loader-scanner__glow {
position: absolute;
left: 12px;
right: 12px;
top: 10px;
height: 48px;
pointer-events: none;
background: linear-gradient(transparent, color-mix(in oklab, light-dark(#373a40, #f1f2f3) 30%, transparent), transparent) 0 0 / 100% 16px no-repeat;
animation: loader-scanner-glow 1.8s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes loader-scanner-glow {
0%, 100% { background-position: 0 -8px; }
50% { background-position: 0 40px; }
}
@media (prefers-reduced-motion: reduce) {
.loader-scanner__beam, .loader-scanner__glow { animation-duration: 6s; }
}Goal: Create a "Scanner" loader.
Visual behavior: A barcode scanner: a laser line sweeps up and down the code while a band of light travels with it, lighting each bar it crosses.
Technique:
- The barcode is thirty bars of seeded random width (1 to 3px) in a flex row, with a row of digits underneath.
- The beam is a 2px line with a layered glow that sweeps 48px down and back on a 1.8s ease-in-out loop.
- A second element over the bars carries a 16px bright gradient band whose background-position follows the beam on the same timing, lighting the bars beneath it.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the sweep slows to 6s.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Scanner" loader.
Visual behavior: A barcode scanner: a laser line sweeps up and down the code while a band of light travels with it, lighting each bar it crosses.
Technique:
- The barcode is thirty bars of seeded random width (1 to 3px) in a flex row, with a row of digits underneath.
- The beam is a 2px line with a layered glow that sweeps 48px down and back on a 1.8s ease-in-out loop.
- A second element over the bars carries a 16px bright gradient band whose background-position follows the beam on the same timing, lighting the bars beneath it.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the sweep slows to 6s.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A barcode scanner: a laser line sweeps up and down the code while a band of light travels with it, lighting each bar it crosses.
- Dependencies
- None
- Difficulty
- Beginner
- Triggers
- continuous, loop, glow
- Techniques
- keyframes, gradient
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the sweep slows to 6s.