npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fjandin/react-shader

v0.0.30

Published

React component for rendering WebGPU shaders with WGSL

Readme

@fjandin/react-shader

A React component library for rendering WebGPU shaders. Provides <ReactGpuShader> for WGSL with automatic uniform handling, storage buffers, and audio reactivity.

Installation

npm install @fjandin/react-shader
# or
yarn add @fjandin/react-shader
# or
bun add @fjandin/react-shader

Quick Start

import { ReactGpuShader } from "@fjandin/react-shader"

const fragment = /*wgsl*/ `
fn mainImage(uv: vec2f) -> vec4f {
  let color = 0.5 + 0.5 * cos(uniforms.iTime + vec3f(uv, 0.0) + vec3f(0.0, 2.0, 4.0));
  return vec4f(color, 1.0);
}
`

function App() {
  return (
    <div style={{ width: "800px", height: "600px" }}>
      <ReactGpuShader fragment={fragment} />
    </div>
  )
}

Shaders define a mainImage(uv: vec2f) -> vec4f function. Built-in uniforms are accessed via uniforms.iTime, uniforms.iResolution, etc. The component automatically generates the uniform struct and wrapping @fragment entry point.

Props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | fragment | string | Yes | - | WGSL fragment shader source code | | uniforms | Record<string, GpuUniformValue> | No | {} | Custom uniform values (number, Vec2, Vec3, Vec4) | | storageBuffers | Record<string, Vec4Array> | No | - | Named storage buffers of Vec4 arrays | | className | string | No | - | CSS class name for the canvas | | fullscreen | boolean | No | false | Render as fixed fullscreen overlay | | timeScale | number | No | 1 | Scale factor for elapsed time | | onFrame | (info: FrameInfo) => void | No | - | Callback invoked on each frame | | onClick | (info: FrameInfo) => void | No | - | Callback invoked on canvas click | | onMouseMove | (info: FrameInfo) => void | No | - | Callback invoked on mouse move | | onMouseDown | (info: FrameInfo) => void | No | - | Callback invoked on mouse button press | | onMouseUp | (info: FrameInfo) => void | No | - | Callback invoked on mouse button release | | onMouseWheel | (info: FrameInfo, wheelDelta: number) => void | No | - | Callback invoked on mouse wheel scroll |

Built-in Uniforms

These uniforms are automatically provided to your shader every frame:

| Uniform | WGSL Type | Description | |---------|-----------|-------------| | iTime | f32 | Elapsed time in seconds (scaled by timeScale) | | iMouse | vec2f | Mouse position in pixels (Y=0 at bottom) | | iMouseNormalized | vec2f | Mouse position normalized with aspect correction (shorter axis -0.5 to 0.5, center is 0,0) | | iMouseLeftDown | f32 | 1.0 when left mouse button is pressed, 0.0 otherwise | | iResolution | vec2f | Canvas resolution in pixels (includes high-DPI scaling) |

Custom Uniforms

Custom uniforms support scalar and vector types:

<ReactGpuShader
  fragment={fragment}
  uniforms={{
    scale: 2.0,            // f32
    offset: [0.5, 0.5],   // vec2f
    color: [1.0, 0.5, 0.2], // vec3f
    transform: [1, 0, 0, 1], // vec4f
  }}
/>

Custom uniforms are accessed via uniforms.scale, uniforms.color, etc. in your WGSL code.

Storage Buffers

For large dynamic array data, storage buffers are more efficient than uniforms and support dynamic resizing:

const [particles, setParticles] = useState<Vec4Array>([
  [0, 0, 0.5, 1],
  [1, 1, 0.3, 0.8],
])

<ReactGpuShader
  fragment={fragment}
  storageBuffers={{ particles }}
/>

In WGSL, storage buffers are declared as array<vec4f> and accessed by name:

fn mainImage(uv: vec2f) -> vec4f {
  for (var i: u32 = 0; i < arrayLength(&particles); i++) {
    let p = particles[i];
    // Use p.xy as position, p.z as radius, p.w as intensity...
  }
  return vec4f(0.0);
}

Storage buffers use over-allocation (1.5x growth) to minimize GPU buffer rebuilds when array sizes change frequently.

Audio Reactivity

The useAudio hook provides real-time audio analysis for audio-reactive shaders:

import { ReactGpuShader, useAudio } from "@fjandin/react-shader"

function AudioVisualizer() {
  const audio = useAudio({
    source: "microphone", // "microphone" | "element" | "display"
    smoothing: 0.9,       // 0-1, lerp factor between frames
  })

  return (
    <>
      <button onClick={() => audio.isRunning ? audio.stop() : audio.start()}>
        {audio.isRunning ? "Stop" : "Start"}
      </button>
      <ReactGpuShader
        fragment={fragment}
        uniforms={{
          audioLow: audio.levels.low,
          audioMid: audio.levels.mid,
          audioHigh: audio.levels.high,
        }}
      />
    </>
  )
}

useAudio options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | source | "microphone" \| "element" \| "display" | "microphone" | Audio input source | | mediaElement | HTMLAudioElement \| HTMLVideoElement | - | Media element (when source is "element") | | fftSize | number | 2048 | FFT size for frequency analysis | | smoothingTimeConstant | number | 0.8 | AnalyserNode smoothing | | smoothing | number | 0.9 | Frame-to-frame lerp factor (0 = instant, 0.9 = very smooth) |

useAudio return value:

| Field | Type | Description | |-------|------|-------------| | levels | AudioLevels | { low, mid, high, bands } - normalized 0-1 frequency levels | | frequencyData | Uint8Array \| null | Raw frequency data | | state | AudioConnectionState | "disconnected" \| "connecting" \| "connected" \| "error" | | error | Error \| null | Connection error if any | | start | () => Promise<void> | Start audio capture | | stop | () => void | Stop audio capture | | isRunning | boolean | Whether audio is currently capturing |

Shader Helper Functions

Pre-built WGSL shader functions are available:

import {
  generateSimplexNoiseFunctionGpu,
  generateColorPaletteFunctionGpu,
  generateDistortionRippleFunctionGpu,
  generateSceneCirclesFunctionGpu,
} from "@fjandin/react-shader"

Inject them into your shader source:

const fragment = /*wgsl*/ `
${generateSimplexNoiseFunctionGpu()}
fn mainImage(uv: vec2f) -> vec4f {
  let n = SimplexNoise3D(vec3f(uv, uniforms.iTime));
  return vec4f(vec3f(n), 1.0);
}
`

Frame Callback

Use the onFrame callback to update uniforms based on animation timing:

function App() {
  const [customTime, setCustomTime] = useState(0)

  return (
    <ReactGpuShader
      fragment={fragment}
      uniforms={{ customTime }}
      onFrame={(info) => {
        setCustomTime((prev) => prev + info.deltaTime * 0.5)
      }}
    />
  )
}

The FrameInfo object contains:

  • deltaTime - Time since last frame in seconds
  • time - Total elapsed time in seconds
  • resolution - Canvas resolution as [width, height]
  • mouse - Mouse position as [x, y]
  • mouseNormalized - Aspect-corrected mouse position as [x, y]
  • mouseLeftDown - Whether left mouse button is pressed

TypeScript

All types are exported:

import type {
  Vec2,
  Vec3,
  Vec4,
  Vec2Array,
  Vec3Array,
  Vec4Array,
  GpuUniformValue,
  GpuStorageBuffers,
  DefaultUniforms,
  FrameInfo,
  ReactGpuShaderProps,
  AudioLevels,
  AudioConnectionState,
  AudioSourceType,
  UseAudioOptions,
  UseAudioReturn,
} from "@fjandin/react-shader"

Features

  • WebGPU rendering with WGSL shaders
  • Storage buffers for large dynamic arrays
  • Audio reactivity via useAudio hook
  • Pre-built shader functions (simplex noise, color palettes, distortion ripples, circles)
  • High-DPI display support with automatic DPR change detection
  • Automatic canvas resizing
  • Shader compilation error display
  • Mouse tracking with WebGPU coordinate convention
  • Optimized render loop with minimal per-frame allocations

Requirements

  • React >= 17.0.0
  • A browser with WebGPU support

License

MIT