Fireflies
React + Tailwind
// Each firefly: a home position, how far it wanders, its own pace and blink.
const FLIES = [
{ x: 18, y: 40, dx: 14, dy: -10, dur: 5.2, blink: 2.1, d: 0 },
{ x: 52, y: 22, dx: -10, dy: 12, dur: 6.1, blink: 2.7, d: -0.8 },
{ x: 86, y: 46, dx: 12, dy: -14, dur: 4.7, blink: 1.9, d: -1.6 },
{ x: 110, y: 18, dx: -14, dy: 10, dur: 5.8, blink: 2.4, d: -0.4 },
{ x: 38, y: 62, dx: 16, dy: 8, dur: 6.6, blink: 3.1, d: -2.2 },
{ x: 74, y: 70, dx: -12, dy: -12, dur: 5.5, blink: 2.2, d: -1.1 },
{ x: 124, y: 60, dx: -10, dy: -8, dur: 4.9, blink: 2.9, d: -2.7 },
];
const css = `
@keyframes loader-fireflies-drift {
0%, 100% { transform: translate(0, 0); }
25% { transform: translate(var(--dx), calc(var(--dy) * 0.4)); }
50% { transform: translate(calc(var(--dx) * 0.4), var(--dy)); }
75% { transform: translate(calc(var(--dx) * -0.5), calc(var(--dy) * 0.6)); }
}
@keyframes loader-fireflies-blink { 0%, 55%, 100% { opacity: 0.15; transform: scale(0.6); } 70% { opacity: 1; transform: scale(1); } }
.loader-fireflies { position: relative; width: 144px; height: 84px; }
.loader-fireflies__fly { position: absolute; width: 0; height: 0; animation: loader-fireflies-drift var(--dur) ease-in-out infinite var(--d); }
.loader-fireflies__fly::after {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background: light-dark(#373a40, #f1f2f3);
box-shadow: 0 0 8px 3px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 45%, transparent), 0 0 18px 6px color-mix(in oklab, light-dark(#42454c, #e5e6e9) 18%, transparent);
animation: loader-fireflies-blink var(--blink) ease-in-out infinite var(--d);
}
@media (prefers-reduced-motion: reduce) {
.loader-fireflies__fly, .loader-fireflies__fly::after { animation-duration: 0s; animation-iteration-count: 1; }
.loader-fireflies__fly::after { opacity: 0.7; }
}
`;
export function FirefliesLoader() {
return (
<>
<style>
{css}
</style>
<div className="loader-fireflies" role="status" aria-label="Loading">
{FLIES.map((f, i) => (
<span
key={i}
className="loader-fireflies__fly"
aria-hidden="true"
style={{ left: f.x, top: f.y, '--dx': `${f.dx}px`, '--dy': `${f.dy}px`, '--dur': `${f.dur}s`, '--blink': `${f.blink}s`, '--d': `${f.d}s` } as React.CSSProperties}
/>
))}
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-fireflies" role="status" aria-label="Loading">
<span class="loader-fireflies__fly" aria-hidden="true" style="left:18px;top:40px;--dx:14px;--dy:-10px;--dur:5.2s;--blink:2.1s;--d:0s"></span>
<span class="loader-fireflies__fly" aria-hidden="true" style="left:52px;top:22px;--dx:-10px;--dy:12px;--dur:6.1s;--blink:2.7s;--d:-0.8s"></span>
<span class="loader-fireflies__fly" aria-hidden="true" style="left:86px;top:46px;--dx:12px;--dy:-14px;--dur:4.7s;--blink:1.9s;--d:-1.6s"></span>
<span class="loader-fireflies__fly" aria-hidden="true" style="left:110px;top:18px;--dx:-14px;--dy:10px;--dur:5.8s;--blink:2.4s;--d:-0.4s"></span>
<span class="loader-fireflies__fly" aria-hidden="true" style="left:38px;top:62px;--dx:16px;--dy:8px;--dur:6.6s;--blink:3.1s;--d:-2.2s"></span>
<span class="loader-fireflies__fly" aria-hidden="true" style="left:74px;top:70px;--dx:-12px;--dy:-12px;--dur:5.5s;--blink:2.2s;--d:-1.1s"></span>
<span class="loader-fireflies__fly" aria-hidden="true" style="left:124px;top:60px;--dx:-10px;--dy:-8px;--dur:4.9s;--blink:2.9s;--d:-2.7s"></span>
</div>CSS
@keyframes loader-fireflies-drift {
0%, 100% { transform: translate(0, 0); }
25% { transform: translate(var(--dx), calc(var(--dy) * 0.4)); }
50% { transform: translate(calc(var(--dx) * 0.4), var(--dy)); }
75% { transform: translate(calc(var(--dx) * -0.5), calc(var(--dy) * 0.6)); }
}
@keyframes loader-fireflies-blink { 0%, 55%, 100% { opacity: 0.15; transform: scale(0.6); } 70% { opacity: 1; transform: scale(1); } }
.loader-fireflies { position: relative; width: 144px; height: 84px; }
.loader-fireflies__fly { position: absolute; width: 0; height: 0; animation: loader-fireflies-drift var(--dur) ease-in-out infinite var(--d); }
.loader-fireflies__fly::after {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background: light-dark(#373a40, #f1f2f3);
box-shadow: 0 0 8px 3px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 45%, transparent), 0 0 18px 6px color-mix(in oklab, light-dark(#42454c, #e5e6e9) 18%, transparent);
animation: loader-fireflies-blink var(--blink) ease-in-out infinite var(--d);
}
@media (prefers-reduced-motion: reduce) {
.loader-fireflies__fly, .loader-fireflies__fly::after { animation-duration: 0s; animation-iteration-count: 1; }
.loader-fireflies__fly::after { opacity: 0.7; }
}Goal: Create a "Fireflies" loader.
Visual behavior: Seven fireflies drifting on their own slow paths and blinking in turn, each with a different pace and rhythm.
Technique:
- Each firefly is a zero-size span with its home position inline and --dx, --dy, --dur, --blink and --d custom properties.
- A drift keyframe moves it through four offsets computed from --dx and --dy; its ::after is the glowing dot, with a blink keyframe that scales and brightens it.
- Drift and blink durations differ, so the swarm never repeats the same picture.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the fireflies stay at home, softly lit.
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 "Fireflies" loader.
Visual behavior: Seven fireflies drifting on their own slow paths and blinking in turn, each with a different pace and rhythm.
Technique:
- Each firefly is a zero-size span with its home position inline and --dx, --dy, --dur, --blink and --d custom properties.
- A drift keyframe moves it through four offsets computed from --dx and --dy; its ::after is the glowing dot, with a blink keyframe that scales and brightens it.
- Drift and blink durations differ, so the swarm never repeats the same picture.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the fireflies stay at home, softly lit.
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
Seven fireflies drifting on their own slow paths and blinking in turn, each with a different pace and rhythm.
- Dependencies
- None
- Difficulty
- Beginner
- Triggers
- continuous, loop, glow, ambient
- Techniques
- keyframes, css variables, pseudo elements
- 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 fireflies stay at home, softly lit.