# WebGL gradient shaders, explained for designers

[Canonical HTML page](https://gradiently.design/guide/shader-gradients)

Some gradients on the web drift, glow and shimmer in a way no image or CSS rule can. They are shaders. Here is how they work, in words a designer can use.

## The short version

- A WebGL gradient is drawn by a shader: a small program that runs on the graphics chip and decides the colour of every pixel, often many times a second.
- Because a shader works out colour from maths rather than reading a stored picture, it can add movement, noise, grain and light at any size without the file getting bigger.
- Shader gradients cost more to run than CSS gradients, so a good one pauses when it is off screen, respects reduced motion and has a plain CSS fallback.
- Use CSS for static blends and simple motion, and reach for a shader when the texture, depth or motion is the point of the design.

A **WebGL gradient** is a gradient that your browser paints with a shader: a short program that runs on the graphics chip (the GPU) and answers one question for every pixel on screen, "what colour are you right now?" A CSS gradient is a fixed recipe of colours and positions. A shader is a formula, so it can bend, grain, glow and move the colour, and do it again sixty times a second, without any image being downloaded.

You don't need to write shaders to design with them, but knowing what they can and can't do helps you brief a developer, judge a tool and decide when a plain [CSS gradient](https://gradiently.design/guide/css-linear-gradient) is the better choice.

## What a shader is, in plain words

WebGL is the browser's way of talking to the GPU, and every WebGL drawing uses two kinds of shader. The **vertex shader** places shapes; for a full-screen gradient that is just one rectangle covering the canvas. The **fragment shader** does the interesting work: it runs once per pixel, receives that pixel's position and the current time, and returns a colour.

Because the GPU runs thousands of these tiny programs in parallel, a formula that would be slow in ordinary code becomes cheap enough to redraw every frame. Here is a complete fragment shader for a three-colour gradient whose boundary sways gently over time, with a line of grain on top.

```text
#version 300 es
precision highp float;

uniform vec2 u_resolution;   // canvas size in pixels
uniform float u_time;        // seconds since start
out vec4 outColor;

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;   // 0 to 1 across the canvas

  vec3 night  = vec3(0.07, 0.04, 0.18);  // #120a2e
  vec3 violet = vec3(0.49, 0.23, 0.93);  // #7c3aed
  vec3 coral  = vec3(0.98, 0.45, 0.45);  // #fa7373

  // A slow wave bends the blend so the colours drift.
  float wave = sin(u_time * 0.3 + uv.x * 3.0) * 0.08;
  float t = clamp(uv.y + wave, 0.0, 1.0);

  vec3 color = mix(night, violet, smoothstep(0.0, 0.6, t));
  color = mix(color, coral, smoothstep(0.55, 1.0, t));

  // Fine grain: a different random value per pixel, per frame.
  float n = fract(sin(dot(gl_FragCoord.xy + u_time, vec2(12.9898, 78.233))) * 43758.5453);
  color += (n - 0.5) * 0.04;

  outColor = vec4(color, 1.0);
}
```

A WebGL2 fragment shader in GLSL. `smoothstep` softens each transition, the sine wave moves the boundary, and the last lines add grain that also hides banding.

- Night ground: #120a2e
- Violet: #7c3aed
- Coral: #fa7373

The three colours in the shader. GLSL writes colour as 0 to 1 per channel, so #7c3aed becomes 124, 58 and 237 divided by 255.

## How a shader gradient differs from a CSS gradient

A CSS gradient is declarative: you list colours and positions, and the browser paints them. It is the right tool most of the time. A shader is procedural: you describe how colour should behave, and the maths produces it. That difference shows up in what each can do and what each costs.

| Aspect | CSS gradient | Shader gradient |
| --- | --- | --- |
| Defined by | Colours, stops and a direction | A formula run per pixel |
| Motion | Moving position, size or animated custom properties | Anything you can express in maths, every frame |
| Texture and grain | Needs an extra layer, such as SVG noise | Built in, and it can move |
| Cost to run | Very low once painted | Keeps the GPU busy while it animates |
| Works without JavaScript | Yes | No: a script sets up the canvas |
| Works in email | Partly, depending on the client | No |

Neither is better in general. Each wins at a different job.

The CSS fallback: `linear-gradient(180deg, #120a2e 0%, #4c1d95 40%, #7c3aed 62%, #fa7373 100%)`

The same palette as a still CSS gradient. Every shader background should ship a fallback like this, shown before the shader starts and whenever it can't run.

## Why shader gradients feel alive

Three techniques do most of the work, and you will hear developers name them. **Noise** is smooth randomness: instead of a straight blend, the colour follows a landscape of soft hills, which is why shader gradients look organic rather than geometric. **Domain warping** feeds noise back into itself, so the colour folds and swirls like ink in water. **Time** slides the whole landscape along, which turns a still field into a drift.

- **Grain** can be generated per pixel and per frame, so it shimmers like film instead of sitting still like a texture laid on top. See [grainy gradients](https://gradiently.design/guide/grainy-gradients).
- **Light** can be faked with a few lines: a highlight that follows the noise reads as sheen on silk or glass.
- **Resolution** is free. The formula runs at whatever size the screen is, so a shader gradient stays crisp on a phone and on a 4K monitor with the same code.
- **Banding** is easier to beat, because the shader can add a little dither before the colour is rounded to 8 bits. [Gradient banding](https://gradiently.design/guide/gradient-banding) explains why that matters.

### Use CSS when

- The gradient is still, or moves in one simple way
- It sits behind lots of text on a long page
- It must work with scripts off, or in email
- You need it in one line of code

### Use a shader when

- The movement or texture is the point of the design
- It is one hero area, not every section
- You want grain, sheen or flow that CSS can't fake
- You can provide a CSS fallback and a pause

## The cost of a moving WebGL background

A shader that animates keeps the GPU working for as long as it is on screen. On a desktop that is barely noticeable. On an older phone it can mean warm hands, a draining battery and a page that scrolls less smoothly. The costs scale with the number of pixels and the complexity of the formula, so a full-screen shader on a high-density display is the heaviest case. [Do gradients slow a website down](https://gradiently.design/guide/css-gradient-performance) covers how to measure it.

1. **Show the CSS fallback first** Paint the still gradient immediately, then fade the canvas in once the shader has drawn its first frame. Nobody should see an empty box.
2. **Check that WebGL is available** Some browsers, devices and privacy settings turn it off. If the context can't be created, keep the fallback.
3. **Respect reduced motion** If the visitor asks for less motion, draw one frame and stop. [Reduced motion](https://gradiently.design/guide/reduced-motion) explains the setting.
4. **Pause when it can't be seen** Stop the animation loop when the canvas scrolls out of view or the tab is hidden.
5. **Render at a sensible resolution** Soft gradients rarely need every device pixel. Drawing at half resolution and letting the browser scale up can cut the work a lot for a blur you can't tell apart.

```js
const canvas = document.querySelector('#hero-canvas')
const gl = canvas.getContext('webgl2')
const still = matchMedia('(prefers-reduced-motion: reduce)').matches
let visible = true

if (!gl) {
  canvas.remove() // the CSS gradient behind it stays
} else {
  new IntersectionObserver(([entry]) => {
    visible = entry.isIntersecting
  }).observe(canvas)

  const frame = (ms) => {
    if (visible && !document.hidden) draw(gl, ms / 1000) // your draw call
    if (!still) requestAnimationFrame(frame)
  }
  requestAnimationFrame(frame)
}
```

The guard rails in a few lines: a fallback when WebGL is missing, one frame only for reduced motion, and no drawing while off screen or in a hidden tab.

For reference, the [WebGL API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) documents every call used here, and [caniuse](https://caniuse.com/webgl2) tracks WebGL2 support across browsers.

## Designing a shader gradient: what to brief

You can direct a shader without reading GLSL. Brief it the way you would brief a material, not a colour swatch. Give the ground colour and the three to five colours that live on it, in hex. Describe the structure: soft pools, long bands, folded silk. Name the speed in seconds per cycle rather than "slow", because slow means different things to different people. Say how much grain, and whether it moves. Finally, say where text will sit, so the busiest part of the field stays away from it.

> **Judge it still first** Pause the shader on a random frame and look at it as a static image. If a still frame doesn't look good, the motion won't save it. Motion should add to a design that already works.

## How Gradiently draws Marks with shaders

Gradiently's Marks are shader gradients. Each Mark is a recipe of colours, up to three stacked materials, light, grain and motion, and the Studio draws it live with WebGL2 behind whatever you design. Because it is computed rather than stored, the same Mark renders sharply at any of the platform sizes and moves when you export a looping video. The renderer also knows where your words are, so it can move the calmest part of the field behind them. [What is a Mark](https://gradiently.design/guide/what-is-a-mark) explains the recipe in full.

A Mark is a shader recipe with a name, a code and one owner. Its page shows it rendering live.

## FAQ

### What is a WebGL gradient?

It is a gradient drawn by a shader, a small program running on the graphics chip that calculates the colour of every pixel. Because it is a formula, it can move, add grain and render at any size.

### Is a shader gradient bad for performance?

A still or paused shader costs little, but an animating one keeps the GPU busy while it is on screen. Pause it when it is out of view, draw at a sensible resolution and keep a CSS fallback.

### Can I make a shader gradient without coding?

Yes. Several design tools generate shader backgrounds from controls, and Gradiently's Forge builds Marks, which are shader gradients, from a sentence or control by control.

### Do shader gradients respect reduced motion settings?

Only if they are built to. Check the prefers-reduced-motion media query in JavaScript and draw a single still frame when it is set.

### What is GLSL?

GLSL is the shading language WebGL uses. Fragment shaders written in it decide the colour of each pixel, and the browser compiles them for the graphics chip.
