Dust. Fragment shaders as a design primitive.

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.

~25 kB Brotli total
2 Canvases
0 Dependencies

Base Shaders

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.

dust/solid
dust/bevel
dust/gradient — move pointer

Prismatic facets — wide format.

dust/crystalline

Northern lights colour shift.

dust/aurora

Radial pulse rings from center.

dust/pulse-ring
dust/ripple

The Compose System

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.

Single Effects

effect="shimmer" — hover to activate
effect="hoverglow" — hover for flare
effect="crystalline"
no effects — just the bevel base

Stacked Effects

effect="hoverglow noisefog"
effect="hoverglow shimmer"
effect="ripple"

Triple Composition

Bevel + Shimmer + Aurora + Noise Fog

fog 0.4 / aurora 0.6 / shimmer on hover

CSS partial weights: --dust-fx-noisefog: 0.4

Gradient + Aurora + Pulse Ring + Noise Fog

all at full weight

different base, same effects

Solid + Lens Flare + Ripple + Shimmer

all hover-activated — move pointer

3 hover effects stacked

CSS Effect Control

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.

--dust-fx-shimmer: 0.7 / speed: 3.0

Solid + Noise Fog + Lens Flare

--dust-color: #1a1a2e

Gradient + Pulse Ring

--dust-inner / --dust-outer

Class-Driven Theming

Define a CSS class with custom properties. Apply it to any component. The shader picks up the new values automatically.

.danger { --dust-color: #e74c3c }
.ocean { --dust-fx-noisefog-color: ... }
.mint { --dust-color: #1abc9c }

Z-Layer Control

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.

opaque parent — shader invisible
layer="hi" — shader renders above bg
Crystal Chip
chip with layer="hi"
+
fab with layer="hi"

Component Library

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.

Buttons & Actions

Save
dust-button
Submit
--dust-color
Delete
--dust-color
Deep
--dust-depth: 0.3
Disabled
disabled
+
dust-fab
+
green
+
disabled
Default
dust-chip
Selected
selected
Purple
custom color

Inputs

dust-switch
checked
dust-checkbox
checked
indeterminate
dust-radio
checked
grouped
dust-slider
min
max
dust-textfield
email
password
dust-select
dust-combobox

Display

Card

Shader background with header, body, and footer slots.

Footer area
dust-card

Fog Card

Any shader works as the card background.

with noisefog effect

Aurora Card

Aurora shader running behind content slots.

Action
with nested dust-button
dust-avatar
purple
small
dust-badge (dot)
value: 3
max: 50
circular 70%
indeterminate
linear 40%
indeterminate

Shader Showcase — Crystalline

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.

1
Crystal
chip
+
fab
switch
checkbox
radio
slider
badge
avatar
circular
linear
textfield
select
combobox

Reactive Field

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.

Bevel Solid Chip +

Outer Reactive Field

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

Bevel Solid Crystalline Aurora
Chip +
Shimmer

Infinitely Expandable

Bring your own shaders, compose your own effects, extend your own components. The system is open at every layer.

Custom Shader

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>
Custom plasma shader
Button + Chip

Custom Component

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);
Click to cycle palette
Button
+
Chip