Skip to content

FreeIn reviewv1.0.0

Iridescent

A film of oil on water, or holographic foil: slow bands of shifting colour with a soft sheen that follows the pointer.
npx shadcn@latest add @vibebox/iridescent

Controls

1
1
<Iridescent />

About

A scalar field of noise, warped by two more, read as the thickness of a thin film. Thickness goes through a cyclic ramp of four colours, so a small change in height moves the colour a long way and the bands are the iso-lines of the field. Where the field is lowest the dark base is mixed back in. A soft highlight comes from the field's slope against a fixed light, and a fine grain covers the dither. Nothing is drawn but one full-screen pass.

Use it when

  • A brand wants a premium, tactile surface behind its headline: beauty, jewellery, design or a launch.
  • A product page wants a holographic or pearlescent feel without a photo.
  • A creative studio or fintech wants colour that moves slowly and never reads as a stock gradient.

Skip it when

  • The page is dense with body copy; use Satin or Dot Waves.
  • The brand's colours are one flat tone; use Studio.

Install

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the component with the shadcn CLI:
    npx shadcn@latest add @vibebox/iridescent
  3. 3Use it:
    import { Iridescent } from "@/components/vibebox/iridescent";
    
    <Iridescent preset="holo" className="min-h-[560px] rounded-xl">
      <div className="p-12 text-white">
        <h1 className="text-4xl font-semibold">Work that catches the light.</h1>
      </div>
    </Iridescent>

Code

3 files
components/vibebox/iridescent/index.tsx
/**
 * VibeBox Motion · Iridescent
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
export { Iridescent, type IridescentProps, type IridescentColor, type IridescentValue } from "./iridescent.client";
export { PRESET_NAMES as IRIDESCENT_PRESETS, presets as iridescentPresets, type IridescentPreset } from "./iridescent.shader";
components/vibebox/iridescent/iridescent.client.tsx
/**
 * VibeBox Motion · Iridescent
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
"use client";

import type { ReactNode } from "react";
import { BackdropCanvas, type PosterSpec } from "@/lib/vibebox/backdrop-canvas";
import { iridescentPiece, presets, type IridescentPreset } from "./iridescent.shader";

export type IridescentColor = "base" | "a" | "b" | "c" | "d" | "sheenColor";
export type IridescentValue = "scale" | "warp" | "cover" | "sheen" | "grain";

export type IridescentProps = {
  /** What sits on the scene: a heading, a call to action, a whole section. */
  children?: ReactNode;
  /** A named look: `holo`, `pearl`, `oil`, `gold`, `aurora`, `graphite`. */
  preset?: IridescentPreset;
  /** Overrides single palette colours with any CSS colour. */
  colors?: Partial<Record<IridescentColor, string>>;
  /** Multiplies the preset's speed (1 keeps it, 0 holds the scene still). */
  speed?: number;
  /** Multiplies the preset's brightness. */
  intensity?: number;
  /** The pointer and touch move the scene. */
  interactive?: boolean;
  /** Accessible name; leave it out when the scene is only a background. */
  label?: string;
  /** Hold one still frame until this is false (for a gallery card). */
  still?: boolean;
  className?: string;
} & Partial<Record<IridescentValue, number>>;

const POSTER: PosterSpec = { type: "linear", from: "a", to: "c", mid: "b", angle: 120, blobs: [{ color: "d", at: [70, 28], size: 55 }, { color: "base", at: [18, 88], size: 40 }] };

export function Iridescent({ children, preset, colors, speed, intensity, interactive = true, label, still, className, scale, warp, cover, sheen, grain }: IridescentProps) {
  return (
    <BackdropCanvas
      slug="iridescent"
      piece={iridescentPiece}
      presets={presets}
      fallback="holo"
      preset={preset}
      colors={colors}
      values={{ scale, warp, cover, sheen, grain }}
      speed={speed}
      intensity={intensity}
      poster={POSTER}
      scale={0.5}
      stillTime={3}
      still={still}
      interactive={interactive}
      label={label}
      className={className}
    >
      {children}
    </BackdropCanvas>
  );
}
components/vibebox/iridescent/iridescent.shader.ts
/**
 * VibeBox Motion · Iridescent
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
import { compose, createScenePiece, type ScenePreset } from "@/lib/vibebox/engine/backdrops";

/*
 * A film of oil on water, or a holographic foil. A height field flows under a domain warp; its
 * height is the film's thickness, and thickness picks a colour from a cyclic ramp of four, the
 * way interference does. Where the film is thin the dark base shows through, so the colour
 * arrives in veins and sheets rather than as a flat wash. A soft specular streak follows the
 * surface's slope and a fine grain keeps it from banding. The pointer pushes the film.
 */

const SCENE = /* glsl */ `
uniform vec3 uBase;
uniform vec3 uA;
uniform vec3 uB;
uniform vec3 uC;
uniform vec3 uD;
uniform vec3 uSheenColor;
uniform float uScale;
uniform float uWarp;
uniform float uCover;
uniform float uSheen;
uniform float uGrain;
uniform float uSpeed;
uniform float uIntensity;

float T;

/* A smooth cyclic ramp through the four film colours. */
vec3 ramp(float t) {
  t = fract(t) * 4.0;
  float k = floor(t);
  float f = t - k;
  f = f * f * (3.0 - 2.0 * f);
  vec3 a = lin(uA), b = lin(uB), c = lin(uC), d = lin(uD);
  if (k < 1.0) return mix(a, b, f);
  if (k < 2.0) return mix(b, c, f);
  if (k < 3.0) return mix(c, d, f);
  return mix(d, a, f);
}

float film(vec2 p) {
  // Big, slow forms: two octaves of value noise under a gentle warp, so the bands sweep.
  vec2 w = vec2(fbm3(p * 0.5 + vec2(T * 0.04, -T * 0.03)), fbm3(p * 0.5 + vec2(5.2, 1.3) + vec2(-T * 0.03, T * 0.04)));
  p += (w - 0.5) * uWarp * 1.7;
  return vnoise(p * 0.85 + vec2(T * 0.03, 0.0)) * 0.6 + vnoise(p * 1.9 - vec2(0.0, T * 0.04)) * 0.28 + vnoise(p * 4.1 + vec2(T * 0.05, 3.0)) * 0.12;
}

void main() {
  vec2 uv = centred(gl_FragCoord.xy);
  T = uTime * uSpeed;
  vec2 look = (uPointer - 0.5) * (0.3 + uHover * 0.6);
  vec2 p = uv * uScale * 1.5 + look * 0.8;

  float e = 0.01;
  float h = film(p);
  vec2 grad = vec2(film(p + vec2(e, 0.0)) - h, film(p + vec2(0.0, e)) - h) / e;

  // Thickness picks the colour from a cyclic ramp, the way interference does; a slow diagonal
  // sweep is added so the colours travel across the frame.
  float thick = h * 5.2 + uv.y * 0.5 - uv.x * 0.3 + 0.12 * sin(T * 0.1);
  vec3 col = ramp(thick);
  // Where the film is thinnest a little of the dark base shows through, as deep colour.
  float thin = smoothstep(uCover + 0.22, uCover - 0.3, h);
  col = mix(col, lin(uBase), thin * 0.55);

  // A soft sheen streak from the slope, and a lifted edge along the steepest parts.
  vec3 n = normalize(vec3(-grad * 0.16, 1.0));
  vec3 L = normalize(vec3(-0.45, 0.55, 0.7));
  float spec = pow(max(dot(reflect(-L, n), vec3(0.0, 0.0, 1.0)), 0.0), 14.0);
  col += lin(uSheenColor) * spec * uSheen * 0.6;
  col += ramp(thick + 0.45) * smoothstep(1.0, 3.2, length(grad)) * 0.12;
  col *= 0.88 + 0.22 * dot(n, L);

  col *= uIntensity;
  col *= 1.0 - 0.36 * smoothstep(0.35, 1.2, length(uv * vec2(0.8, 1.05)));
  float g = (h2(ivec2(gl_FragCoord.xy)) - 0.5) * uGrain * 0.07;
  col += col * g;
  fragColor = vec4(finish(col, gl_FragCoord.xy), 1.0);
}
`;

export const fragment = compose(SCENE);

export const PRESET_NAMES = ["holo", "pearl", "oil", "gold", "aurora", "graphite"] as const;
export type IridescentPreset = (typeof PRESET_NAMES)[number];

export const presets: Record<IridescentPreset, ScenePreset> = {
  holo: {
    colors: { base: "#0a0d1c", a: "#74d9ff", b: "#b79bff", c: "#ff9fd9", d: "#ffe2a8", sheenColor: "#ffffff" },
    values: { scale: 1, warp: 1, cover: 0.4, sheen: 0.6, grain: 0.6, speed: 0.7, intensity: 1 },
  },
  pearl: {
    colors: { base: "#14131a", a: "#fff4f6", b: "#dfe9ff", c: "#f7e1ff", d: "#fff0d8", sheenColor: "#ffffff" },
    values: { scale: 0.9, warp: 0.8, cover: 0.38, sheen: 0.7, grain: 0.7, speed: 0.55, intensity: 1.05 },
  },
  oil: {
    colors: { base: "#04050a", a: "#1a7bff", b: "#a528ff", c: "#00e0a8", d: "#ff3f9a", sheenColor: "#bfe6ff" },
    values: { scale: 1.15, warp: 1.25, cover: 0.55, sheen: 0.6, grain: 0.9, speed: 0.75, intensity: 1 },
  },
  gold: {
    colors: { base: "#0d0a06", a: "#fff0b8", b: "#f0c05a", c: "#b87a26", d: "#ffe6a0", sheenColor: "#fff8e0" },
    values: { scale: 1, warp: 0.9, cover: 0.42, sheen: 0.8, grain: 0.9, speed: 0.6, intensity: 1 },
  },
  aurora: {
    colors: { base: "#03070f", a: "#2dffb0", b: "#33a8ff", c: "#8a5cff", d: "#00ffd8", sheenColor: "#d6fff2" },
    values: { scale: 0.85, warp: 1.1, cover: 0.5, sheen: 0.5, grain: 0.8, speed: 0.65, intensity: 1 },
  },
  graphite: {
    colors: { base: "#07080b", a: "#d7dbe6", b: "#7d8699", c: "#3e4658", d: "#aeb6c6", sheenColor: "#ffffff" },
    values: { scale: 1, warp: 0.9, cover: 0.4, sheen: 0.9, grain: 1, speed: 0.55, intensity: 1 },
  },
};

export const iridescentPiece = createScenePiece("iridescent", fragment);

Props

PropTypeDefaultDescription
childrenReactNode—What sits on the scene: a heading, a call to action, a whole section.
preset"holo" | "pearl" | "oil" | "gold" | "aurora" | "graphite""holo"The look: holo, pearl, oil, gold, aurora, graphite.
colors{ base?: string; a?: string; b?: string; c?: string; d?: string; sheenColor?: string }—Overrides single palette colours with any CSS colour: base (the dark that shows through where the film is thinnest), a (the first colour of the film's ring), b (the second colour of the film's ring), c (the third colour of the film's ring), d (the fourth colour of the film's ring), sheenColor (the colour of the soft highlight).
speednumber1Multiplies the preset's speed; 0 holds the scene still.
intensitynumber1Multiplies the preset's brightness, 0.5 to 1.4.
scalenumber1How large the bands are (smaller is bigger bands) (0.5 to 1.8; each look sets its own).
warpnumber1How far the film is bent before it is read (0 to 1.6; each look sets its own).
covernumber0.4How much dark base shows where the film is thin (0 to 1; each look sets its own).
sheennumber0.6Strength of the highlight that rides the slope (0 to 1.4; each look sets its own).
grainnumber0.6A fine film grain that keeps the bands from banding (0 to 2; each look sets its own).
interactivebooleantrueThe film leans toward the pointer and is pushed a little further when it is over the scene.
labelstring—Accessible name; leave it out when the scene is only a background.
stillbooleanfalseHold one composed frame until false (a gallery card that plays on hover).
classNamestring—Merged with cn(); give it a size and a radius.

Accessibility and motion

Keyboard
Not focusable; content on it keeps its own focus order.
Screen readers
Decorative by default; with label it is an image with that name.
Touch
A finger pushes the film like the cursor does; scrolling is never blocked.
Reduced motion
One still frame of the film; the bands do not drift.

Edge cases and limits

  • The light looks (pearl, gold) need dark text; the saturated ones (oil, aurora) need white.
  • Very large scale values make single bands fill the frame; keep scale between 0.7 and 1.4 for a hero.
  • The grain is fine on purpose: it hides banding in the long gradients, so leave it above 0.4.
  • Many thin cyan, violet and magenta lines of light flowing across a dark background in one wave

    Dozens of thin lines of light riding one slow wave, bunching where they cross, with a glow that follows the pointer.

    Backgrounds · WebGL2

  • A soft spectrum gradient behind a heading and a button

    A soft spectrum mesh gradient that drifts behind your content and leans toward the cursor; still under reduced motion.

    Backgrounds · WebGL2

  • Green and violet aurora curtains over a mountain ridge and stars

    Aurora

    Free

    Curtains of northern light over a night sky and a mountain ridge, drawn toward the pointer.

    Backgrounds · WebGL2

↑ ↓ to move↵ to openesc to close