Skip to content
Black FridayBlack Friday launch price: Lifetime $149 until 30 Nov. Regular price $299 from 1 Dec.ends in 15d 03:13:38Get Lifetime →
loopkit
freeNew127 lines · framer-motion

Hero Gradient Beams

Slow light beams, zero assets to load. A centred hero with 2–5 rotating conic-gradient beams layered behind the headline, built from pure CSS and framer-motion with no canvas or WebGL.

Use it for
Launch pages that want depth and motion on the first screen but need to stay light — no shader, no particles, just colour and rotation.
Themes
Light + dark
License
Commercial use
Live
New · AI workspace

Light, focused on one idea

Slow rotating beams give the first screen depth without a single asset to load.

tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion } from "framer-motion";
import { cn } from "@/lib/utils";

function useReducedMotionSafe() {
  const prefersReduced = useReducedMotion();
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  return mounted ? !!prefersReduced : false;
}

/**
 * Centred hero with rotating conic-gradient light beams radiating from behind
 * the headline. Pure CSS/SVG + framer-motion, no canvas or WebGL — the
 * cheapest background in the set, built for the free tier.
 */

export interface HeroGradientBeamsProps {
  eyebrow?: string;
  headline?: string;
  subcopy?: string;
  primaryCta?: string;
  secondaryCta?: string;
  /** Number of beam layers (2–5). */
  beamCount?: number;
  /** Rotation speed multiplier. */
  speed?: number;
  className?: string;
}

const BEAM_DEFS = [
  { colorVar: "--signal", size: 150, opacity: 0.4, dur: 34, dir: 1 },
  { colorVar: "--info", size: 120, opacity: 0.3, dur: 26, dir: -1 },
  { colorVar: "--lime", size: 95, opacity: 0.22, dur: 20, dir: 1 },
  { colorVar: "--ok", size: 70, opacity: 0.18, dur: 16, dir: -1 },
  { colorVar: "--signal", size: 50, opacity: 0.16, dur: 12, dir: 1 },
];

export function HeroGradientBeams({
  eyebrow = "New · AI workspace",
  headline = "Light, focused on one idea",
  subcopy = "Slow rotating beams give the first screen depth without a single asset to load.",
  primaryCta = "Get started",
  secondaryCta = "Explore features",
  beamCount = 3,
  speed = 1,
  className,
}: HeroGradientBeamsProps) {
  const reduce = useReducedMotionSafe();
  const beams = BEAM_DEFS.slice(0, Math.max(2, Math.min(5, beamCount)));

  return (
    <section className={cn("relative isolate overflow-hidden bg-background", className)}>
      <div className="absolute inset-0 flex items-center justify-center" aria-hidden>
        <div className="relative size-[60rem] max-w-none">
          {beams.map((b, i) => (
            <motion.div
              key={i}
              className="absolute inset-0 rounded-full"
              style={{
                background: `conic-gradient(from 0deg, transparent, color-mix(in srgb, var(${b.colorVar}) ${Math.round(b.opacity * 100)}%, transparent) ${b.size / 4}deg, transparent ${b.size / 2}deg, transparent)`,
                filter: "blur(18px)",
              }}
              animate={reduce ? undefined : { rotate: b.dir * 360 }}
              transition={reduce ? undefined : { duration: b.dur / Math.max(0.2, speed), repeat: Infinity, ease: "linear" }}
            />
          ))}
          <div className="absolute inset-[30%] rounded-full bg-[radial-gradient(circle,var(--signal),transparent_70%)] opacity-30 blur-3xl" />
        </div>
      </div>
      <div
        className="absolute inset-0 [background-image:radial-gradient(circle_at_1px_1px,var(--border)_1px,transparent_1px)] [background-size:28px_28px] opacity-[0.25] [mask-image:radial-gradient(ellipse_70%_55%_at_50%_35%,black,transparent_75%)]"
        aria-hidden
      />
      <div className="absolute inset-0 bg-gradient-to-b from-background/0 via-background/10 to-background" aria-hidden />

      <div className="relative mx-auto flex min-h-[30rem] max-w-4xl flex-col items-center px-4 pb-20 pt-20 text-center sm:min-h-[34rem] sm:px-6 sm:pt-28">
        <motion.span
          initial={reduce ? false : { opacity: 0, y: -8 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.4 }}
          className="inline-flex items-center gap-2 rounded-full border border-border bg-card/80 px-3 py-1.5 font-mono text-[11px] uppercase tracking-[0.16em] text-muted-foreground backdrop-blur"
        >
          <span className="size-1.5 rounded-full bg-signal" aria-hidden />
          {eyebrow}
        </motion.span>

        <motion.h1
          initial={reduce ? false : { opacity: 0, y: 14 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.55, delay: 0.1 }}
          className="mt-6 font-display text-[clamp(2.4rem,6.6vw,5rem)] font-extrabold leading-[0.96] tracking-[-0.04em]"
        >
          {headline}
        </motion.h1>

        <motion.p
          initial={reduce ? false : { opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, delay: 0.28 }}
          className="mt-5 max-w-lg text-lg leading-relaxed text-muted-foreground"
        >
          {subcopy}
        </motion.p>

        <motion.div
          initial={reduce ? false : { opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, delay: 0.38 }}
          className="mt-8 flex flex-wrap items-center justify-center gap-3"
        >
          <a href="#" className="inline-flex h-12 items-center rounded-full bg-primary px-6 text-[15px] font-semibold text-primary-foreground transition hover:opacity-90">
            {primaryCta} →
          </a>
          <a href="#" className="inline-flex h-12 items-center rounded-full border-2 border-foreground px-6 text-[15px] font-semibold transition hover:bg-foreground hover:text-background">
            {secondaryCta}
          </a>
        </motion.div>
      </div>
    </section>
  );
}

export default HeroGradientBeams;

How it behaves

  • →Each beam is a blurred conic-gradient circle rotating at its own speed and direction via framer-motion's animate, layered from largest/dimmest to smallest/brightest.
  • →beamCount (2–5) controls how many layers render; speed scales every beam's rotation duration together so they stay in proportion.
  • →A soft radial glow sits at the centre, and a dot-grid overlay fades out toward the edges to keep the composition anchored behind the headline.
  • →prefers-reduced-motion removes the animate/transition props entirely, leaving the beams static instead of looping.

Accessibility

  • ✓All gradient and grid layers are aria-hidden; only the eyebrow, heading, copy and two real <a> CTAs are in the accessibility tree.
  • ✓Headline is a single <h1>; CTAs keep the design system's visible focus ring.
  • ✓No canvas/WebGL dependency, so the effect degrades to a plain rotation with CSS support alone.
  • ✓Honours prefers-reduced-motion.