Guide
How to add an animated shader background to a Next.js hero
A moving mesh gradient or grain shader can give a landing page hero real depth, but it is easy to end up with unreadable text, a busy GPU or a background that ignores motion preferences. This guide covers the parts that matter in Next.js, using the beUI ShaderBackground component, which wraps the Paper Design shaders behind one typed variant prop.
Install the beUI Shader Background
Add the component with the shadcn CLI. It installs the source to components/motion/shader-background.tsx and adds its dependencies, @paper-design/shaders-react and Framer Motion, to your package.json.
npx shadcn@latest add @beui/shader-backgroundWhere does the client component boundary go?
WebGL needs the browser, so the shader file starts with "use client". You do not need to make your page a client component to use it. A Server Component page can render ShaderBackground directly as long as the props are serializable, and colors as hex strings and numeric settings all are. Keep your headline and copy in the server-rendered part of the page so they are in the initial HTML.
ShaderBackground takes a variant such as mesh-gradient, grain-gradient, warp, waves, voronoi or dot-orbit, plus the props that variant accepts. The types are a discriminated union, so your editor only suggests the props that belong to the variant you picked.
Position the shader behind the hero content
Make the section relative and isolate, then place the shader in an absolutely positioned layer with inset-0 and a negative z-index. The isolate class creates a stacking context, so the negative z-index stays inside the section instead of slipping behind the page background. ShaderBackground fills its parent with h-full w-full, so the wrapper decides its size.
Mark the wrapper aria-hidden because the shader is decorative and has nothing for assistive technology to read.
import { ShaderBackground } from "@/components/motion/shader-background";
export default function Page() {
return (
<section className="relative isolate overflow-hidden">
{/* Decorative layer: behind the content and hidden from assistive tech. */}
<div aria-hidden="true" className="absolute inset-0 -z-10">
<ShaderBackground
variant="mesh-gradient"
colors={["#e0eaff", "#241d9a", "#f75092", "#9f50d3"]}
distortion={0.8}
swirl={0.3}
speed={0.4}
/>
{/* A scrim keeps headline contrast steady across every frame. */}
<div className="absolute inset-0 bg-black/45" />
</div>
<div className="mx-auto max-w-3xl px-6 py-32 text-center text-white">
<h1 className="text-5xl font-semibold tracking-tight">Ship faster</h1>
<p className="mt-4 text-lg text-white/80">A short line that stays readable.</p>
</div>
</section>
);
}Keep text readable over a moving gradient
A gradient that animates will pass through light and dark areas, so contrast that works on one frame can fail on the next. Check your text against the lightest and darkest colors in the palette, not just the average.
The simplest fix is a semi-transparent scrim between the shader and the text, like the bg-black/45 layer above. Choosing a palette that stays within one lightness range, keeping speed low and using larger, heavier type for the headline all help as well.
Shader background performance tips
The cost of a fragment shader scales with the number of pixels it draws every frame. The underlying Paper Design mount accepts maxPixelCount and minPixelRatio, and ShaderBackground passes them through, so you can cap the drawing buffer for a full-bleed hero. Its animation loop also pauses while the browser tab is hidden.
A few more habits keep it light: use one shader per page rather than several, prefer lower speed values, and for sections far down the page consider a static variant such as static-mesh-gradient or static-radial-gradient, which renders a gradient without animating it.
<ShaderBackground
variant="grain-gradient"
colors={["#7300ff", "#eba8ff", "#00bfff", "#2a00ff"]}
colorBack="#000000"
softness={0.6}
speed={0.5}
// Cap the drawing buffer: fewer pixels means less GPU work per frame.
maxPixelCount={1280 * 720 * 2}
/>Reduced motion for shader backgrounds
ShaderBackground reads useReducedMotion from Framer Motion and sets speed to 0 when the user prefers reduced motion, so the gradient freezes on a still frame instead of disappearing. This applies to every animated variant, whether or not you pass speed yourself.
Freezing keeps the visual design intact for everyone while removing continuous movement, which can be uncomfortable for people with vestibular disorders.
Components in this guide
Shader Background
Canvas shader backgrounds (mesh gradient, grain, warp, waves, voronoi, dot orbit and more) with a single typed variant prop. Reduced-motion freezes animated variants.
Text Animation
Animated text primitives for spring reveals, chromatic sweeps, shimmer loading states, letter-cascade swaps and character scrambles.
Marquee
Infinite horizontal or vertical scroll with pause-on-hover.
FAQ
- Can I use a WebGL shader background in a Next.js Server Component?
- The shader itself is a client component, but you can render it from a Server Component page and pass serializable props like color strings and numbers. Your text stays server-rendered.
- How do I make a mesh gradient background in React?
- Install the beUI Shader Background and render it with variant="mesh-gradient", a colors array of hex values, and optional distortion, swirl and speed props.
- Does the shader background respect prefers-reduced-motion?
- Yes. The component sets speed to 0 on every animated variant for users who prefer reduced motion, which freezes the shader on a still frame.
- Why is my shader background on top of my text?
- The shader layer needs a lower stacking order than the content. Put it in an absolute inset-0 wrapper with -z-10 and add isolate to the section so the negative z-index stays contained.
- Will an animated shader slow down my page?
- It uses the GPU on every frame, so cost grows with screen size. Cap the drawing buffer with maxPixelCount, keep one shader per page and use a static variant where motion adds little.
Updated