Animated hero backgrounds in Next.js
Add an animated background behind a Next.js App Router hero: choose DOM, canvas or WebGL, stack content on top, keep text readable and spare the battery.
Published by Mekai Games
A hero background is the layer behind your first headline. In a Next.js App Router project it is a client component placed inside a server-rendered section, with your content stacked on top. This guide builds that section with the free backgrounds from Mekai UI and covers the choices that affect readability, page weight and battery.
Pick a background by how it draws
The backgrounds draw in three different ways. When you think about cost, the drawing method matters more than the look.
- DOM and CSS: Aurora animates six blurred, gradient-filled elements with Motion. There is no canvas and no drawing code of its own.
- 2D canvas: Dot Grid, Meteors and Magnet Lines redraw a
<canvas>on every animation frame. The work grows with what they draw: dots, meteors or lines. - WebGL: Plasma, Grainient and Orb run a fragment shader for every pixel of the canvas on every frame. The work grows with the canvas size, and a full-width hero is a lot of pixels.
WebGL gives the richest color fields, and it is the group to budget for on phones and on laptops running on battery. Each WebGL background has a quality prop for that, covered below. If the hero only needs soft color, Aurora avoids canvas and shader work entirely. If you want the pointer to play a part, Dot Grid and Magnet Lines react to it without a shader.
Aurora
BackgroundsMeteors
BackgroundsDot Grid
BackgroundsPlasma
BackgroundsGrainient
BackgroundsOrb
BackgroundsAll seven backgrounds named above are free. Pro adds more WebGL options with the same props pattern, quality included: Silk for slow folds of light, Liquid Chrome for reflective metal that ripples on click, and Iridescence for an oily rainbow sheen. The full set is in Backgrounds.
Install the component
npx shadcn@latest add https://ui.mekaigames.com/r/aurora.jsonThe shadcn CLI writes components/ui/aurora.tsx into your project and installs its dependency, motion. Swap the slug for any other free background. Pro components come from a private GitHub repository after purchase, and you copy the file into components/ui the same way. Installation has the details, including setup without the CLI.
The layout: one section, four layers
Every background in the set positions itself with absolute inset-0 and fills its nearest positioned parent. That gives you a simple stack:
- The section:
relativeso the background has something to fill, a minimum height, and a solid background color. - The background: the component itself, first inside the section.
- A scrim: an optional gradient between the background and the text.
- The content:
relative z-10, so it paints above the absolutely positioned layers.
import Link from "next/link";
import { Aurora } from "@/components/ui/aurora";
export default function HomePage() {
return (
<main>
<section className="relative isolate flex min-h-[640px] items-center bg-neutral-950 px-6">
<Aurora colors={["#ff784f", "#ffc24a", "#e2552b"]} speed={0.8} blur={80} opacity={0.55} />
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 bg-linear-to-b from-neutral-950/10 via-neutral-950/40 to-neutral-950"
/>
<div className="relative z-10 mx-auto max-w-3xl text-center">
<h1 className="text-5xl font-black tracking-tight text-white sm:text-7xl">
Launch pages for indie games
</h1>
<p className="mx-auto mt-6 max-w-xl text-lg text-neutral-300">
Trailers, wishlist buttons and press kits on one page, built with React and Tailwind.
</p>
<div className="mt-10 flex justify-center gap-3">
<Link href="/signup" className="rounded-full bg-white px-6 py-3 font-semibold text-neutral-950">
Start a page
</Link>
<Link href="/examples" className="rounded-full border border-white/20 px-6 py-3 font-semibold text-white">
See examples
</Link>
</div>
</div>
</section>
</main>
);
}Without relative on the content wrapper, the headline would sit under the background, because the browser paints absolutely positioned elements after normal, non-positioned content. isolate gives the section its own stacking context, so the z-10 stays local and never competes with a sticky header elsewhere on the page.
The background needs no extra classes. Each one sets pointer-events-none on itself, so the links stay clickable. The pointer-reactive ones (Dot Grid, Magnet Lines, Plasma, Grainient and Orb) listen for pointer movement on window and check whether it falls inside their own box, so they keep reacting while the cursor is over your headline.
Keep the headline readable
A moving background changes the contrast under your text from frame to frame. Check the headline against the brightest moment of the animation, not a still screenshot, and aim for the WCAG minimums: 4.5:1 for body text and 3:1 for large headings.
- Add a scrim. The gradient in the example darkens toward the bottom, where the paragraph and buttons sit. For centered layouts, a radial gradient behind the text works too.
- Fade the background out. Every background accepts
className, so Tailwind 4 mask utilities apply directly:<Plasma className="mask-b-from-40%" />fades the field into the section color. - Turn it down. Aurora and Plasma have an
opacityprop. Grainient'swarpsets how much the colors swirl, andlens={0}on Plasma removes the magnifying lens that would otherwise follow the cursor across your text. - Keep one color near the section color. Grainient blends two to four colors, and its default palette includes a dark fourth color,
#2d2824, which leaves calmer areas in the field.
Give the section a background color that matches the darkest tone of the effect. Canvas and WebGL backgrounds draw only after the client code runs, and the WebGL ones fall back to a CSS gradient when WebGL is unavailable, so the section color is what visitors see first. Aurora's blobs are regular elements with inline gradients, so they are already in the server-rendered HTML.
Server page, client background
In the App Router, page.tsx is a Server Component by default. Every Mekai UI background begins with "use client" because it uses hooks, refs and browser APIs, so you import it straight into a server page, as in the example above. Don't add "use client" to the page itself: the heading, paragraph and links stay server-rendered, reach the browser as HTML and add no JavaScript.
Props that cross from a Server Component into a client component must be serializable. Background props are all strings, numbers, booleans or arrays of them, so you can set them in page.tsx. When a prop depends on the browser, such as screen width, put the background in a small client wrapper and import the wrapper instead:
"use client";
import { useSyncExternalStore } from "react";
import { Plasma } from "@/components/ui/plasma";
const SMALL_SCREEN = "(max-width: 767px)";
function subscribe(onChange: () => void) {
const query = window.matchMedia(SMALL_SCREEN);
query.addEventListener("change", onChange);
return () => query.removeEventListener("change", onChange);
}
export function HeroPlasma() {
const small = useSyncExternalStore(
subscribe,
() => window.matchMedia(SMALL_SCREEN).matches,
() => false,
);
return (
<Plasma
colors={["#ff784f", "#ffc24a", "#e2552b"]}
opacity={0.8}
quality={small ? 0.5 : 1}
className="mask-b-from-40%"
/>
);
}The server snapshot returns false, so the server HTML and the first client render match, and the lower setting applies right after hydration. Use <HeroPlasma /> in the page exactly where <Aurora /> sits in the first example.
Performance and battery
The components already handle the expensive cases. Knowing what they do tells you which knobs are left to turn.
- Off-screen pause: Dot Grid, Meteors, Magnet Lines, Plasma, Grainient and Orb watch their canvas with an
IntersectionObserver, stop their animation loop when it leaves the viewport and start it again when it returns. The Pro WebGL backgrounds do the same, so a hero that has scrolled away stops drawing. - Pixel ratio cap: every canvas renders at no more than 2x device pixel ratio, even on 3x phones.
qualityon WebGL: Plasma, Grainient and Orb (and Silk, Liquid Chrome and Iridescence) multiply their render resolution byquality, from 0.25 to 1. At 0.5 on a 2x screen the shader fills a quarter of the pixels and the browser scales the canvas up. Fine detail, such as Grainient's film grain, gets coarser as you lower it.- Less to draw on 2D canvas: fewer meteors with
count, a widergapon Dot Grid, or fewerrowsandcolumnson Magnet Lines all cut the work per frame. - GPU handling: the WebGL backgrounds ask the browser for a low-power GPU, show a CSS gradient if WebGL is missing or the context is lost, and release the GPU context when the component unmounts.
Reduced motion needs no extra code. Each background reads prefers-reduced-motion through Motion's useReducedMotion hook. When it is set, the canvas and WebGL backgrounds draw one still frame and stop, Meteors freezes a few meteors mid-flight, Magnet Lines turns its lines toward the pointer without animating, and Aurora leaves its blobs where they start.
Keep one animated background per screen. Two WebGL canvases in view at once double the shader work, and two moving layers compete with the headline for attention.
Components in this guide
More guides
- Text animations in React: which one to usePick a React text animation by its job: entrance, scroll reveal, CTA highlight, stats or decoding state, with screen reader text and reduced motion built in.
- Gamification UI in React: XP, streaks, leaderboardsBuild XP bars, streaks, daily rewards, achievements, leaderboards and loot reveals in React, and learn when each one helps users and when it turns into noise.
- Respecting prefers-reduced-motion in ReactRead prefers-reduced-motion in React with Motion or CSS, decide what reduced should mean, pause canvas and WebGL loops, and test the setting in Chrome DevTools.
- Install React components from a shadcn registryHow the shadcn CLI installs components from a custom registry URL, what lands in your project, and how to add, update and edit Mekai UI components.