Skip to content
Dethink Components

ComponentsContourFieldBackground

ContourFieldBackground

Topographic contours evolve through a warped noise field.

On this page

A working hero, with ordinary HTML above the shader. Pause the animation, switch themes, or explore a new composition.

Interactive hero

The foreground controls remain usable while the background moves.

Contour Field

EXPLORE THE POSSIBILITIES

Find your next direction.

A changing landscape for ideas that keep moving.

Use this background

Show sourceexamples/shader-backgrounds/preview.tsx
examples/shader-backgrounds/preview.tsx
"use client";

import { useState } from "react";
import Link from "next/link";
import {
  LiquidMeshBackground,
  SilkFlowBackground,
  CausticLightBackground,
  ContourFieldBackground,
  OrbitalGlowBackground,
  type ShaderBackgroundEffect,
  type ShaderBackgroundProps,
} from "@dethink/components";

const backgrounds = {
  "liquid-mesh": {
    Component: LiquidMeshBackground,
    name: "Liquid Mesh",
    eyebrow: "COLOR IN MOTION",
    headline: "Let your ideas flow.",
    description: "Soft gradients that make room for something new.",
  },
  "silk-flow": {
    Component: SilkFlowBackground,
    name: "Silk Flow",
    eyebrow: "A DIFFERENT TEXTURE",
    headline: "Made to feel different.",
    description: "Light catches every fold. Details make the difference.",
  },
  "caustic-light": {
    Component: CausticLightBackground,
    name: "Caustic Light",
    eyebrow: "BENEATH THE SURFACE",
    headline: "See things in a new light.",
    description: "A quieter kind of energy, moving just below the surface.",
  },
  "contour-field": {
    Component: ContourFieldBackground,
    name: "Contour Field",
    eyebrow: "EXPLORE THE POSSIBILITIES",
    headline: "Find your next direction.",
    description: "A changing landscape for ideas that keep moving.",
  },
  "orbital-glow": {
    Component: OrbitalGlowBackground,
    name: "Orbital Glow",
    eyebrow: "YOUR NEXT CHAPTER",
    headline: "A world around your work.",
    description:
      "Bring your ideas into focus. Let everything else fall into place.",
  },
};

export function ShaderBackgroundPreview({
  effect,
  compact = false,
}: {
  effect: ShaderBackgroundEffect;
  compact?: boolean;
}) {
  const [paused, setPaused] = useState(false),
    [dark, setDark] = useState(true),
    [interactive, setInteractive] = useState(false);
  const [speed, setSpeed] = useState<ShaderBackgroundProps["speed"]>("normal"),
    [intensity, setIntensity] =
      useState<ShaderBackgroundProps["intensity"]>("bold");
  const [seed, setSeed] = useState(1),
    [joined, setJoined] = useState(false);
  const { Component, name, eyebrow, headline, description } =
    backgrounds[effect];
  return (
    <section
      data-preview={effect}
      data-theme={dark ? "dark" : "light"}
      className="border-border bg-background text-foreground overflow-hidden rounded-2xl border"
    >
      <div className="border-border flex flex-wrap items-center justify-between gap-3 border-b px-5 py-4">
        <span className="text-muted-foreground font-mono text-xs tracking-widest uppercase">
          {name}
        </span>
        <div className="flex flex-wrap items-center gap-2">
          <button
            type="button"
            onClick={() => setPaused(!paused)}
            aria-pressed={paused}
            className="border-border focus-visible:outline-ring rounded-md border px-3 py-1.5 text-xs focus-visible:outline-2"
          >
            {paused ? "Resume animation" : "Pause animation"}
          </button>
          <button
            type="button"
            onClick={() => setDark(!dark)}
            aria-pressed={dark}
            className="border-border focus-visible:outline-ring rounded-md border px-3 py-1.5 text-xs focus-visible:outline-2"
          >
            Dark theme
          </button>
          <button
            type="button"
            onClick={() => setInteractive(!interactive)}
            aria-pressed={interactive}
            className="border-border focus-visible:outline-ring rounded-md border px-3 py-1.5 text-xs focus-visible:outline-2"
          >
            Mouse parallax
          </button>
        </div>
      </div>
      <Component
        animate={!paused}
        interactive={interactive}
        speed={speed}
        intensity={intensity}
        seed={seed}
        className="min-h-[420px]"
      >
        <div className="from-background/80 via-background/30 relative flex min-h-[420px] items-center bg-linear-to-r to-transparent px-6 py-16 sm:px-12">
          <div className="max-w-xl">
            <p className="text-foreground/70 mb-6 font-mono text-[10px] tracking-[0.2em]">
              {eyebrow}
            </p>
            <h2 className="text-foreground max-w-lg text-4xl leading-[1.06] font-semibold tracking-tight text-balance sm:text-5xl">
              {headline}
            </h2>
            <p className="text-foreground/75 mt-5 max-w-sm text-sm leading-relaxed">
              {description}
            </p>
            <div className="mt-8 flex flex-wrap items-center gap-4">
              <button
                type="button"
                onClick={() => setJoined(!joined)}
                className="bg-foreground text-background focus-visible:outline-ring rounded-full px-5 py-2.5 text-sm font-medium focus-visible:outline-2"
              >
                {joined ? "You're on the list" : "Join the preview"}
              </button>
              <Link
                href={`/components/${effect}-background#installation`}
                className="text-foreground focus-visible:outline-ring rounded px-1 py-2 text-xs underline underline-offset-4 focus-visible:outline-2"
              >
                Use this background
              </Link>
            </div>
            <p
              role="status"
              className="text-foreground/70 mt-3 min-h-5 text-xs"
            >
              {joined ? "Demo confirmed. No information was sent." : ""}
            </p>
          </div>
        </div>
      </Component>
      {!compact && (
        <div className="border-border text-muted-foreground flex flex-wrap items-center gap-5 border-t px-5 py-4 text-xs">
          <label className="flex items-center gap-2">
            Speed{" "}
            <select
              aria-label="Animation speed"
              value={speed}
              onChange={(e) =>
                setSpeed(e.target.value as ShaderBackgroundProps["speed"])
              }
              className="border-border bg-background text-foreground rounded border px-2 py-1"
            >
              <option value="slow">Slow</option>
              <option value="normal">Normal</option>
              <option value="fast">Fast</option>
            </select>
          </label>
          <label className="flex items-center gap-2">
            Intensity{" "}
            <select
              aria-label="Effect intensity"
              value={intensity}
              onChange={(e) =>
                setIntensity(
                  e.target.value as ShaderBackgroundProps["intensity"],
                )
              }
              className="border-border bg-background text-foreground rounded border px-2 py-1"
            >
              <option value="faint">Faint</option>
              <option value="subtle">Subtle</option>
              <option value="bold">Bold</option>
            </select>
          </label>
          <button
            type="button"
            onClick={() => setSeed(seed + 1)}
            className="border-border focus-visible:outline-ring rounded border px-3 py-1.5 focus-visible:outline-2"
          >
            New composition
          </button>
        </div>
      )}
    </section>
  );
}

Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.

View registry files

Import the component into your page or component file.

Usage
import { ContourFieldBackground } from "@dethink/components";

<ContourFieldBackground className="rounded-2xl p-12">
  <h1>Your next idea starts here.</h1>
</ContourFieldBackground>

Native div attributes, events and a forwarded ref are also supported.

ContourFieldBackground props
PropWhat it doesDefault
childrenReactNodeOrdinary HTML content above the inert decorative layer.Not set
animatebooleanFalse selects static CSS and releases the WebGL context. Reduced motion always takes priority.true
speed"slow" | "normal" | "fast"Bounded animation speed. Drawing is capped at 30 fps."normal"
intensity"faint" | "subtle" | "bold"Strength of the shader colors and static fallback."subtle"
seednumberRepeatable initial composition. Non-finite seeds use the default.1
interactivebooleanGentle mouse parallax. Disabled on coarse input and while static; never captures touch gestures.false

Provide a pause control for continuous motion by toggling animate. Reduced motion, forced colors, print, and unavailable WebGL use static CSS. The decorative canvas is hidden from assistive technology and never intercepts a click. Foreground content is present before JavaScript runs. Use a foreground surface or scrim to maintain text contrast.

Set --shader-background-base, --shader-background-accent, and --shader-background-secondary with CSS color values or semantic tokens. The defaults follow the current background, primary, and info tokens. Rendering stops outside the viewport or in hidden tabs. Each visible instance uses one draw call, at most one million pixels, and a maximum device pixel ratio of 1.5.

Explore all five shader backgrounds