Aurora Background with Drifting Colour Bands

A northern lights background for a dark hero section. Four blurred bands of green, teal and violet drift, tilt and stretch across the sky, a layer of thin vertical rays slides sideways through them, and a mountain ridge closes the bottom edge. The whole effect is CSS keyframes on transform and opacity, so it costs almost nothing to run. The hero copy, two buttons and a three-figure readout sit on top and stack into one column on phones.

Code

import { useEffect, useRef, useState } from "react";

// Each band is one blurred strip of colour. Change the colours, where it sits and how fast it drifts.
const bands = [
  { from: "#34d399", to: "#22d3ee", top: "4%", left: "-20%", width: "110%", height: 230, opacity: 0.8, drift: "a", seconds: 17 },
  { from: "#2dd4bf", to: "#818cf8", top: "20%", left: "10%", width: "120%", height: 200, opacity: 0.65, drift: "b", seconds: 23 },
  { from: "#a78bfa", to: "#f472b6", top: "34%", left: "-30%", width: "100%", height: 170, opacity: 0.6, drift: "c", seconds: 29 },
  { from: "#4ade80", to: "#2dd4bf", top: "-6%", left: "30%", width: "90%", height: 180, opacity: 0.6, drift: "b", seconds: 19 },
];

const readings = [
  { label: "Kp index tonight", value: "6.3" },
  { label: "Peak window", value: "23:10 to 01:40" },
  { label: "Cloud cover", value: "12%" },
];

export default function AuroraBackgroundWithDriftingColourBands() {
  const ref = useRef<HTMLElement>(null);
  const [paused, setPaused] = useState(false);

  // Stop the animation clocks while the section is scrolled out of view.
  useEffect(() => {
    const el = ref.current;
    if (!el || typeof IntersectionObserver === "undefined") return;
    const io = new IntersectionObserver(([entry]) => setPaused(!entry.isIntersecting));
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <section ref={ref} className={`aurora-bands relative isolate overflow-hidden bg-[#040a18] text-white ${paused ? "aurora-bands-paused" : ""}`}>
      <style>{`
        @keyframes aurora-bands-drift-a { 0% { transform: translate3d(-10%, 0, 0) rotate(-11deg) scaleY(1); } 50% { transform: translate3d(8%, -14%, 0) rotate(-5deg) scaleY(1.35); } 100% { transform: translate3d(20%, 8%, 0) rotate(-14deg) scaleY(0.85); } }
        @keyframes aurora-bands-drift-b { 0% { transform: translate3d(12%, 6%, 0) rotate(8deg) scaleY(1.2); } 50% { transform: translate3d(-8%, -8%, 0) rotate(2deg) scaleY(0.8); } 100% { transform: translate3d(-22%, 10%, 0) rotate(11deg) scaleY(1.3); } }
        @keyframes aurora-bands-drift-c { 0% { transform: translate3d(0, 10%, 0) rotate(-4deg) scaleY(0.9); } 50% { transform: translate3d(24%, -10%, 0) rotate(-10deg) scaleY(1.4); } 100% { transform: translate3d(40%, 4%, 0) rotate(-2deg) scaleY(1); } }
        @keyframes aurora-bands-rays { from { transform: translate3d(0, 0, 0) skewX(-14deg); } to { transform: translate3d(-180px, 0, 0) skewX(-14deg); } }
        @keyframes aurora-bands-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
        @keyframes aurora-bands-rise { from { opacity: 0; transform: translate3d(0, 18px, 0); } to { opacity: 1; transform: none; } }
        @keyframes aurora-bands-fade { from { opacity: 0; } to { opacity: 1; } }
        .aurora-bands-sky { animation: aurora-bands-fade 1.4s ease-out both; }
        .aurora-bands-rays { animation: aurora-bands-rays 14s linear infinite; }
        .aurora-bands-glow { animation: aurora-bands-breathe 9s ease-in-out infinite; }
        .aurora-bands-rise { animation: aurora-bands-rise 0.9s cubic-bezier(0.16, 1, 0.3, 1) both; }
        .aurora-bands-paused * { animation-play-state: paused !important; }
        @media (prefers-reduced-motion: reduce) { .aurora-bands * { animation: none !important; } }
      `}</style>

      {/* The sky: blurred colour bands, then a layer of thin vertical rays cut out of them */}
      <div aria-hidden="true" className="aurora-bands-sky pointer-events-none absolute inset-0 -z-10">
        <div className="aurora-bands-glow absolute inset-0">
          {bands.map((b, i) => (
            <div
              key={i}
              className="absolute rounded-[100%] blur-3xl"
              style={{
                top: b.top,
                left: b.left,
                width: b.width,
                height: b.height,
                opacity: b.opacity,
                background: `linear-gradient(90deg, transparent, ${b.from} 28%, ${b.to} 68%, transparent)`,
                animation: `aurora-bands-drift-${b.drift} ${b.seconds}s ease-in-out ${-i * 4}s infinite alternate`,
              }}
            />
          ))}
        </div>
        <div
          className="absolute inset-0 overflow-hidden"
          style={{ maskImage: "linear-gradient(to bottom, transparent 0%, black 12%, black 34%, transparent 62%)", WebkitMaskImage: "linear-gradient(to bottom, transparent 0%, black 12%, black 34%, transparent 62%)" }}
        >
          <div
            className="aurora-bands-rays absolute -inset-y-10 -left-1/4 w-[160%] opacity-45 mix-blend-overlay blur-[5px]"
            style={{ backgroundImage: "repeating-linear-gradient(90deg, transparent 0px, rgba(255,255,255,0.6) 14px, transparent 34px, transparent 58px, rgba(255,255,255,0.35) 70px, rgba(255,255,255,0.1) 92px, transparent 110px, rgba(255,255,255,0.75) 128px, transparent 150px, transparent 180px)" }}
          />
        </div>
        {/* A few fixed stars and a darker floor so text stays readable */}
        <div
          className="absolute inset-0 opacity-70"
          style={{ backgroundImage: "radial-gradient(1px 1px at 12% 18%, #fff, transparent), radial-gradient(1px 1px at 27% 9%, #fff, transparent), radial-gradient(1.5px 1.5px at 44% 24%, #dbeafe, transparent), radial-gradient(1px 1px at 61% 7%, #fff, transparent), radial-gradient(1px 1px at 73% 21%, #fff, transparent), radial-gradient(1.5px 1.5px at 88% 12%, #e0f2fe, transparent), radial-gradient(1px 1px at 94% 31%, #fff, transparent), radial-gradient(1px 1px at 6% 37%, #fff, transparent), radial-gradient(1px 1px at 36% 41%, #fff, transparent), radial-gradient(1px 1px at 81% 44%, #fff, transparent)" }}
        />
        <div className="absolute inset-x-0 bottom-0 h-2/3 bg-gradient-to-t from-[#040a18] via-[#040a18]/80 to-transparent" />
        <svg viewBox="0 0 1440 160" preserveAspectRatio="none" className="absolute inset-x-0 bottom-0 h-16 w-full sm:h-28">
          <path d="M0 96 C120 70 190 40 300 62 C420 86 470 22 600 44 C720 64 800 96 930 70 C1050 46 1120 18 1240 48 C1320 68 1390 60 1440 44 L1440 160 L0 160 Z" fill="#0b1b3a" />
          <path d="M0 128 C140 96 260 120 400 100 C560 78 640 126 800 112 C960 98 1040 70 1200 100 C1300 118 1380 104 1440 92 L1440 160 L0 160 Z" fill="#01040c" />
        </svg>
      </div>

      <div className="mx-auto flex min-h-[680px] max-w-5xl flex-col items-center justify-center px-6 pb-28 pt-20 text-center sm:pb-36">
        <p className="aurora-bands-rise inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3.5 py-1.5 text-xs font-medium text-emerald-100 backdrop-blur" style={{ animationDelay: "0.1s" }}>
          <span className="relative flex h-2 w-2">
            <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-300 opacity-70 motion-reduce:animate-none" />
            <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-300" />
          </span>
          Strong activity forecast above 64° north
        </p>
        <h1 className="aurora-bands-rise mt-7 max-w-4xl text-4xl font-semibold leading-[1.05] tracking-tight sm:text-6xl" style={{ animationDelay: "0.2s" }}>
          Be outside when
          <br className="hidden sm:block" /> the{" "}
          <span className="bg-gradient-to-r from-emerald-200 via-teal-200 to-violet-300 bg-clip-text text-transparent">sky lights up</span>
        </h1>
        <p className="aurora-bands-rise mt-6 max-w-xl text-base leading-relaxed text-slate-300 sm:text-lg" style={{ animationDelay: "0.3s" }}>
          Lumen North watches solar wind and local cloud for your exact spot, then tells you an hour before the lights peak. No more refreshing five apps at midnight.
        </p>
        <div className="aurora-bands-rise mt-9 flex w-full flex-col items-center justify-center gap-3 sm:w-auto sm:flex-row" style={{ animationDelay: "0.4s" }}>
          <a href="#" className="inline-flex h-12 w-full items-center justify-center rounded-full bg-emerald-300 px-7 text-sm font-semibold text-slate-950 shadow-[0_0_40px_-8px_rgba(110,231,183,0.9)] transition hover:bg-emerald-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-200 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 sm:w-auto">
            Get tonight's forecast
          </a>
          <a href="#" className="inline-flex h-12 w-full items-center justify-center rounded-full border border-white/20 bg-white/5 px-7 text-sm font-semibold text-white backdrop-blur transition hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/70 sm:w-auto">
            See how it works
          </a>
        </div>
        <dl className="aurora-bands-rise mt-12 grid w-full max-w-2xl grid-cols-1 divide-y divide-white/10 rounded-2xl border border-white/10 bg-slate-950/40 text-left backdrop-blur-md sm:grid-cols-3 sm:divide-x sm:divide-y-0" style={{ animationDelay: "0.55s" }}>
          {readings.map((r) => (
            <div key={r.label} className="px-5 py-4">
              <dt className="text-xs text-slate-400">{r.label}</dt>
              <dd className="mt-1 text-lg font-semibold tabular-nums text-white">{r.value}</dd>
            </div>
          ))}
        </dl>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/aurora-background-with-drifting-colour-bands.json

When to use it

How to customize it

Accessibility

Questions

How does the aurora effect work?

Each band is a wide, short ellipse filled with a horizontal gradient that fades to transparent at both ends, then blurred heavily with `blur-3xl`. A keyframe animation moves, rotates and stretches it using `transform` only. Because the bands overlap, their colours blend as they cross. A second layer of repeating thin white stripes is blended over the top with `mix-blend-overlay`, which only brightens places that already have colour, so the stripes read as light rays inside the bands.

Does it need JavaScript or framer-motion?

No. The animation is plain CSS keyframes in a style element. The only script is a small observer that pauses the animation when the section leaves the screen. You can delete that effect and the component still works.

Will a large blur slow the page down?

Blur is expensive when the blurred content changes, but here only the transform changes. The browser blurs each band once and then moves the result on the compositor, which is cheap. Avoid animating the gradient colours or the blur radius themselves.

How do I make it loop without a visible jump?

The bands use `alternate`, so each one plays forward and then backward and never snaps back to the start. The rays move left by exactly 180 pixels, which is the width of one repeat of the stripe pattern, so that loop is seamless too. If you change the stripe pattern, keep the travel distance equal to its repeat width.

Can I use it on a light background?

Not as it is. The glow depends on bright colour over a near-black sky. For a light page use lower opacity bands and remove the overlay rays, or pick the gradient mesh background, which is designed for light themes.

Related: Starfield Background with Depth and Shooting Stars, Gradient Mesh Background with Flowing Blobs and Grain, Dot Matrix Background with Ripples and Pointer Spotlight.

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