About
A loop over up to sixty-four lines. Each line is the curve y = f(x) of three sines with a phase that grows down the stack, its height set by a perspective-like depth so far lines are higher and finer; the distance from the pixel to the curve gives a Gaussian core and a wider soft halo. Colour comes from a three-stop ramp by the line's index. The pointer draws the nearest lines toward its height with a Gaussian in x. Everything is additive, so crossings brighten on their own.
Use it when
- A software, fintech or data brand wants a calm, premium abstract hero.
- A beauty or fashion drop wants silk-thread colour behind a headline.
- A deck or report cover wants lines that suggest movement without noise.
Skip it when
- The brand is warm and tactile; use Satin or Liquid Ink.
- The page needs a literal place: use Ocean, Terrain or City Night.
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the component with the shadcn CLI:
npx shadcn@latest add @vibebox/flow-lines - 3Use it:
import { FlowLines } from "@/components/vibebox/flow-lines"; <FlowLines preset="aurora" className="min-h-[560px] rounded-xl"> <div className="p-12 text-white"> <h1 className="text-4xl font-semibold">Data that moves like this.</h1> </div> </FlowLines>
Code
3 filescomponents/vibebox/flow-lines/flow-lines.client.tsx
/**
* VibeBox Motion · Flow Lines
* 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 { flowLinesPiece, presets, type FlowLinesPreset } from "./flow-lines.shader";
export type FlowLinesColor = "base" | "a" | "b" | "c";
export type FlowLinesValue = "lines" | "amp" | "freq" | "width" | "glow" | "tilt";
export type FlowLinesProps = {
/** What sits on the scene: a heading, a call to action, a whole section. */
children?: ReactNode;
/** A named look: `aurora`, `ember`, `ocean`, `orchid`, `gold`, `ice`. */
preset?: FlowLinesPreset;
/** Overrides single palette colours with any CSS colour. */
colors?: Partial<Record<FlowLinesColor, 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<FlowLinesValue, number>>;
const POSTER: PosterSpec = { type: "linear", from: "base", to: "base", angle: 180, blobs: [{ color: "a", at: [28, 58], size: 52 }, { color: "c", at: [76, 42], size: 46 }] };
export function FlowLines({ children, preset, colors, speed, intensity, interactive = true, label, still, className, lines, amp, freq, width, glow, tilt }: FlowLinesProps) {
return (
<BackdropCanvas
slug="flow-lines"
piece={flowLinesPiece}
presets={presets}
fallback="aurora"
preset={preset}
colors={colors}
values={{ lines, amp, freq, width, glow, tilt }}
speed={speed}
intensity={intensity}
poster={POSTER}
scale={0.6}
stillTime={4}
still={still}
interactive={interactive}
label={label}
className={className}
>
{children}
</BackdropCanvas>
);
}components/vibebox/flow-lines/flow-lines.shader.ts
/**
* VibeBox Motion · Flow Lines
* 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";
/*
* Many thin lines of light that ride one slow wave. Each line is a curve of x with its own
* phase, so together they bunch and fan like silk threads; they brighten where they converge,
* take their colour from a three-stop ramp across the stack, and thin out toward the far edge so
* the sheet reads as having depth. The pointer pulls the lines toward it.
*/
const SCENE = /* glsl */ `
uniform vec3 uBase;
uniform vec3 uA;
uniform vec3 uB;
uniform vec3 uC;
uniform float uLines;
uniform float uAmp;
uniform float uFreq;
uniform float uWidth;
uniform float uGlow;
uniform float uTilt;
uniform float uSpeed;
uniform float uIntensity;
float T;
void main() {
vec2 uv = centred(gl_FragCoord.xy);
T = uTime * uSpeed;
float aspect = uResolution.x / uResolution.y;
vec2 m = (uPointer - 0.5) * vec2(aspect, 1.0);
int count = int(clamp(uLines, 8.0, 64.0));
vec3 col = lin(uBase);
// A faint floor glow so the dark is not flat.
col += lin(uB) * 0.025 * exp(-pow(uv.y + 0.35, 2.0) * 6.0);
float acc = 0.0;
vec3 tint = vec3(0.0);
for (int i = 0; i < 64; i++) {
if (i >= count) break;
float t = float(i) / float(max(count - 1, 1));
// The stack's depth: nearer lines (t small) are lower and bigger, far ones higher and finer.
float depth = mix(1.0, 0.4, t);
float x = uv.x * uFreq;
float ph = t * 3.2 + T * (0.22 + 0.05 * t);
float y = mix(-0.34, 0.3, t) * uTilt + (-0.06 + 0.12 * t) * 0.0;
float wave = sin(x * 1.1 + ph) * 0.55 + sin(x * 2.3 - ph * 1.7 + 1.3) * 0.28 + sin(x * 0.45 + ph * 0.6) * 0.5;
y += wave * uAmp * depth * 0.22;
// The pointer draws the lines toward it.
float pull = exp(-pow((uv.x - m.x) * 1.6, 2.0)) * (0.5 + uHover * 0.9);
y += (m.y - y) * pull * 0.14 * (1.0 - t);
float d = abs(uv.y - y);
float w = uWidth * 0.0036 * (0.55 + 0.7 * depth);
float core = exp(-pow(d / w, 2.0));
float halo = exp(-d / (w * 7.0)) * 0.18 * uGlow;
float line = (core + halo) * (0.45 + 0.9 * depth);
// Brighter where the stack bunches (neighbours close together).
vec3 c = t < 0.5 ? mix(lin(uA), lin(uB), t * 2.0) : mix(lin(uB), lin(uC), (t - 0.5) * 2.0);
tint += c * line;
acc += line;
}
col += tint * 0.55;
col += lin(uB) * acc * 0.03 * uGlow;
col *= uIntensity;
col *= 1.0 - 0.42 * smoothstep(0.4, 1.2, length(uv * vec2(0.75, 1.0)));
fragColor = vec4(finish(col, gl_FragCoord.xy), 1.0);
}
`;
export const fragment = compose(SCENE);
export const PRESET_NAMES = ["aurora", "ember", "ocean", "orchid", "gold", "ice"] as const;
export type FlowLinesPreset = (typeof PRESET_NAMES)[number];
export const presets: Record<FlowLinesPreset, ScenePreset> = {
aurora: {
colors: { base: "#04060f", a: "#33e0ff", b: "#6f5cff", c: "#ff4fd8" },
values: { lines: 44, amp: 1, freq: 1.7, width: 1, glow: 1, tilt: 1, speed: 0.8, intensity: 1.1 },
},
ember: {
colors: { base: "#0a0405", a: "#ff3b30", b: "#ff9a2e", c: "#ffe27a" },
values: { lines: 40, amp: 1.05, freq: 1.5, width: 1, glow: 1.1, tilt: 1, speed: 0.75, intensity: 1.1 },
},
ocean: {
colors: { base: "#020a10", a: "#00e0c0", b: "#1a8cff", c: "#a8f0ff" },
values: { lines: 48, amp: 0.95, freq: 1.8, width: 0.9, glow: 1, tilt: 1, speed: 0.7, intensity: 1.1 },
},
orchid: {
colors: { base: "#0a0412", a: "#ff6ad5", b: "#b24dff", c: "#6dc4ff" },
values: { lines: 42, amp: 1.1, freq: 1.6, width: 1, glow: 1.1, tilt: 1, speed: 0.8, intensity: 1.1 },
},
gold: {
colors: { base: "#0a0804", a: "#ffd978", b: "#f2a93c", c: "#fff3c8" },
values: { lines: 46, amp: 0.9, freq: 1.5, width: 0.9, glow: 1, tilt: 1, speed: 0.6, intensity: 1.1 },
},
ice: {
colors: { base: "#04060b", a: "#e8f4ff", b: "#9ec8ff", c: "#ffffff" },
values: { lines: 38, amp: 0.9, freq: 1.6, width: 0.8, glow: 0.9, tilt: 1, speed: 0.6, intensity: 1.05 },
},
};
export const flowLinesPiece = createScenePiece("flow-lines", fragment);components/vibebox/flow-lines/index.tsx
/**
* VibeBox Motion · Flow Lines
* 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 { FlowLines, type FlowLinesProps, type FlowLinesColor, type FlowLinesValue } from "./flow-lines.client";
export { PRESET_NAMES as FLOW_LINES_PRESETS, presets as flowLinesPresets, type FlowLinesPreset } from "./flow-lines.shader";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | What sits on the scene: a heading, a call to action, a whole section. |
| preset | "aurora" | "ember" | "ocean" | "orchid" | "gold" | "ice" | "aurora" | The look: aurora, ember, ocean, orchid, gold, ice. |
| colors | { base?: string; a?: string; b?: string; c?: string } | — | Overrides single palette colours with any CSS colour: base (the dark behind the lines), a (the colour of the lines nearest the viewer), b (the colour of the lines in the middle), c (the colour of the lines furthest away). |
| speed | number | 1 | Multiplies the preset's speed; 0 holds the scene still. |
| intensity | number | 1 | Multiplies the preset's brightness, 0.5 to 1.4. |
| lines | number | 44 | How many lines are drawn (8 to 64) (8 to 64; each look sets its own). |
| amp | number | 1 | How tall the wave is (0.2 to 2; each look sets its own). |
| freq | number | 1.7 | How many swells fit across the frame (0.6 to 3; each look sets its own). |
| width | number | 1 | How thick each line is (0.4 to 2; each look sets its own). |
| glow | number | 1 | How much light spreads around each line (0 to 2; each look sets its own). |
| tilt | number | 1 | How far the stack leans back (0 is flat) (0 to 2; each look sets its own). |
| interactive | boolean | true | The lines are drawn toward the pointer and pulled a little further when it is over the scene. |
| label | string | — | Accessible name; leave it out when the scene is only a background. |
| still | boolean | false | Hold one composed frame until false (a gallery card that plays on hover). |
| className | string | — | 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
labelit is an image with that name. - Touch
- A finger draws the lines toward it like the cursor does; scrolling is never blocked.
- Reduced motion
- One still frame of the lines; the wave does not drift.
Edge cases and limits
- The crossing where the lines bunch is the brightest part: keep text off it.
- At 64 lines on a small phone the lines nearly merge; the default 40 to 48 is tuned for hero size.
- It is almost all black: on a light page, wrap it in a rounded card rather than full-bleed.
Related

Burst
FreePleated silk seen from its centre: folds run out of a dark middle like a drawn curtain, with a fine cloth grain.
Backgrounds · WebGL2

Iridescent
FreeA film of oil on water, or holographic foil: slow bands of shifting colour with a soft sheen that follows the pointer.
Backgrounds · WebGL2

Spectrum Mesh
FreeA soft spectrum mesh gradient that drifts behind your content and leans toward the cursor; still under reduced motion.
Backgrounds · WebGL2