Shader Lab
Set up @basementstudio/shader-lab — a React runtime that plays compositions authored in the Shader Lab editor. Compositions can stack text layers, image layers, and custom TSL (Three Shading Language) shader layers, with a timeline, and can be used as a DOM element, as a texture in a three.js scene, or as a post-processing pass.
Full suite, not just text. The same runtime renders any composition the editor can export.
When to use
- User wants a bespoke shader-driven hero, background, or accent on a landing page.
- User wants to reuse compositions they already built (or will build) in the Shader Lab editor at https://eng.basement.studio/tools/shader-lab.
- User wants post-processing (bloom, grain, distortion) over an existing three.js / R3F scene.
Before installing, confirm browser scope
This runtime requires WebGPU. That means:
- Chrome, Edge: supported
- Safari 18+: supported
- Firefox: behind a flag as of early 2026
Ask the user if a WebGPU-only effect is acceptable for their audience. If not, either:
- Build a CSS / canvas2D fallback for non-WebGPU browsers and gate the shader behind a capability check, or
- Use a different tool (react-three-fiber + fragment shader on a mesh is a WebGL alternative with broader reach).
Do not skip this check — shipping a silently-broken hero on Firefox is worse than not shipping it.
Steps
Check if already installed
- Look for
@basementstudio/shader-lab in package.json dependencies.
- If found, skip install and move to step 4.
Detect package manager
bun.lock or bun.lockb → bun
pnpm-lock.yaml → pnpm
yarn.lock → yarn
- else → npm
Install the runtime and three.js
bun add @basementstudio/shader-lab three
bun add -d @types/three
(substitute npm install / pnpm add / yarn add per manager)
Create a compositions directory
Recommended path: src/shaders/compositions/ (or app/_shaders/compositions/ in pure App Router projects).
Each composition lives in its own file as a typed config. Structure:
src/shaders/
├── compositions/
│ ├── hero.ts
│ ├── accent-warp.ts
│ └── background-flow.ts
└── ShaderComposition.tsx // thin client wrapper around ShaderLabComposition
Add the client wrapper
ShaderLabComposition and the hooks are client-side only. In Next.js App Router, wrap them in a "use client" component so server components can import it freely.
src/shaders/ShaderComposition.tsx:
"use client";
import { ShaderLabComposition } from "@basementstudio/shader-lab";
import type { ComponentProps } from "react";
type Config = ComponentProps<typeof ShaderLabComposition>["config"];
export function ShaderComposition({
config,
onRuntimeError,
}: {
config: Config;
onRuntimeError?: (message: string) => void;
}) {
return (
<ShaderLabComposition
config={config}
onRuntimeError ??
((message) => {
if (process.env.NODE_ENV !== "production") {
console.error("[shader-lab]", message);
}
})
}
/>
);
}
Add a WebGPU capability guard
src/shaders/useSupportsWebGPU.ts:
"use client";
import { useEffect, useState } from "react";
export function useSupportsWebGPU(): boolean | null {
const [supported, setSupported] = useState<boolean | null>(null);
useEffect(() => {
const gpu = (navigator as Navigator & { gpu?: unknown }).gpu;
setSupported(Boolean(gpu));
}, []);
return supported;
}
Usage on a page:
"use client";
import { ShaderComposition } from "@/shaders/ShaderComposition";
import { useSupportsWebGPU } from "@/shaders/useSupportsWebGPU";
import { heroConfig } from "@/shaders/compositions/hero";
import { HeroFallback } from "@/components/HeroFallback";
export function Hero() {
const supported = useSupportsWebGPU();
if (supported === null) return null; // avoid flash during detection
if (!supported) return <HeroFallback />;
return <ShaderComposition config={heroConfig} />;
}
Author compositions
The primary way to build a composition is in the Shader Lab editor at https://eng.basement.studio/tools/shader-lab. Export gives you a config object you can paste into a file under src/shaders/compositions/.
Minimal handwritten example for reference:
import type { ComponentProps } from "react";
import type { ShaderLabComposition } from "@basementstudio/shader-lab";
type Config = ComponentProps<typeof ShaderLabComposition>["config"];
export const heroConfig: Config = {
layers: [
{ type: "text", content: "Your text" },
// stack a custom shader layer above for effects:
// { type: "customShader", sketch: yourSketchFn },
],
timeline: { duration: 6, loop: true, tracks: [] },
};
Custom shader layers use TSL (Three Shading Language) — a Fn()-wrapped function that returns a TSL node. Injected globals include time, inputTexture, and noise / tonemapping / SDF helpers. Write these in the editor first, then export.
Advanced: compositions as textures or post-processing
- Composition as a texture in an R3F scene: use
useShaderLab(config, { width, height, pixelRatio }) and pass the returned texture to a material.
- Post-processing over an existing renderer:
useShaderLab(config, { renderer }) and drive it with postprocessing.render(inputTexture, time, delta) in your render loop.
- Manual render loop:
useShaderLabCanvasSource / useShaderLabPostProcessingSource for full control.
These are client-side, same "use client" rule applies.
Where configs live
Treat composition configs as content, not code. They belong in a dedicated directory (src/shaders/compositions/) next to any per-composition sketch functions. Reuse configs across projects by copy or by referencing a shared package if the studio maintains one.
Troubleshooting
- Blank output, no error: likely WebGPU not available. Add the capability guard above.
- "Runtime error" callback fires: inspect the message, most common cause is an invalid sketch function or an unsupported TSL node. Reopen the composition in the Shader Lab editor and re-export.
- SSR hydration warning: the component is client-only. Ensure the parent is marked
"use client" or imported via dynamic(() => ..., { ssr: false }).
- Three.js version conflict:
three is a peer dep. If another package pins an incompatible version, resolve with overrides / resolutions in package.json.
References
1---2name: shader-lab3description: Shader Lab4---56# Shader Lab78Set up `@basementstudio/shader-lab` — a React runtime that plays compositions authored in the Shader Lab editor. Compositions can stack text layers, image layers, and custom TSL (Three Shading Language) shader layers, with a timeline, and can be used as a DOM element, as a texture in a three.js scene, or as a post-processing pass.910Full suite, not just text. The same runtime renders any composition the editor can export.1112## When to use1314- User wants a bespoke shader-driven hero, background, or accent on a landing page.15- User wants to reuse compositions they already built (or will build) in the Shader Lab editor at https://eng.basement.studio/tools/shader-lab.16- User wants post-processing (bloom, grain, distortion) over an existing three.js / R3F scene.1718## Before installing, confirm browser scope1920This runtime requires **WebGPU**. That means:2122- Chrome, Edge: supported23- Safari 18+: supported24- Firefox: behind a flag as of early 20262526Ask the user if a WebGPU-only effect is acceptable for their audience. If not, either:2728- Build a CSS / canvas2D fallback for non-WebGPU browsers and gate the shader behind a capability check, or29- Use a different tool (react-three-fiber + fragment shader on a mesh is a WebGL alternative with broader reach).3031Do not skip this check — shipping a silently-broken hero on Firefox is worse than not shipping it.3233## Steps34351. **Check if already installed**36 - Look for `@basementstudio/shader-lab` in `package.json` dependencies.37 - If found, skip install and move to step 4.38392. **Detect package manager**40 - `bun.lock` or `bun.lockb` → bun41 - `pnpm-lock.yaml` → pnpm42 - `yarn.lock` → yarn43 - else → npm44453. **Install the runtime and three.js**46 ```bash47 bun add @basementstudio/shader-lab three48 bun add -d @types/three49 ```50 (substitute `npm install` / `pnpm add` / `yarn add` per manager)51524. **Create a compositions directory**5354 Recommended path: `src/shaders/compositions/` (or `app/_shaders/compositions/` in pure App Router projects).5556 Each composition lives in its own file as a typed config. Structure:57 ```58 src/shaders/59 ├── compositions/60 │ ├── hero.ts61 │ ├── accent-warp.ts62 │ └── background-flow.ts63 └── ShaderComposition.tsx // thin client wrapper around ShaderLabComposition64 ```65665. **Add the client wrapper**6768 `ShaderLabComposition` and the hooks are client-side only. In Next.js App Router, wrap them in a `"use client"` component so server components can import it freely.6970 `src/shaders/ShaderComposition.tsx`:71 ```tsx72 "use client";7374 import { ShaderLabComposition } from "@basementstudio/shader-lab";75 import type { ComponentProps } from "react";7677 type Config = ComponentProps<typeof ShaderLabComposition>["config"];7879 export function ShaderComposition({80 config,81 onRuntimeError,82 }: {83 config: Config;84 onRuntimeError?: (message: string) => void;85 }) {86 return (87 <ShaderLabComposition88 config={config}89 onRuntimeError={90 onRuntimeError ??91 ((message) => {92 if (process.env.NODE_ENV !== "production") {93 console.error("[shader-lab]", message);94 }95 })96 }97 />98 );99 }100 ```1011026. **Add a WebGPU capability guard**103104 `src/shaders/useSupportsWebGPU.ts`:105 ```tsx106 "use client";107108 import { useEffect, useState } from "react";109110 export function useSupportsWebGPU(): boolean | null {111 const [supported, setSupported] = useState<boolean | null>(null);112113 useEffect(() => {114 const gpu = (navigator as Navigator & { gpu?: unknown }).gpu;115 setSupported(Boolean(gpu));116 }, []);117118 return supported;119 }120 ```121122 Usage on a page:123 ```tsx124 "use client";125126 import { ShaderComposition } from "@/shaders/ShaderComposition";127 import { useSupportsWebGPU } from "@/shaders/useSupportsWebGPU";128 import { heroConfig } from "@/shaders/compositions/hero";129 import { HeroFallback } from "@/components/HeroFallback";130131 export function Hero() {132 const supported = useSupportsWebGPU();133 if (supported === null) return null; // avoid flash during detection134 if (!supported) return <HeroFallback />;135 return <ShaderComposition config={heroConfig} />;136 }137 ```1381397. **Author compositions**140141 The primary way to build a composition is in the **Shader Lab editor** at https://eng.basement.studio/tools/shader-lab. Export gives you a config object you can paste into a file under `src/shaders/compositions/`.142143 Minimal handwritten example for reference:144 ```ts145 import type { ComponentProps } from "react";146 import type { ShaderLabComposition } from "@basementstudio/shader-lab";147148 type Config = ComponentProps<typeof ShaderLabComposition>["config"];149150 export const heroConfig: Config = {151 layers: [152 { type: "text", content: "Your text" },153 // stack a custom shader layer above for effects:154 // { type: "customShader", sketch: yourSketchFn },155 ],156 timeline: { duration: 6, loop: true, tracks: [] },157 };158 ```159160 Custom shader layers use TSL (Three Shading Language) — a `Fn()`-wrapped function that returns a TSL node. Injected globals include `time`, `inputTexture`, and noise / tonemapping / SDF helpers. Write these in the editor first, then export.1611628. **Advanced: compositions as textures or post-processing**163164 - **Composition as a texture in an R3F scene**: use `useShaderLab(config, { width, height, pixelRatio })` and pass the returned `texture` to a material.165 - **Post-processing over an existing renderer**: `useShaderLab(config, { renderer })` and drive it with `postprocessing.render(inputTexture, time, delta)` in your render loop.166 - **Manual render loop**: `useShaderLabCanvasSource` / `useShaderLabPostProcessingSource` for full control.167168 These are client-side, same `"use client"` rule applies.169170## Where configs live171172Treat composition configs as content, not code. They belong in a dedicated directory (`src/shaders/compositions/`) next to any per-composition sketch functions. Reuse configs across projects by copy or by referencing a shared package if the studio maintains one.173174## Troubleshooting175176- **Blank output, no error**: likely WebGPU not available. Add the capability guard above.177- **"Runtime error" callback fires**: inspect the message, most common cause is an invalid sketch function or an unsupported TSL node. Reopen the composition in the Shader Lab editor and re-export.178- **SSR hydration warning**: the component is client-only. Ensure the parent is marked `"use client"` or imported via `dynamic(() => ..., { ssr: false })`.179- **Three.js version conflict**: `three` is a peer dep. If another package pins an incompatible version, resolve with `overrides` / `resolutions` in `package.json`.180181## References182183- Runtime README: https://github.com/basementstudio/shader-lab184- Editor: https://eng.basement.studio/tools/shader-lab185- TSL docs: https://threejs.org/docs/?q=tsl (Three Shading Language)