Swish UI
All componentsBackgrounds Free

Aurora gradient

Three soft, blurred colour blobs drift slowly behind a heading.

It imports two small hooks from @/lib/motion and the colour tokens; the zip includes both.

Demo.tsx

"use client";
import { useRef } from "react";
import { useInView } from "@/lib/motion";
import styles from "./demo.module.css";

export default function Demo() {
  const root = useRef<HTMLDivElement>(null);
  const inView = useInView(root);

  return (
    <div ref={root} className={`${styles.root} ${inView ? "" : styles.paused}`}>
      <div className={styles.sky} aria-hidden="true">
        <span className={`${styles.blob} ${styles.one}`} />
        <span className={`${styles.blob} ${styles.two}`} />
        <span className={`${styles.blob} ${styles.three}`} />
      </div>
      <div className={styles.copy}>
        <span className={styles.kicker}>For villa hosts</span>
        <h2 className={styles.heading}>Sell the nights between your bookings</h2>
        <p className={styles.sub}>StayGap finds open nights and offers them to guests already staying.</p>
      </div>
    </div>
  );
}

demo.module.css

.root { container-type: inline-size; position: relative; isolation: isolate; width: 100%; height: 100%; display: grid; place-items: center; padding: 20px; overflow: hidden; background: var(--white); }
.sky { position: absolute; inset: 0; z-index: -1; filter: blur(48px); opacity: .6; }
.blob { position: absolute; width: 60%; aspect-ratio: 1; border-radius: 50%; animation: drift 22s ease-in-out infinite alternate; }
.one { left: -10%; top: -30%; background: var(--accent-soft); }
.two { right: -15%; top: 10%; background: var(--sky); animation-duration: 27s; animation-delay: -9s; }
.three { left: 20%; bottom: -45%; background: var(--sand); animation-duration: 31s; animation-delay: -15s; }
@keyframes drift {
  0% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(12%, 10%) scale(1.12); }
  100% { transform: translate(-8%, 4%) scale(.94); }
}
.paused .blob { animation-play-state: paused; }

.copy { display: grid; justify-items: center; gap: 10px; max-width: 30em; text-align: center; }
.kicker { font-size: 12px; font-weight: 600; color: var(--accent-text); }
.heading { font-size: clamp(22px, 5cqi, 40px); line-height: 1.1; letter-spacing: -0.03em; font-weight: 650; text-wrap: balance; }
.sub { font-size: 14px; color: var(--muted); max-width: 26em; }

@media (prefers-reduced-motion: reduce) {
  .blob { animation: none; }
}

Use it for

  • A single hero or closing section that needs warmth
  • Brand moments where the palette should feel soft

Avoid

  • A glowing gradient behind hero text is a common generated UI tell, so keep it faint and slow
  • Behind long text or dense UI, where contrast suffers
  • Every section of a page

How it works

Three absolutely positioned circles in theme colours sit in a layer with a single blur filter. Each runs the same transform keyframes (translate and scale) with a different duration and negative delay, alternating, so they never line up. Animation pauses through animation-play-state when the section is off screen.

Accessibility

The layer is aria-hidden and the heading keeps full contrast on a light base. Reduced motion stops the drift and keeps the soft gradient.

Found on