Documentation

Quickstart (JavaScript)

Shaders provides a vanilla JavaScript API that works with any bundler-based JS environment (Vite, Webpack, etc.), Web Components, or plain HTML with an ESM-compatible setup — no framework required.

Install the npm package

npm install shaders

Set up a canvas

Create a <canvas> element in your HTML and give it a size:

<canvas id="my-shader" style="width: 100%; height: 400px;"></canvas>

Your first shader

Import createShader, pass it the canvas and a list of components:

import { createShader } from 'shaders/js'

const canvas = document.getElementById('my-shader')

const shader = await createShader(canvas, {
  components: [
    { type: 'LinearGradient', props: { colorA: '#0f172a', colorB: '#7c3aed' } },
    { type: 'CursorTrail', props: {} }
  ]
})

The components array defines your visual layers, evaluated top to bottom, blended together on the GPU. In this example, we render the linear gradient first, and then the cursor trail effect on top of that.

Sizing the canvas

The <canvas> has no intrinsic size. Set dimensions via CSS — the shader tracks them automatically via ResizeObserver:

<!-- Fixed size -->
<canvas id="shader" style="width: 600px; height: 400px;"></canvas>

<!-- Full viewport -->
<canvas id="shader" style="width: 100vw; height: 100dvh;"></canvas>

<!-- Fluid with aspect ratio (using Tailwind or equivalent CSS) -->
<canvas id="shader" class="w-full aspect-video"></canvas>

If you resize the canvas programmatically via JavaScript after initialization, call shader.resize() to sync:

canvas.style.height = '600px'
shader.resize()

Configuring components

Props are passed in the props object for each component. All prop names are camelCase:

const shader = await createShader(canvas, {
  components: [
    {
      type: 'LinearGradient',
      props: {
        colorA: '#ff6b6b',
        colorB: '#4ecdc4',
        angle: 45,
        colorSpace: 'oklch'
      }
    }
  ]
})

Updating at runtime

Assign an id to any component, then use shader.update() to change its props at any time:

const shader = await createShader(canvas, {
  components: [
    { type: 'LinearGradient', id: 'gradient', props: { colorA: '#0f172a', colorB: '#7c3aed', angle: 0 } }
  ]
})

// Hook up a slider
document.querySelector('#angle-slider').addEventListener('input', e => {
  shader.update('gradient', { angle: Number(e.target.value) })
})

When update() is called, the shader updates on the GPU instantly — no recompilation, no flicker.

Browse the Component Docs for the full prop reference on every component.

Cleanup

When you're done, call destroy() to free GPU resources:

shader.destroy()

Always destroy shaders when navigating away — for example, in a single-page app's route change handler.

Next steps