Every component surface is a live GPU shader. Per-pixel lighting, cursor tracking, composable effects — rendered on two shared WebGL canvases (low and hi z-layers). Standard Custom Elements. Zero runtime dependencies.
Every component accepts a shader attribute. These are the built-in surfaces — the visual material behind your DOM content. Mouse interaction is continuous: the shader knows where the cursor is, how fast it's moving, and how long it's been there.
Prismatic facets — wide format.
Northern lights colour shift.
Radial pulse rings from center.
Effects are composable layers that stack on any base. Use -fx shader variants and the effect attribute. The system generates one uber-shader per combination — no runtime branching, no multi-pass overhead. Control each effect's weight and parameters via CSS custom properties.
Bevel + Shimmer + Aurora + Noise Fog
fog 0.4 / aurora 0.6 / shimmer on hover
Gradient + Aurora + Pulse Ring + Noise Fog
all at full weight
Solid + Lens Flare + Ripple + Shimmer
all hover-activated — move pointer
Effect parameters are CSS custom properties. Change a property and the shader responds on the next frame. Theme entire component trees with a single class.
Solid + Noise Fog + Lens Flare
Gradient + Pulse Ring
Define a CSS class with custom properties. Apply it to any component. The shader picks up the new values automatically.
The low projector renders at z-index: -1 — behind everything. Any parent with an opaque background hides the shader. The layer="hi" attribute switches to a second projector at z-index: 1, rendering above opaque backgrounds. Content stays above both via shadow DOM stacking.
Standard Custom Elements that drop into any HTML page. Every component is a regular DOM node — flexbox, CSS sizing, accessibility tree, event listeners. The shader is just the surface.
Shader background with header, body, and footer slots.
Any shader works as the card background.
Aurora shader running behind content slots.
One effect applied across every component type. The shader adapts to each component's shape, size, and interaction model. Adjust the speed to see how CSS properties drive shader parameters in real time.
Components share a physics simulation. Click anywhere — ripples propagate through every component in the field. Each component receives the wave as a shader effect, creating a unified surface that spans multiple DOM elements.
The water surface renders between components. Waves flow around obstacles, bounce off edges, and create caustic highlights. The simulation runs on the CPU; the rendering is a single fullscreen shader pass. 1
Bring your own shaders, compose your own effects, extend your own components. The system is open at every layer.
Register a fragment shader. Use it on any component.
// Register your shader
registerShader('my/plasma', `
vec2 uv = gl_FragCoord.xy / u_resolution;
float t = u_time * 0.4;
// Two overlapping sine waves = plasma
float v = sin(uv.x*8.0+t) * cos(uv.y*6.0-t*0.7)
+ sin(length(uv-0.5)*12.0-t*1.5) * 0.5;
float w = v * 0.5 + 0.5;
vec3 col = mix(u_color.rgb, u_color.rgb + vec3(0.3, 0.1, 0.4), w);
// Lighten on hover — u_hover is 0.0 or 1.0
col += u_hover * 0.12;
// Rounded-rect SDF — clips to component shape
float rad = u_radius * min(u_resolution.x, u_resolution.y) * 0.5;
vec2 q = abs(gl_FragCoord.xy - u_resolution*0.5) - u_resolution*0.5 + rad;
float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - rad;
float a = 1.0 - smoothstep(-1.0, 0.5, d);
fragColor = vec4(clamp(col, 0.0, 1.0) * a, a);
`);
// Use it — works on any component, any shape
<dust-button shader="my/plasma" animated>Go</dust-button>
<dust-fab shader="my/plasma" animated>+</dust-fab>
Extend DustElement. Inherit shaders, CSS bridge, pointer tracking, and all effects. Click the panels to cycle palettes.
import { DustElement } from '@dust/hub';
const PALETTES = [
[0.18, 0.20, 0.45], // blue
[0.45, 0.12, 0.18], // crimson
[0.12, 0.38, 0.22], // emerald
[0.42, 0.28, 0.12], // amber
];
class PaletteCycle extends DustElement {
private idx = 0;
connectedCallback() {
this.setAttribute('shader', 'my/plasma');
this.setAttribute('animated', '');
super.connectedCallback();
this.addEventListener('click', this.cycle);
}
private cycle = () => {
this.idx = (this.idx + 1) % PALETTES.length;
const c = PALETTES[this.idx];
setComponentUniform(this.comp.id, 'u_color', c);
};
}
customElements.define('palette-cycle', PaletteCycle);