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.

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.

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.

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.

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:

<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. 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.

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 steps