Iridescence <mv-iridescence>
WebGL soap film held upright: gravity drains it thin at the top and thick at the bottom, so its interference colors stack in bands that slow eddies curl and marble, and the thickest film settles into a pearl of the accent.
How it works
WebGL soap film held upright: gravity drains it thin at the top and thick at the bottom, so its interference colors stack in bands that slow eddies curl and marble, and the thickest film settles into a pearl of the accent. Twin strip lights are reflected in the film; the pointer slides them and changes the viewing angle, which shifts the colors around it. The film follows the theme, glowing in reflected light on a dark page and backlit with its transmitted pastels on a light one. It pauses off screen, shares the page's WebGL context budget and keeps a still frame under reduced motion.
| Category | Backgrounds |
|---|---|
| Type | WebGL element (<mv-iridescence>) |
| Status | stable |
| Also installs | shader |
| Keywords | safe-rewrite, webgl, shader, iridescent, holographic, soap-film, pearl, hero |
When to use
- A beauty, fashion, crypto or creative brand wants a holographic, soap-film sheen as a hero backdrop
- The backdrop should answer the pointer: reflections slide and colors shift where the visitor moves
- The same iridescent hero must work in light (pastel, backlit) and dark (glowing) themes without extra tuning
Avoid when
- The holographic look should apply to cards and buttons, not the page background → use Surface instead
- A reflective liquid-metal surface is the intended look → use Liquid Chrome instead
- A still section background is enough and no WebGL should run → use Backdrop instead
Install
node scripts/add.mjs iridescence --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["iridescence"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/i18n.js, core/motion.js, core/observe.js, core/webgl.js, components/shader/shader.js, components/shader/shader.css, components/iridescence/iridescence.js, components/iridescence/iridescence.css.
Usage
Canonical markup, to start from and customize with attributes, data-* and CSS variables:
<mv-iridescence data-fill></mv-iridescence>
<div style="position:relative;display:grid;place-items:center;height:100%;min-height:320px;padding:2rem 1rem;box-sizing:border-box;text-align:center;pointer-events:none">
<div>
<p style="margin:0;font:700 clamp(1.6rem,4vw,2.6rem)/1.1 var(--mv-font-sans);letter-spacing:-.03em;color:var(--mv-fg);text-shadow:0 0 28px var(--mv-bg),0 0 10px var(--mv-bg)">Thin as light</p>
<p style="margin:.6rem auto 0;max-width:26rem;color:var(--mv-fg);text-shadow:0 0 16px var(--mv-bg),0 0 6px var(--mv-bg)">A soap film drains into bands of color. Move the pointer to tilt the light.</p>
</div>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
color | CSS color | var(--mv-accent) | Pearl into which the thickest film settles; only its hue counts (tokens accepted, read again on theme change). |
scale | number | 1 | Size of the swirls. |
amplitude | number | 1 | Strength of the eddies that stir the bands (0 = flat horizontal bands). |
saturation | number | 1 | Vividness of the interference colors (0 = a neutral sheen, max 3). |
depth | number | 0.5 | Tonal depth: 0 = the whole film shows its colors, 1 = colors mostly where the strip lights fall. |
glint | number | 1 | Strength of the strip-light reflections and their glare (0 = none, max 3). |
speed | number | 1 | Time multiplier. |
paused | boolean | Freezes the animation on the current frame. | |
interactive | boolean | Follows the pointer only over the element (otherwise across the whole window). | |
dpr | number | 1.5 | Max pixel density (capped at 2). |
data-fill | boolean | Fills the positioned parent (absolute, inset 0). |
Events
| Name | Description |
|---|---|
mv-error | The shader failed to compile on this GPU. detail: { message }. The element gets data-error and shows its fallback. |
Content structure
| Name | Description |
|---|---|
children | Content placed inside the element sits above the film. |
Accessibility
Decorative: the canvas is aria-hidden and never takes pointer events, and content placed inside stays in the normal reading order. The loop stops off screen and in hidden tabs; reduced motion (OS or <html data-motion="reduce">) and paused keep a still, representative frame. Without WebGL2 a still band of soap-film colors in the theme tokens is shown; forced colors and print hide the canvas. The bands are vivid and change brightness, so text placed over them needs a halo or a backing panel to stay readable.