Minimal Hero
Original · freeminimal typographic hero
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"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
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 quoteSimilar components
Browse all →
Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

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

App Preview Hero
OriginalA 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
OriginalA dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.

Image Backdrop Hero
OriginalA 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
OriginalA 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
OriginalA 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
OriginalA 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
OriginalA 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
MITCentered, 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
MITTwo-column hero on a dark background with a headline, paragraph, and two buttons beside a gradient media panel. Responsive column stack on mobile.

