Web InnoventixFreeCode

Minimal Hero

Original · free

minimal typographic hero

byWeb InnoventixReact + Tailwind
heroxminimalheroes
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/herox-minimal.json
herox-minimal.tsx
"use client";

import { useEffect, useId, useState, type FormEvent } from "react";
import {
  AnimatePresence,
  motion,
  useReducedMotion,
  type Variants,
} from "motion/react";

const ROTATING = ["interfaces", "prototypes", "brand systems", "launch pages"];

const container: Variants = {
  hidden: {},
  show: {
    transition: { staggerChildren: 0.11, delayChildren: 0.08 },
  },
};

const item: Variants = {
  hidden: { opacity: 0, y: 18 },
  show: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.6, ease: "easeOut" },
  },
};

export default function HeroxMinimal() {
  const reduce = useReducedMotion();
  const fieldId = useId();
  const msgId = `${fieldId}-msg`;

  const [wordIndex, setWordIndex] = useState(0);
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<"idle" | "error" | "success">("idle");

  useEffect(() => {
    if (reduce) return;
    const timer = window.setInterval(() => {
      setWordIndex((i) => (i + 1) % ROTATING.length);
    }, 2600);
    return () => window.clearInterval(timer);
  }, [reduce]);

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
    setStatus(valid ? "success" : "error");
  }

  const word = ROTATING[wordIndex];

  return (
    <section className="relative w-full overflow-hidden bg-neutral-50 px-6 py-24 text-neutral-900 sm:py-32 lg:py-40 dark:bg-zinc-950 dark:text-neutral-50">
      <style>{`
        @keyframes hxmin-pulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.35; transform: scale(0.7); }
        }
        @keyframes hxmin-float {
          0%, 100% { transform: translate3d(0, 0, 0); }
          50% { transform: translate3d(0, -20px, 0); }
        }
        @keyframes hxmin-drift {
          0%, 100% { transform: translate3d(0, 0, 0); }
          50% { transform: translate3d(0, 24px, 0); }
        }
        .hxmin-dot { animation: hxmin-pulse 2.1s ease-in-out infinite; }
        .hxmin-blob-a { animation: hxmin-float 11s ease-in-out infinite; }
        .hxmin-blob-b { animation: hxmin-drift 13s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .hxmin-dot, .hxmin-blob-a, .hxmin-blob-b { animation: none !important; }
        }
      `}</style>

      {/* Ambient background */}
      <div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
        <div className="hxmin-blob-a absolute -top-32 left-1/2 h-[38rem] w-[38rem] -translate-x-1/2 rounded-full bg-gradient-to-br from-indigo-300/50 via-violet-300/40 to-transparent blur-3xl dark:from-indigo-600/25 dark:via-violet-600/20" />
        <div className="hxmin-blob-b absolute -bottom-40 right-[-8rem] h-[30rem] w-[30rem] rounded-full bg-gradient-to-tr from-sky-300/40 via-emerald-200/30 to-transparent blur-3xl dark:from-sky-700/20 dark:via-emerald-700/15" />
        <div
          className="absolute inset-0 opacity-[0.35] dark:opacity-[0.18]"
          style={{
            backgroundImage:
              "linear-gradient(to right, currentColor 1px, transparent 1px), linear-gradient(to bottom, currentColor 1px, transparent 1px)",
            backgroundSize: "72px 72px",
            color: "rgb(120 120 130 / 0.25)",
            maskImage:
              "radial-gradient(ellipse 70% 60% at 50% 40%, black 30%, transparent 78%)",
            WebkitMaskImage:
              "radial-gradient(ellipse 70% 60% at 50% 40%, black 30%, transparent 78%)",
          }}
        />
      </div>

      <motion.div
        variants={container}
        initial={reduce ? "show" : "hidden"}
        animate="show"
        className="relative mx-auto flex max-w-4xl flex-col items-center text-center"
      >
        {/* Eyebrow */}
        <motion.div variants={item}>
          <span className="inline-flex items-center gap-2 rounded-full border border-neutral-200 bg-white/70 px-4 py-1.5 text-xs font-medium tracking-wide text-neutral-600 shadow-sm backdrop-blur dark:border-zinc-800 dark:bg-zinc-900/60 dark:text-neutral-300">
            <span className="relative flex h-2 w-2">
              <span className="hxmin-dot absolute inline-flex h-full w-full rounded-full bg-emerald-500" />
              <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
            </span>
            Basis 2.0 — now in open beta
          </span>
        </motion.div>

        {/* Headline */}
        <motion.h1
          variants={item}
          className="mt-8 text-balance text-5xl font-semibold leading-[1.05] tracking-tight sm:text-6xl lg:text-7xl"
        >
          A calmer canvas for
          <br className="hidden sm:block" /> building{" "}
          <span className="relative inline-flex min-w-[8ch] justify-center align-baseline">
            {reduce ? (
              <span className="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent dark:from-indigo-400 dark:to-violet-400">
                {word}
              </span>
            ) : (
              <AnimatePresence mode="wait" initial={false}>
                <motion.span
                  key={word}
                  initial={{ opacity: 0, y: "0.4em" }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: "-0.4em" }}
                  transition={{ duration: 0.4, ease: "easeOut" }}
                  className="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent dark:from-indigo-400 dark:to-violet-400"
                >
                  {word}
                </motion.span>
              </AnimatePresence>
            )}
          </span>
        </motion.h1>

        {/* Subcopy */}
        <motion.p
          variants={item}
          className="mt-7 max-w-xl text-pretty text-lg leading-relaxed text-neutral-600 sm:text-xl dark:text-neutral-400"
        >
          Basis is the minimal workspace where notes, design, and working
          prototypes share one infinite page — so your best ideas stop getting
          lost between twelve open tabs.
        </motion.p>

        {/* Email capture */}
        <motion.div variants={item} className="mt-10 w-full max-w-md">
          <form onSubmit={handleSubmit} noValidate className="flex flex-col gap-3 sm:flex-row">
            <label htmlFor={fieldId} className="sr-only">
              Work email address
            </label>
            <input
              id={fieldId}
              type="email"
              inputMode="email"
              autoComplete="email"
              placeholder="you@studio.com"
              value={email}
              onChange={(e) => {
                setEmail(e.target.value);
                if (status !== "idle") setStatus("idle");
              }}
              aria-invalid={status === "error"}
              aria-describedby={status === "idle" ? undefined : msgId}
              className="w-full flex-1 rounded-xl border border-neutral-300 bg-white px-4 py-3 text-base text-neutral-900 shadow-sm outline-none transition placeholder:text-neutral-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/40 dark:border-zinc-700 dark:bg-zinc-900 dark:text-neutral-50 dark:placeholder:text-neutral-500"
            />
            <button
              type="submit"
              className="inline-flex shrink-0 items-center justify-center gap-2 rounded-xl bg-neutral-900 px-6 py-3 text-base font-medium text-white shadow-sm transition hover:bg-neutral-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 active:scale-[0.98] dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200"
            >
              Request access
              <svg
                aria-hidden="true"
                viewBox="0 0 20 20"
                className="h-4 w-4"
                fill="none"
                stroke="currentColor"
                strokeWidth={2}
                strokeLinecap="round"
                strokeLinejoin="round"
              >
                <path d="M4 10h11M11 5l5 5-5 5" />
              </svg>
            </button>
          </form>

          <p
            id={msgId}
            role="status"
            aria-live="polite"
            className={`mt-3 min-h-[1.25rem] text-sm ${
              status === "error"
                ? "text-rose-600 dark:text-rose-400"
                : status === "success"
                  ? "text-emerald-600 dark:text-emerald-400"
                  : "text-neutral-500 dark:text-neutral-500"
            }`}
          >
            {status === "error"
              ? "Please enter a valid email address."
              : status === "success"
                ? "You're on the list — check your inbox for a confirmation."
                : "No credit card. Free while in beta. Leave anytime."}
          </p>
        </motion.div>

        {/* Secondary action + social proof */}
        <motion.div
          variants={item}
          className="mt-10 flex flex-col items-center gap-6 sm:flex-row sm:gap-8"
        >
          <a
            href="#tour"
            className="group inline-flex items-center gap-2 rounded-md text-sm font-medium text-neutral-700 transition hover:text-neutral-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-indigo-500 dark:text-neutral-300 dark:hover:text-white"
          >
            <span className="flex h-8 w-8 items-center justify-center rounded-full border border-neutral-300 transition group-hover:border-indigo-500 group-hover:bg-indigo-50 dark:border-zinc-700 dark:group-hover:bg-indigo-950/40">
              <svg aria-hidden="true" viewBox="0 0 20 20" className="h-3.5 w-3.5 fill-current">
                <path d="M6.3 4.5a1 1 0 0 1 1.52-.85l7.2 4.5a1 1 0 0 1 0 1.7l-7.2 4.5A1 1 0 0 1 6.3 13.5V4.5Z" />
              </svg>
            </span>
            Watch the 2-min tour
          </a>

          <div className="flex items-center gap-3">
            <div className="flex -space-x-2">
              {[
                "from-indigo-400 to-violet-500",
                "from-emerald-400 to-sky-500",
                "from-amber-400 to-rose-500",
                "from-sky-400 to-indigo-500",
              ].map((g, i) => (
                <span
                  key={i}
                  className={`h-8 w-8 rounded-full border-2 border-neutral-50 bg-gradient-to-br ${g} dark:border-zinc-950`}
                />
              ))}
            </div>
            <span className="text-sm text-neutral-500 dark:text-neutral-400">
              Joined by 4,000+ independent makers
            </span>
          </div>
        </motion.div>
      </motion.div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →
Spotlight Hero

Spotlight Hero

Original

A centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero

Split Hero

Original

A two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero

Gradient Spotlight Hero

Original

A minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

App Preview Hero

App Preview Hero

Original

A centred hero that pairs headline copy with a realistic product dashboard mock built entirely from markup, complete with browser chrome and a floating notification card.

Waitlist Capture Hero

Waitlist Capture Hero

Original

A dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.

Image Backdrop Hero

Image Backdrop Hero

Original

A cinematic full-bleed hero with a layered image-style backdrop, overlaid left-aligned copy and a trusted-by logos strip.

Gradient Mesh Hero with Staggered Text Reveal

Gradient Mesh Hero with Staggered Text Reveal

Original

A full-screen hero pairing a drifting animated gradient-mesh backdrop with a word-by-word staggered blur-in headline and a logo marquee under the fold.

Typewriter Rotating Words Hero

Typewriter Rotating Words Hero

Original

A hero whose headline cycles through rotating words with a live typewriter caret over an animated grid backdrop, degrading to a gentle word swap when reduced motion is on.

Floating UI Cards Hero with Mouse Parallax

Floating UI Cards Hero with Mouse Parallax

Original

A hero with glassy floating UI cards that drift continuously and shift on mouse-parallax depth around a staggered centre headline.

Aurora Hero with Shimmer CTA and Logo Marquee

Aurora Hero with Shimmer CTA and Logo Marquee

Original

A dark hero with animated aurora light beams, a shimmering sweep across the primary CTA, and dual-direction logo marquees beneath the fold.

Gradient Conversion Hero

Gradient Conversion Hero

MIT

Centered, full-screen marketing hero with a colour-accent headline word, supporting copy, and dual primary/secondary CTA buttons. Dark-mode ready and dependency-free.

Split Showcase Hero

Split Showcase Hero

MIT

Two-column hero on a dark background with a headline, paragraph, and two buttons beside a gradient media panel. Responsive column stack on mobile.