Quickstart

By the end of this page you'll have a gradient with a cursor trail running in your app, sized with CSS and driven by state. Pick your framework in any code block and every block follows; plain JavaScript works too.

Installing the package

Shaders is one package for every framework:

npm install shaders

Import the components from the entry point for your framework: shaders/vue, shaders/react, shaders/svelte or shaders/solid. Without a framework, the JavaScript API comes from shaders/js.

Rendering your first effect

Let's start with a gradient and put a cursor trail on top of it. Add this component anywhere in your app:

import { Shader, LinearGradient, CursorTrail } from 'shaders/react'

export default function Hero() {
  return (
    <Shader className="w-full h-64">
      <LinearGradient colorA="#0f172a" colorB="#7c3aed" />
      <CursorTrail />
    </Shader>
  )
}

Move your cursor over the canvas to see the trail.

<Shader> renders a single <canvas>. The components inside it are layers, drawn in order like layers in Figma: the gradient first, then the trail on top. In plain JavaScript you bring the <canvas>, and each entry in components is a layer.

Sizing the canvas

The canvas has no size of its own. It takes whatever size you give <Shader> through class (className in React) or style:

{/* Fill the viewport */}
<Shader className="w-screen h-screen">
  <LinearGradient />
</Shader>

{/* Fixed size */}
<Shader style={{ width: 600, height: 400 }}>
  <LinearGradient />
</Shader>

{/* Full width, 16:9 */}
<Shader className="w-full aspect-video">
  <LinearGradient />
</Shader>

The canvas resizes itself when its CSS size changes, so you don't need to call anything on resize. Style <Shader>, not the <canvas> inside it: the internal markup can change between releases. In plain JavaScript, call shader.resize() if you change the canvas size from script.

Setting props

Each component takes props the same way any component in your framework does. In Vue, attributes are kebab-case and non-string values need the : binding; in plain JavaScript they go in each component's props object:

<Shader className="w-full h-64">
  <LinearGradient
    colorA="#ff6b6b"
    colorB="#4ecdc4"
    angle={45}
    colorSpace="oklch"
  />
</Shader>

colorSpace="oklch" blends the two colors through a perceptual color space, so the middle of the gradient stays bright instead of going gray. Every component's props, with defaults and ranges, are in the component docs, and the design editor exports any effect you build as code.

Driving a prop from state

Props accept your framework's state like any other prop. In plain JavaScript, give the component an id and pass new props to shader.update(). We'll wire the gradient's angle to a range input:

import { useState } from 'react'
import { Shader, LinearGradient } from 'shaders/react'

export default function GradientDial() {
  const [angle, setAngle] = useState(0)

  return (
    <>
      <Shader className="w-full h-64">
        <LinearGradient colorA="#ff6b6b" colorB="#4ecdc4" angle={angle} />
      </Shader>

      <input
        type="range"
        min={0}
        max={360}
        value={angle}
        onChange={e => setAngle(Number(e.target.value))}
      />
    </>
  )
}

Drag the slider and the gradient turns on the next frame. Changing angle updates a value on the GPU; the effect doesn't recompile.

Cleaning up

The framework components clean up after themselves: when <Shader> unmounts, rendering stops and its GPU memory is freed. In plain JavaScript, call destroy() when the canvas leaves the page, for example on a route change in a single-page app:

shader.destroy()

Using server rendering

Shaders needs a browser with a GPU, so it can't render on the server. Each meta-framework handles this differently:

  • Next.js: put 'use client' at the top of any file that renders a <Shader>. See Using Next.js.
  • Nuxt: wrap the <Shader> in <ClientOnly>. See Using Nuxt.
  • SvelteKit: nothing to do, because <Shader> renders nothing on the server and starts on the client. See Using SvelteKit.
  • SolidStart: load the component that renders a <Shader> with clientOnly. See Using SolidStart.

Next steps