048

Aurora Gradient Field

£10 or the whole Vault, £99

A vivid mesh-gradient background of drifting colour with a bright lead bloom and a slower halo that follow the cursor across the page, built without canvas or WebGL.

intermediate
7 more details
gradientambientorganic-motionmouse-followquickTotoken-themingpointer-effects

This demo reads better at your own screen size than in the frame below:

Updated

About this effect

An ambient aurora background built entirely from layered CSS radial gradients. Six colour stops drift on their own slow, non-harmonic cycles so the field never visibly repeats, and a wide conic sweep turns behind them like a curtain of light.

Read the full effect overview

A bright bloom follows the cursor around the page, arriving about a third of a second behind it, while a much wider halo follows at nearly twice the delay. That separation leaves a visible wake across the field instead of a single light welded to the pointer. The two gradient layers also lean toward the cursor by different amounts, which is what turns a flat wash into depth.

Four palettes cross-fade through a dip rather than cutting, and every one of them is a set of CSS hue tokens the script never reads, so your own palette is a copied block and six numbers.

The legibility treatment is local, not global: a feathered band of shade sits behind the reading column only, so the field runs at full strength everywhere the copy is not, and body text still measures above 5.9:1 against every palette.

There is no canvas and no WebGL, so it runs on anything and costs a fraction of a shader background.

Aurora Gradient Field - GSAP animation effect preview

What's included

11 items
  • Pure CSS gradient field driven by custom properties, no canvas and no WebGL
  • Dual cursor light: a compact bright lead bloom and a wide halo following at nearly twice the delay, both bound to the window so overlaid content never swallows movement
  • Six drifting colour stops on non-harmonic cycles, so the loop is never visible
  • Two gradient layers that lean toward the pointer by different amounts and in opposite directions, for parallax depth without a second asset
  • Conic aurora sweep turning slowly behind the stops, which is what stops the field reading as a static wash
  • Local contrast treatment: a feathered band of shade behind the reading column instead of a scrim over the whole canvas, so the field stays vivid in the margins while body text holds AA
  • Four palettes included, defined purely as CSS hue tokens plus optional lightness and saturation offsets that you can copy and re-point
  • Palette controls that dip the field through the change so new hues fade in instead of cutting, with the fade timeline managed so overlapping presses cannot strand it dim
  • Fine grain layer that dithers the ramps and kills the banding large gradients suffer from
  • Gradient writes throttled while transforms stay at full frame rate, and the field pauses off screen or in a hidden tab
  • A tap equivalent on touch and a composed still under reduced motion

Perfect for

5 use cases
  • Hero and landing page backgrounds that need depth without a heavy asset
  • SaaS and product sites where the background must stay behind readable copy
  • Section backdrops, footers and call-to-action bands that would otherwise be flat colour
  • Login, pricing and coming-soon pages that want atmosphere with almost no payload
  • Dark interfaces that need ambient colour on low-power devices where WebGL is a bad trade

How it works

6 sections

Each layer paints a stack of radial gradients whose stop positions, radii and light values are CSS custom properties. One parametric clock advances each frame and writes those properties, so every stop moves on its own sine with an unrelated frequency and phase.

Pointer position drives two bloom layers: the bright core follows at 0.32 seconds and the larger echo at 1.9 times that delay. Both move as offsets from the centre where CSS already parks them, so their resting state is correct before any script runs. The listener is on the window rather than the field, because the field is a background with the page's content stacked over it. The same pointer value eases into the two layers' transforms, leaning them opposite ways for depth.

The field's light values are written every pass but held flat: only the hue drifts. That is what lets the reading band be measured once and stay correct, rather than needing a contrast check at a peak that is easy to forget.

A palette press runs a single timeline that dips the layers' opacity, swaps the palette attribute at the bottom of the dip and fades back up. There is exactly one of these at a time and it is killed before a new one is built, because two overlapping dips can otherwise leave the field almost invisible with nothing scheduled to restore it.

Every value written to a custom property is clamped and checked for finiteness first, because a non-finite number silently invalidates the declaration that reads it and takes the whole gradient with it.

The field stops animating entirely when it is off screen or the tab is hidden. Under reduced motion the script paints a single composed frame, attaches no per-frame loop or pointer listeners, and keeps palette controls operable with an instant colour change.

Difficulty Intermediate
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Aurora Gradient Field opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add the field to your <body>:

Code snippet omitted: it ships with the download.

3. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

The script finds every [data-aurora] element, writes custom properties onto it and its layers on a shared ticker, drives the bloom with gsap.quickTo, and stops entirely when the field scrolls off screen or the tab is hidden. It needs no GSAP plugins.

Pointer tracking is bound to window, not to the field. This is deliberate and it is what makes the effect usable as a page background: if the listener sat on the element, every paragraph, link and button stacked on top of the field would swallow the movement and the light would only follow the cursor over the gaps between your content.

Using It With Your Own Design

The effect never looks at a class name. It needs four things from your markup:

  1. A positioning context on the field. [data-aurora] is position: relative; overflow: hidden; isolation: isolate. The isolation matters: layer two and the sweep blend with mix-blend-mode: screen, and without an isolated stacking context they would blend with whatever sits behind the field on your page.
  2. At least one [data-aurora-layer]. Layers are position: absolute; inset: -24%. That overscan is deliberate: the layers translate toward the pointer, and at inset: 0 you would see their edges arrive. Two layers is the tuned arrangement; one is calmer and cheaper, three or more starts to mud.
  3. Your content above the field. The layers are all pointer-events: none, but they are still painted after nothing in particular, so give your content wrapper position: relative; z-index: 2.
  4. A palette. Either use one of the four included, or copy a palette block in style.css and change the six hue numbers. Nothing in the JavaScript knows the palette names.

[data-aurora-sweep], [data-aurora-focus], [data-aurora-echo], [data-aurora-grain], [data-aurora-scrim] and [data-aurora-readband] are all optional, but they each do a job:

  • The sweep is the wide conic band that turns behind the stops. Without it the field is a pleasant wash that never quite reads as aurora. Its cone origin sits outside the element on purpose; move it inside and its apex shows as a hard convergence point in the middle of your hero.
  • The bloom and echo are the paired light that follows the pointer. The compact bloom arrives at data-aurora-follow; the wider echo uses 1.9 times that delay, leaving a soft wake on decisive pointer moves. Both are parked dead centre by CSS (left: 50%; top: 50% with negative margins) and the script drives them as offsets from there, so the resting position is correct before any script runs and stays correct without JavaScript. Do not re-anchor them to the top-left and centre them from JavaScript: they then paint in the corner for one frame on load, which is a visible flash.
  • The grain is a small SVG noise tile at 14% opacity. Large gradients band visibly on 8-bit displays, and this is the difference between smooth and stripey on a real monitor. It is the first thing to delete if you are compositing over your own texture.
  • The scrim is a light, whole-surface veil that seats the colour on a dark page. It takes roughly a sixth off the field and no more. It is not the contrast treatment, and turning it up until your body copy is readable is the mistake this effect used to ship with: it costs you the whole field to protect the fraction of it that has text on it.
  • The readband is the contrast treatment, and it is local. A band of shade the width of your reading column, feathered to fully transparent on both sides so there is no edge to see, running the full height of the field. Text sits in the shaded core and the field runs at full strength in the margins. Its opacity is held flat, so the column's contrast ratio never drifts.

Putting text over the field

Set --a-read on the readband to your own measure and leave everything else alone:

Code snippet omitted: it ships with the download.

--a-read is the flat, fully shaded core; the feather (--a-read-feather, 9rem) is added outside it, so the element is wider than the number you set. That is the intent: the shade has to be at full strength across the whole measure and reach nothing at all before the page edge.

Text that runs wider than the reading column needs its own patch, because the band deliberately does not reach it. A masthead row, a footer, a full-bleed caption: give it a feathered pseudo-element of its own rather than widening the band. The one thing to get right is where the feather sits: put every gradient stop at the same length as the corresponding negative inset, so the patch is at full strength across the whole block and fades only in the overhang. Feather in percentages instead and the ends of a wide row sit in the falloff, which on most pages is exactly where the smallest type is.

Everything in style.css from the .stage rule down is the demo's own layout and can be deleted. The one rule worth keeping from it: the field is position: fixed; inset: 0; z-index: 0 and your content is position: relative; z-index: 1. That is what makes this a background rather than a box.

Options

Set these on the [data-aurora] element.

Attribute Values Default Description
data-aurora marker required Identifies a field
data-aurora-palette aurora, tide, dusk, ember, or your own none Selects a palette block in the CSS
data-aurora-speed number 1 Drift rate. 0.5 is barely-there ambience, 2 is restless
data-aurora-intensity number 1 Scales how far the stops travel on their drift
data-aurora-parallax pixels 48 How far the layers lean toward the pointer. 0 disables the lean
data-aurora-follow seconds 0.32 How long the bloom takes to reach the pointer. Below about 0.15 it is welded to the cursor and reads as a sprite; past about 0.5 the connection to your hand is gone

Child hooks:

Attribute Where Description
data-aurora-layer="1" / "2" inside the field A gradient stop stack. The number selects which three hues it uses
data-aurora-sweep inside the field The rotating conic aurora band
data-aurora-focus inside the field The compact lead bloom that follows the pointer
data-aurora-echo inside the field The wider trailing halo, following at 1.9 times the configured delay
data-aurora-grain inside the field The anti-banding noise tile
data-aurora-scrim inside the field The light whole-surface veil that seats the field on a dark page
data-aurora-readband inside the field The feathered band of shade behind the reading column. Set --a-read to your measure
data-aurora-palette-control="name" anywhere Swaps the palette, fading the field through the change
data-aurora-target="field-id" on a control Points a control at a field it does not live inside

Custom Properties

The script writes these onto the field; the CSS declares a composed resting value for each, which is what you see before the script runs and what a visitor without JavaScript keeps.

Property Resting Description
--a-lum 52 Lightness of every stop, as a plain number. Held flat. Past about 72 an HSL stop runs out of room for chroma and the field reads pale rather than brighter
--a-sat 94 Saturation of every stop. Held flat
--a-alpha 1 Master alpha over each stop's own alpha
--a-hue-shift 0 Degrees added to every hue
--a-sweep 0.64 Opacity of the conic band
--a-focus 1 Opacity of the pointer bloom and its derived echo
--a-scrim 0.1 Opacity of the whole-surface veil. Small by design: it grounds the field, it does not carry the text contrast
--a-veil 0.38 Opacity of the reading band's core, which is what does carry the text contrast
--a-fade 1 Layer opacity, used to dip the field through a palette change
--h1 … --h6 per palette The six hues, in degrees
--a-base per palette The ground colour the field sits on
--a-lum-b / --a-sat-b per palette, 0 Lightness and saturation offsets for one palette, so it can be deeper or paler than the others rather than only a different hue

Only --a-hue-shift moves while the field runs; the light values are written every pass but held flat. That is what lets the readband be tuned once and stay correct: a background whose brightness swings has to be measured for contrast at its peak as well as at rest, and the peak is always the one that gets forgotten.

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce the script paints one composed frame and attaches no ticker or pointer listeners. Palette buttons remain operable and change the still instantly, without the cross-fade dip.
  • Without JavaScript: the CSS carries a complete resting state, so the field paints in full. Nothing on the page is hidden waiting for a script.
  • Contrast: the field's light values are held flat, so the reading band can be tuned once. Measured per real glyph pixel across all four palettes, white copy over the field stays at or above 5.9:1, comfortably past the 4.5:1 AA threshold. Text that runs outside the band needs its own patch; see "Putting text over the field" above.
  • Keyboard: the palette controls are ordinary buttons carrying aria-pressed, so the only interactive part of the effect is reachable and announced.
  • Touch: a coarse pointer has no hover, so a tap or a drag places the bloom where the finger landed.
  • Decorative by definition: every layer carries aria-hidden="true" and pointer-events: none. Nothing the effect adds is announced or focusable.

Performance

  • Transforms are set every frame; the gradient properties, which repaint the whole field, are written at about 24fps. At this drift rate the difference is invisible and it roughly halves the paint cost.
  • An IntersectionObserver removes the ticker when the field leaves the viewport, and a visibilitychange listener removes it in a hidden tab.
  • Prefer one field per page. Several large gradient fields painting at once is the one way to make this expensive.

Dependencies

  • GSAP 3.15.0 (core only, no plugins)

Browser Support

Uses modern hsl() slash-alpha syntax, conic-gradient, mix-blend-mode and background-blend-mode: current Chrome, Safari, Firefox and Edge. Older engines fall back to the field's base colour, which is a solid dark ground rather than a broken layout.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.