Galaxy
React + Tailwind
import { useMemo } from 'react';
const css = `
@keyframes loader-galaxy-turn { to { transform: rotate(360deg); } }
@keyframes loader-galaxy-twinkle {
0%, 100% { opacity: var(--o); }
50% { opacity: calc(var(--o) * 0.35); }
}
.loader-galaxy {
position: relative;
width: 110px;
height: 110px;
animation: loader-galaxy-turn 9s linear infinite;
}
.loader-galaxy__core {
position: absolute;
left: 50%;
top: 50%;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
border-radius: 50%;
background: radial-gradient(circle, light-dark(#373a40, #f1f2f3), color-mix(in oklab, light-dark(#373a40, #f1f2f3) 30%, transparent) 60%, transparent);
box-shadow: 0 0 20px 6px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 20%, transparent);
}
.loader-galaxy__star {
position: absolute;
border-radius: 50%;
background: light-dark(#373a40, #f1f2f3);
animation: loader-galaxy-twinkle var(--tw) ease-in-out var(--d) infinite;
}
@media (prefers-reduced-motion: reduce) {
.loader-galaxy { animation-duration: 40s; }
.loader-galaxy__star { animation: none; opacity: var(--o); }
}
`;
export function GalaxyLoader() {
// Two logarithmic arms with seeded scatter, so server and client agree.
const stars = useMemo(() => {
let seed = 5;
const rand = () => ((seed = (seed * 16807) % 2147483647) / 2147483647);
const out: React.CSSProperties[] = [];
for (let arm = 0; arm < 2; arm++)
for (let i = 0; i < 30; i++) {
const t = i / 29;
const r = 8 + t * 44;
const a = arm * Math.PI + t * 4.4 + (rand() - 0.5) * 0.5;
const jitter = (rand() - 0.5) * (3 + t * 7);
const size = 1 + rand() * (2.2 - t * 1.2);
out.push({
left: `${(55 + Math.cos(a) * r + jitter).toFixed(1)}px`,
top: `${(55 + Math.sin(a) * r + jitter).toFixed(1)}px`,
width: size,
height: size,
'--o': (1 - t * 0.6).toFixed(2),
'--tw': `${(1.2 + rand() * 2).toFixed(2)}s`,
'--d': `${(-rand() * 3).toFixed(2)}s`,
} as React.CSSProperties);
}
return out;
}, []);
return (
<>
<style>
{css}
</style>
<div className="loader-galaxy" role="status" aria-label="Loading">
<span className="loader-galaxy__core" aria-hidden="true" />
{stars.map((s, i) => (
<span key={i} className="loader-galaxy__star" style={s} aria-hidden="true" />
))}
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="loader-galaxy" role="status" aria-label="Loading">
<span class="loader-galaxy__core" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:63.2px;top:53.5px;width:2.7116585441453656px;height:2.7116585441453656px;--o:1.00;--tw:1.79s;--d:-1.99s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:63.6px;top:53.7px;width:1.8511950475787529px;height:1.8511950475787529px;--o:0.98;--tw:1.99s;--d:-2.02s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:65.0px;top:60.7px;width:1.327805560568835px;height:1.327805560568835px;--o:0.96;--tw:1.55s;--d:-0.80s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:65.3px;top:60.8px;width:1.0799018637777715px;height:1.0799018637777715px;--o:0.94;--tw:3.03s;--d:-1.00s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:67.7px;top:60.2px;width:2.9223486083711934px;height:2.9223486083711934px;--o:0.92;--tw:2.50s;--d:-0.69s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:65.4px;top:66.3px;width:1.5373303484265177px;height:1.5373303484265177px;--o:0.90;--tw:1.36s;--d:-1.52s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:66.5px;top:70.2px;width:1.609455076102074px;height:1.609455076102074px;--o:0.88;--tw:1.67s;--d:-2.04s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:61.3px;top:70.3px;width:2.493989814087712px;height:2.493989814087712px;--o:0.86;--tw:3.11s;--d:-2.48s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:66.5px;top:74.8px;width:2.1462402969027288px;height:2.1462402969027288px;--o:0.83;--tw:2.73s;--d:-0.77s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:59.1px;top:74.1px;width:1.774047899051078px;height:1.774047899051078px;--o:0.81;--tw:1.93s;--d:-0.55s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:51.8px;top:77.4px;width:1.3373647697524196px;height:1.3373647697524196px;--o:0.79;--tw:1.95s;--d:-2.39s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:48.2px;top:78.5px;width:1.851966913723789px;height:1.851966913723789px;--o:0.77;--tw:2.29s;--d:-0.91s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:48.4px;top:80.4px;width:1.9904867248179987px;height:1.9904867248179987px;--o:0.75;--tw:2.39s;--d:-2.80s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:43.5px;top:79.6px;width:1.7540216436588212px;height:1.7540216436588212px;--o:0.73;--tw:2.68s;--d:-1.11s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:42.3px;top:82.5px;width:1.624628381644893px;height:1.624628381644893px;--o:0.71;--tw:2.34s;--d:-1.95s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:38.6px;top:81.5px;width:1.3954907679395498px;height:1.3954907679395498px;--o:0.69;--tw:2.82s;--d:-2.55s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:35.7px;top:81.0px;width:1.122017151170932px;height:1.122017151170932px;--o:0.67;--tw:2.08s;--d:-1.02s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:31.3px;top:79.0px;width:1.9384277125084195px;height:1.9384277125084195px;--o:0.65;--tw:3.19s;--d:-1.33s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:25.6px;top:74.5px;width:2.098766115003751px;height:2.098766115003751px;--o:0.63;--tw:2.33s;--d:-2.87s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:17.7px;top:65.8px;width:1.293437278885871px;height:1.293437278885871px;--o:0.61;--tw:1.89s;--d:-0.23s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:15.4px;top:54.3px;width:1.598457307839883px;height:1.598457307839883px;--o:0.59;--tw:2.98s;--d:-2.31s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:12.2px;top:42.2px;width:1.2199784343127575px;height:1.2199784343127575px;--o:0.57;--tw:2.54s;--d:-1.48s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:17.9px;top:43.4px;width:1.9967992973145736px;height:1.9967992973145736px;--o:0.54;--tw:2.11s;--d:-0.37s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:15.5px;top:48.9px;width:1.0795870409171775px;height:1.0795870409171775px;--o:0.52;--tw:2.35s;--d:-1.96s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:14.4px;top:33.5px;width:1.018101040250219px;height:1.018101040250219px;--o:0.50;--tw:1.34s;--d:-0.40s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:20.9px;top:28.0px;width:1.2325836554321385px;height:1.2325836554321385px;--o:0.48;--tw:3.01s;--d:-1.76s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:18.7px;top:19.7px;width:1.8449867141417746px;height:1.8449867141417746px;--o:0.46;--tw:2.01s;--d:-2.79s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:36.1px;top:9.8px;width:1.7987131558481138px;height:1.7987131558481138px;--o:0.44;--tw:3.07s;--d:-0.68s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:45.8px;top:8.7px;width:1.8130367906760627px;height:1.8130367906760627px;--o:0.42;--tw:2.68s;--d:-2.75s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:30.3px;top:12.5px;width:1.8244934034647857px;height:1.8244934034647857px;--o:0.40;--tw:1.72s;--d:-1.33s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:47.9px;top:56.6px;width:2.3863388664956853px;height:2.3863388664956853px;--o:1.00;--tw:3.20s;--d:-1.79s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:45.9px;top:55.7px;width:1.5531869345287186px;height:1.5531869345287186px;--o:0.98;--tw:1.42s;--d:-0.05s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:43.7px;top:52.7px;width:3.0041189690107584px;height:3.0041189690107584px;--o:0.96;--tw:1.23s;--d:-0.43s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:42.4px;top:48.1px;width:2.4740803678286927px;height:2.4740803678286927px;--o:0.94;--tw:2.67s;--d:-0.15s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:43.8px;top:45.8px;width:1.9376283046531142px;height:1.9376283046531142px;--o:0.92;--tw:2.82s;--d:-1.07s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:44.6px;top:41.6px;width:2.346434739354847px;height:2.346434739354847px;--o:0.90;--tw:3.03s;--d:-0.83s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:45.2px;top:39.2px;width:1.0872868321600164px;height:1.0872868321600164px;--o:0.88;--tw:2.52s;--d:-0.77s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:42.2px;top:40.5px;width:2.0403918006035298px;height:2.0403918006035298px;--o:0.86;--tw:1.70s;--d:-0.91s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:46.8px;top:36.8px;width:1.0036794175700043px;height:1.0036794175700043px;--o:0.83;--tw:1.38s;--d:-2.34s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:49.9px;top:35.6px;width:1.299163982443572px;height:1.299163982443572px;--o:0.81;--tw:1.59s;--d:-0.37s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:53.2px;top:32.0px;width:2.6714029118815423px;height:2.6714029118815423px;--o:0.79;--tw:2.74s;--d:-0.77s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:57.8px;top:31.4px;width:1.9983438115559258px;height:1.9983438115559258px;--o:0.77;--tw:2.23s;--d:-0.21s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:65.5px;top:30.6px;width:1.3410189261802818px;height:1.3410189261802818px;--o:0.75;--tw:2.50s;--d:-1.04s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:63.8px;top:26.9px;width:1.885346527475913px;height:1.885346527475913px;--o:0.73;--tw:2.62s;--d:-0.29s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:70.3px;top:30.4px;width:1.5131325013595287px;height:1.5131325013595287px;--o:0.71;--tw:1.85s;--d:-1.70s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:73.2px;top:30.0px;width:1.6479051176071497px;height:1.6479051176071497px;--o:0.69;--tw:3.20s;--d:-1.75s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:77.2px;top:31.5px;width:2.106912843139593px;height:2.106912843139593px;--o:0.67;--tw:2.59s;--d:-0.76s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:83.9px;top:40.8px;width:1.3443821928333344px;height:1.3443821928333344px;--o:0.65;--tw:2.36s;--d:-1.72s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:84.5px;top:37.2px;width:1.4913519267064936px;height:1.4913519267064936px;--o:0.63;--tw:1.27s;--d:-2.48s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:91.4px;top:41.8px;width:2.085824769431676px;height:2.085824769431676px;--o:0.61;--tw:1.50s;--d:-0.25s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:87.1px;top:37.8px;width:2.1895534899518836px;height:2.1895534899518836px;--o:0.59;--tw:2.47s;--d:-1.79s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:93.6px;top:47.9px;width:1.5361538922470137px;height:1.5361538922470137px;--o:0.57;--tw:1.25s;--d:-0.55s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:92.4px;top:61.0px;width:1.6096767663165772px;height:1.6096767663165772px;--o:0.54;--tw:2.02s;--d:-2.46s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:93.6px;top:58.7px;width:1.084550714715223px;height:1.084550714715223px;--o:0.52;--tw:2.01s;--d:-2.83s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:93.0px;top:83.3px;width:2.145668189526638px;height:2.145668189526638px;--o:0.50;--tw:1.89s;--d:-1.38s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:96.1px;top:84.8px;width:2.071490318502737px;height:2.071490318502737px;--o:0.48;--tw:1.42s;--d:-1.23s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:75.0px;top:91.8px;width:2.0781822586956737px;height:2.0781822586956737px;--o:0.46;--tw:3.03s;--d:-0.66s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:92.4px;top:90.3px;width:2.072708946368634px;height:2.072708946368634px;--o:0.44;--tw:1.21s;--d:-2.65s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:88.1px;top:98.2px;width:1.9785685202328183px;height:1.9785685202328183px;--o:0.42;--tw:1.77s;--d:-0.50s" aria-hidden="true"></span>
<span class="loader-galaxy__star" style="left:66.7px;top:100.5px;width:1.1735871979843766px;height:1.1735871979843766px;--o:0.40;--tw:2.16s;--d:-2.92s" aria-hidden="true"></span>
</div>CSS
@keyframes loader-galaxy-turn { to { transform: rotate(360deg); } }
@keyframes loader-galaxy-twinkle {
0%, 100% { opacity: var(--o); }
50% { opacity: calc(var(--o) * 0.35); }
}
.loader-galaxy {
position: relative;
width: 110px;
height: 110px;
animation: loader-galaxy-turn 9s linear infinite;
}
.loader-galaxy__core {
position: absolute;
left: 50%;
top: 50%;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
border-radius: 50%;
background: radial-gradient(circle, light-dark(#373a40, #f1f2f3), color-mix(in oklab, light-dark(#373a40, #f1f2f3) 30%, transparent) 60%, transparent);
box-shadow: 0 0 20px 6px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 20%, transparent);
}
.loader-galaxy__star {
position: absolute;
border-radius: 50%;
background: light-dark(#373a40, #f1f2f3);
animation: loader-galaxy-twinkle var(--tw) ease-in-out var(--d) infinite;
}
@media (prefers-reduced-motion: reduce) {
.loader-galaxy { animation-duration: 40s; }
.loader-galaxy__star { animation: none; opacity: var(--o); }
}Goal: Create a "Galaxy" loader.
Visual behavior: Two spiral arms of twinkling stars wheel slowly round a glowing core, the outer stars smaller and fainter.
Technique:
- Sixty stars are placed on two logarithmic-style arms (angle grows with radius) with seeded scatter, so server and client render the same field.
- Each star carries its base opacity, twinkle period and a negative delay in CSS variables and twinkles on a shared keyframe.
- The whole galaxy rotates once every 9s round a radial-gradient core with a soft glow.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the stars stop twinkling and the galaxy turns very slowly.
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 "Galaxy" loader.
Visual behavior: Two spiral arms of twinkling stars wheel slowly round a glowing core, the outer stars smaller and fainter.
Technique:
- Sixty stars are placed on two logarithmic-style arms (angle grows with radius) with seeded scatter, so server and client render the same field.
- Each star carries its base opacity, twinkle period and a negative delay in CSS variables and twinkles on a shared keyframe.
- The whole galaxy rotates once every 9s round a radial-gradient core with a soft glow.
Accessibility: The root has role="status" and aria-label="Loading"; everything inside is decorative. Under prefers-reduced-motion the stars stop twinkling and the galaxy turns very slowly.
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
Two spiral arms of twinkling stars wheel slowly round a glowing core, the outer stars smaller and fainter.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- continuous, loop, ambient, glow
- Techniques
- keyframes, css variables, radial 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 stars stop twinkling and the galaxy turns very slowly.