Flowing Wave Lines Hero Background

A light hero section with thirty seven curved lines sweeping from the bottom right corner to the top left. Each line is an SVG path built from three stacked sine waves, stroked with one amber to rose to indigo gradient. On load the lines draw themselves in, then rise and fall a few pixels in a staggered ripple. A soft patch of the page colour sits behind the headline, copy and two buttons so the text stays easy to read. On phones the buttons stack and the lines crop instead of shrinking.

Code

// Adapted from Kokonut UI (https://github.com/kokonut-labs/kokonutui/blob/main/components/kokonutui/background-paths.tsx)
// Copyright (c) 2025 kokonutUI. MIT License: https://we.inc/components/licenses
import { useMemo } from "react";
import { motion, useReducedMotion } from "framer-motion";

// Three bands of lines. More lines, bigger waves and slower drift at the back.
const layers = [
  { name: "back", count: 12, amplitude: 150, segments: 10, opacity: 0.3, opacityStep: 0.035, width: 5, widthStep: 0.3, drift: 15, seconds: 8 },
  { name: "middle", count: 15, amplitude: 100, segments: 8, opacity: 0.24, opacityStep: 0.03, width: 4, widthStep: 0.25, drift: 10, seconds: 6 },
  { name: "front", count: 10, amplitude: 60, segments: 6, opacity: 0.2, opacityStep: 0.06, width: 3, widthStep: 0.2, drift: 6, seconds: 4.5 },
];

const points = ["Hourly forecast updates", "Tide gate timing", "Works offline at sea"];

/** One smooth line running from the bottom right corner to the top left, built from three stacked sine waves. */
function wavePath(index: number, amplitude: number, segments: number): string {
  const phase = index * 0.2;
  const pts: { x: number; y: number }[] = [];
  for (let i = 0; i <= segments; i++) {
    const t = i / segments;
    const eased = 1 - (1 - t) ** 2;
    const fade = 1 - eased * 0.3;
    const wave =
      Math.sin(t * Math.PI * 3 + phase) * amplitude * 0.7 * fade +
      Math.cos(t * Math.PI * 4 + phase) * amplitude * 0.3 * fade +
      Math.sin(t * Math.PI * 2 + phase) * amplitude * 0.2 * fade;
    pts.push({ x: 2400 - 4800 * eased, y: 800 + (-1600 + index * 25) * eased + wave });
  }
  return pts
    .map((p, i) => {
      if (i === 0) return `M ${p.x.toFixed(1)} ${p.y.toFixed(1)}`;
      const prev = pts[i - 1];
      const c1 = prev.x + (p.x - prev.x) * 0.4;
      const c2 = prev.x + (p.x - prev.x) * 0.6;
      return `C ${c1.toFixed(1)} ${prev.y.toFixed(1)}, ${c2.toFixed(1)} ${p.y.toFixed(1)}, ${p.x.toFixed(1)} ${p.y.toFixed(1)}`;
    })
    .join(" ");
}

export default function FlowingWaveLinesHeroBackground() {
  const reduce = useReducedMotion();
  const lines = useMemo(
    () =>
      layers.flatMap((layer, l) =>
        Array.from({ length: layer.count }, (_, i) => ({
          key: `${layer.name}-${i}`,
          d: wavePath(i + l * 3, layer.amplitude, layer.segments),
          opacity: Math.min(layer.opacity + i * layer.opacityStep, 0.85),
          width: layer.width + i * layer.widthStep,
          drift: layer.drift,
          seconds: layer.seconds,
          delay: i * 0.03 + l * 0.1,
          ripple: i * 0.14,
        })),
      ),
    [],
  );

  return (
    <section className="relative flex min-h-[640px] w-full items-center justify-center overflow-hidden bg-[#fbf7f0] px-4 py-20">
      <div className="pointer-events-none absolute inset-0" aria-hidden="true">
        <svg className="h-full w-full" fill="none" preserveAspectRatio="xMidYMid slice" viewBox="-2400 -800 4800 1600">
          <defs>
            <linearGradient id="fwl-stroke" x1="0%" x2="100%" y1="0%" y2="0%">
              <stop offset="0%" stopColor="#f59e0b" />
              <stop offset="50%" stopColor="#f43f5e" />
              <stop offset="100%" stopColor="#6366f1" />
            </linearGradient>
          </defs>
          {lines.map((line) => (
            <motion.path
              key={line.key}
              d={line.d}
              stroke="url(#fwl-stroke)"
              strokeLinecap="round"
              strokeWidth={line.width}
              initial={reduce ? false : { pathLength: 0, opacity: 0 }}
              animate={
                reduce
                  ? { pathLength: 1, opacity: line.opacity }
                  : { pathLength: 1, opacity: line.opacity, y: [0, -line.drift * 2.4, 0] }
              }
              transition={{
                pathLength: { duration: 1.1, delay: line.delay, ease: [0.2, 0.65, 0.3, 0.9] },
                opacity: { duration: 0.5, delay: line.delay },
                y: { duration: line.seconds, delay: line.ripple, repeat: Infinity, ease: "easeInOut" },
              }}
            />
          ))}
        </svg>
      </div>
      {/* Soft patch of paper behind the copy so the lines never fight the text. */}
      <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_46%_48%_at_50%_50%,rgba(251,247,240,0.94)_30%,rgba(251,247,240,0)_100%)]" aria-hidden="true" />

      <div className="relative z-10 mx-auto max-w-2xl text-center">
        <motion.p
          initial={reduce ? false : { opacity: 0, y: 12 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6, delay: 0.15 }}
          className="inline-flex items-center gap-2 rounded-full border border-stone-300 bg-white/80 px-3.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-stone-700"
        >
          <span className="h-1.5 w-1.5 rounded-full bg-rose-500" aria-hidden="true" />
          Passage planning for small boats
        </motion.p>
        <motion.h1
          initial={reduce ? false : { opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.9, delay: 0.25, ease: [0.2, 0.65, 0.3, 0.9] }}
          className="mt-6 text-4xl font-bold tracking-tighter text-stone-900 sm:text-6xl"
        >
          Chart a course that bends with the wind
        </motion.h1>
        <motion.p
          initial={reduce ? false : { opacity: 0, y: 16 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.8, delay: 0.4 }}
          className="mx-auto mt-5 max-w-xl text-base leading-relaxed text-stone-700 sm:text-lg"
        >
          Leeward reads the forecast every hour and redraws your route around squalls, tide gates and shipping lanes before you leave the dock.
        </motion.p>
        <motion.div
          initial={reduce ? false : { opacity: 0, y: 16 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.8, delay: 0.55 }}
          className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row"
        >
          <a href="#" className="inline-flex h-12 w-full items-center justify-center rounded-full bg-stone-900 px-7 text-sm font-semibold text-white transition hover:bg-stone-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-900 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fbf7f0] sm:w-auto">
            Plan a passage
          </a>
          <a href="#" className="inline-flex h-12 w-full items-center justify-center rounded-full border border-stone-400 bg-white/70 px-7 text-sm font-semibold text-stone-900 transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-900 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fbf7f0] sm:w-auto">
            See a sample route
          </a>
        </motion.div>
        <ul className="mt-9 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm font-medium text-stone-700">
          {points.map((point) => (
            <li key={point} className="flex items-center gap-2">
              <svg viewBox="0 0 20 20" className="h-4 w-4 text-rose-600" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <path d="M4 10.5l4 4 8-9" />
              </svg>
              {point}
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/flowing-wave-lines-hero-background.json

When to use it

How to customize it

Accessibility

Questions

How are the wave lines generated?

A small function walks along a diagonal from one corner to the other and adds three sine waves of different frequencies to the height at each step. The points are joined with cubic curves whose control points share the height of their neighbour, which gives smooth S shaped bends. Nothing is random, so the pattern is the same on the server and in the browser.

Why do the lines draw in and then move?

Each path animates `pathLength` from 0 to 1 once, which strokes it from end to end. After that only a vertical `transform` repeats, with a small delay per line, so neighbouring lines move one after another like a ripple.

Is animating this many SVG paths slow?

The repeating part animates a transform only, which browsers handle on the compositor. Thirty seven paths is comfortable on phones. If you raise the counts well past one hundred, drop the per line ripple and animate the three groups instead.

Can I use it on a dark page?

Yes. Set the section and patch colour to a dark value such as `#0a0a0a`, switch the text classes to light ones and raise the line `opacity` values a little, because thin lines read fainter on dark backgrounds.

Does it work without framer-motion?

The path maths has no dependency. You could replace the motion paths with plain paths and a CSS keyframe that translates each group, at the cost of the draw-in effect.

Adapted from Background Paths in Kokonut UI, used under its open-source licence. Licences and credits

Related: Aurora Background with Drifting Colour Bands, Gradient Mesh Background with Flowing Blobs and Grain, Soft Light Beams Canvas Background.

More animated backgrounds or all free UI components. Start for free to have a full site built around it.

More in Backgrounds

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap