react-aurora-background
v0.2.1
Published
A dependency-free animated WebGL aurora/gradient background for React, driven by hand-written GLSL simplex noise -- no three.js required.
Maintainers
Readme
react-aurora-background
An animated WebGL aurora background for React, written from scratch in GLSL. No three.js, no shader library, no dependencies beyond React itself — just a canvas, one compiled WebGL1 program, and simplex noise driving a slow, organic color field in real time.

Why this exists
Every other animated gradient background for React that I could find pulls in three.js just to draw a full-screen quad and run a fragment shader on it. That's roughly 600KB of dependency for something WebGL1 can do directly in about 150 lines. So this component skips the abstraction layer entirely and talks to the GL context itself.
What you get for that:
- No runtime dependencies.
reactandreact-domare peers, nothing else ships. - Actual GLSL under the hood — 3D simplex noise (Ashima Arts' reference implementation) layered with fractal Brownian motion, not a canvas-2D gradient pretending to be one.
- A flow field that reacts to the cursor, with the option to turn that off.
- Three colors, animation speed, and noise scale all exposed as props, so the look is yours to tune.
- A clean mount/unmount cycle — the program, buffers, and event listeners are torn down properly, so you can drop this into a router without leaking GL contexts on every navigation.
- A safe fallback: if WebGL isn't available for whatever reason, it logs a warning and renders nothing instead of throwing, so you can put a static background behind it and never worry about a crash.
- Full TypeScript definitions.
Installation
npm install react-aurora-backgroundUsage
The component renders a <canvas> that fills its container through CSS (width: 100%; height: 100%), so give it a positioned parent to control its size. The usual pattern is to use it as an absolutely positioned background layer behind your actual content:
import { AuroraBackground } from 'react-aurora-background'
function Hero() {
return (
<div style={{ position: 'relative', height: '100vh' }}>
<AuroraBackground
style={{ position: 'absolute', inset: 0 }}
colorA="#0d0526"
colorB="#661a99"
colorC="#1a99e6"
/>
<div style={{ position: 'relative', zIndex: 1 }}>
<h1>Your content on top</h1>
</div>
</div>
)
}Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| className | string | '' | Extra class name(s) applied to the canvas |
| style | CSSProperties | — | Inline styles applied to the canvas |
| colorA | string \| [number, number, number] | '#0d0526' | First gradient color — the darkest, used as the base |
| colorB | string \| [number, number, number] | '#661a99' | Second gradient color |
| colorC | string \| [number, number, number] | '#1a99e6' | Third gradient color |
| speed | number | 0.06 | Animation speed |
| scale | number | 0.8 | Noise scale. Higher values produce smaller, denser cloud shapes |
| interactive | boolean | true | Whether the flow visibly swirls around the cursor |
| quality | 'auto' \| 'high' \| 'medium' \| 'low' | 'auto' | Rendering quality. auto measures frame rate for the first 1.5 seconds, then adjusts between high, medium, and low |
| swirlRadius | number | 0.55 | How far the swirl reaches from the cursor, in UV units |
| swirlStrength | number | 2.4 | How tightly the flow twists around the cursor, in radians at the center |
| respectReducedMotion | boolean | true | Whether to honor the OS-level prefers-reduced-motion: reduce setting |
Colors
Color props take a CSS color string — hex in either form, or rgb()/rgba() in either the comma or the space syntax:
<AuroraBackground colorB="#661a99" />
<AuroraBackground colorB="#a3f" />
<AuroraBackground colorB="rgb(102, 25, 154)" />They also accept an [r, g, b] array with channels already in the 0–1 range, the way GLSL works with color — so pure red is [1, 0, 0], not [255, 0, 0]. This is the format the shader uses internally, and it stays supported; reach for it if you're computing colors numerically and would rather skip the string round-trip. An unparseable color logs a warning and falls back to that channel's default instead of throwing.
A note on live updates
colorA, colorB, colorC, speed, scale, interactive, quality, swirlRadius, and swirlStrength can all change freely between renders — hook them up to sliders, theme toggles, whatever you need. The WebGL context is created once, on mount, and every prop change is picked up by the next drawn frame in place, so the animation keeps running continuously through it. There is no need to memoize colors or worry about passing a new literal on every render; that used to force a full context rebuild in earlier versions of this component, but it no longer does. Color strings are parsed once per render rather than once per frame, so passing hex costs nothing at animation time.
quality="auto" is adaptive: it measures frame time during startup and periodically afterwards, then adjusts the shader from three noise octaves and a higher pixel ratio down to one octave and a capped pixel ratio when the GPU is under pressure. Use high, medium, or low to pin a level when you need deterministic rendering cost.
Accessibility
By default the component checks prefers-reduced-motion and reacts to it. If the user has that setting turned on at the OS level, the flow field stops on a single still frame instead of animating continuously, and the cursor-driven swirl is disabled along with it — the swirl is itself a form of motion, so leaving it running on a frozen background would defeat the point. The check is live for as long as the component is mounted, so flipping the setting in the OS takes effect immediately, with no remount needed.
If you have a specific reason to opt out — a demo page built around the animation, for instance — set respectReducedMotion={false} and the component will animate unconditionally, the way it did before this setting existed.
Browser support
Requires WebGL1, which is available in essentially every browser released in the last decade. On the rare device where it's missing or disabled, the component logs a warning to the console and renders an empty canvas instead of throwing — style a fallback background behind it if that matters for your use case.
Development
git clone https://github.com/godwire/react-aurora-background.git
cd react-aurora-background
npm install
npm run dev # starts the example app at http://localhost:5173,
# importing the component directly from src/Other scripts worth knowing about:
npm run typecheck # tsc --noEmit
npm run build # builds dist/ (ESM + CJS + .d.ts)The example/ app aliases react-aurora-background to ../src/index.ts, so it always reflects whatever's currently in src/ — there's no build or link step to remember while you're iterating.
Deploying the example
cd example
npm run buildDeploy example/dist anywhere that serves static files — Vercel, Netlify, GitHub Pages all work. On Vercel specifically: import the repo, set the root directory to example, and pick the Vite framework preset.
Publishing to npm
npm run build
npm login
npm publishpackage.json restricts the published files to dist ("files": ["dist"]), so only the built output ships — not the source or the example app.
How it works
The vertex shader is a no-op passthrough. A single two-triangle quad covers the canvas in clip space, and all the real work happens per pixel in the fragment shader, which reads gl_FragCoord directly for screen position rather than passing UVs down from the vertex stage.
3D simplex noise (snoise) is sampled at (x, y, time), so animating the field is just a matter of advancing the third coordinate — that's what keeps the motion continuous and organic instead of looking like a texture scrolling past. The time value the shader receives isn't raw elapsed seconds: it's accumulated frame by frame on the JavaScript side as phase += delta * speed, so a change to the speed prop only affects how fast the field moves from that moment on, rather than snapping the whole field to wherever a different constant speed would have put it since mount. Three octaves of that noise are stacked (fractal Brownian motion) to get the soft, cloud-like structure, then the result is remapped to [0, 1] and used to blend between the three configured colors with wide smoothstep ranges, which is what keeps the transitions gentle instead of banded.
The cursor interaction works by rotating the noise sample point around the cursor position, with the rotation angle falling off smoothly with distance — strong right at the cursor, gone a short distance out. That's what makes the flow field itself visibly swirl and track the cursor, rather than just placing a static highlight on top of an otherwise unchanged pattern. A smaller glow is layered on top to reinforce where the interaction is centered.
License
MIT — see LICENSE.
