Back to blog

How to Build a React Hero Section with Tailwind CSS

Published on March 16, 2026·5 min read

A hero section is the most critical piece of real estate on your landing page. Visitors form an impression within 50 milliseconds, so your hero has to communicate value immediately. This guide walks you through building a production-ready hero section in React with Tailwind CSS v4 and Framer Motion — the same approach used across dozens of high-converting SaaS landing pages.

What Makes a Great Hero

Before writing a single line of JSX, understand what the component needs to accomplish:

  • Headline — one clear value proposition, 6–10 words maximum
  • Subheadline — 1–2 sentences expanding on the headline, addressing the reader's problem
  • Primary CTA — one button, action-oriented label ("Start building", not "Submit")
  • Secondary CTA — optional, lower commitment ("See examples", "Watch demo")
  • Social proof — avatars, star rating, or a single number ("Trusted by 4,200+ teams")
  • Badge — optional announcement pill above the headline ("New: dark mode support")

Component Structure

Start with the TypeScript interface. Typed props prevent runtime errors and make the component self-documenting:

// HeroCentered.tsx
"use client";

import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";

interface HeroCenteredProps {
  badge?: string;
  title: string;
  titleAccent?: string;
  description: string;
  ctaLabel: string;
  ctaUrl: string;
  ctaSecondaryLabel?: string;
  ctaSecondaryUrl?: string;
  socialProof?: {
    avatars: string[];   // image URLs
    count: number;
    label: string;
  };
}

The "use client" directive is required because Framer Motion uses browser APIs. In Next.js 15, all components are Server Components by default, so you opt into client rendering explicitly.

Layout and Theming

Instead of hardcoding colors like bg-white or text-gray-900, use CSS custom properties. This makes the component theme-aware — switch a data-theme attribute on a parent element and the whole section repaints:

export default function HeroCentered({ title, titleAccent, description, ctaLabel, ctaUrl, badge }: HeroCenteredProps) {
  return (
    <section
      style={{
        position: "relative",
        overflow: "hidden",
        paddingTop: "var(--section-padding-y-lg)",
        paddingBottom: "var(--section-padding-y-lg)",
        background: "var(--color-background)",
        minHeight: "85vh",
        display: "flex",
        alignItems: "center",
      }}
    >
      <div
        style={{
          width: "100%",
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
        }}
      >
        <div style={{ maxWidth: "720px", margin: "0 auto", textAlign: "center" }}>
          {/* Content goes here */}
        </div>
      </div>
    </section>
  );
}

The CSS token --section-padding-y-lg is 8rem by default. --container-max-width is 1280px. Both are defined globally and inherited automatically.

Framer Motion Entrance Animations with Stagger

A staggered entrance makes the hero feel polished without being distracting. Each element enters 60–80ms after the previous one:

const EASE = [0.16, 1, 0.3, 1] as const; // custom spring-like cubic-bezier

// Badge
<motion.div
  initial={{ opacity: 0, y: 8 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.4, ease: EASE }}
>
  <span style={{
    padding: "0.5rem 1.25rem",
    borderRadius: "var(--radius-full)",
    border: "1px solid var(--color-accent-border)",
    background: "var(--color-accent-subtle)",
    color: "var(--color-foreground-muted)",
    fontSize: "0.8125rem",
    fontWeight: 500,
  }}>
    {badge}
  </span>
</motion.div>

// Headline — starts 60ms after badge
<motion.h1
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.6, delay: 0.06, ease: EASE }}
  style={{
    fontSize: "clamp(2.5rem, 5vw, 4.5rem)",
    fontWeight: 700,
    lineHeight: 1.08,
    letterSpacing: "-0.03em",
    color: "var(--color-foreground)",
  }}
>
  {title}{" "}
  <em style={{
    fontStyle: "italic",
    fontWeight: 400,
    textDecoration: "underline",
    textDecorationColor: "var(--color-accent)",
    textDecorationThickness: "3px",
    textUnderlineOffset: "6px",
  }}>
    {titleAccent}
  </em>
</motion.h1>

// Description — starts 140ms after badge
<motion.p
  initial={{ opacity: 0, y: 12 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.5, delay: 0.14, ease: EASE }}
  style={{
    fontSize: "1.125rem",
    lineHeight: 1.7,
    color: "var(--color-foreground-muted)",
  }}
>
  {description}
</motion.p>

The clamp() on font-size handles responsive scaling without media queries — it grows linearly from 2.5rem on mobile to 4.5rem on wide screens.

Background Treatments

Three techniques work particularly well for hero backgrounds:

1. Accent Glow

A blurred, semi-transparent circle in the accent color creates depth without distracting from the copy:

<div
  aria-hidden
  style={{
    position: "absolute",
    top: "-20%",
    left: "50%",
    transform: "translateX(-50%)",
    width: "60vw",
    height: "60vw",
    maxWidth: "800px",
    borderRadius: "50%",
    background: "var(--color-accent)",
    opacity: 0.06,
    filter: "blur(100px)",
    pointerEvents: "none",
  }}
/>

The aria-hidden attribute hides the decorative element from screen readers. Opacity 0.06 is subtle enough to work on any theme.

2. Dot Grid

A repeating SVG dot grid adds texture:

<div
  aria-hidden
  style={{
    position: "absolute",
    inset: 0,
    backgroundImage: "radial-gradient(circle, var(--color-border) 1px, transparent 1px)",
    backgroundSize: "24px 24px",
    opacity: 0.5,
    maskImage: "radial-gradient(ellipse 70% 60% at 50% 50%, black, transparent)",
  }}
/>

The maskImage fades the grid at the edges so it doesn't clash with the bottom section.

3. Subtle Noise

A 200×200px noise texture SVG data URI adds a premium tactile feel:

background-image: url("data:image/svg+xml,...");
opacity: 0.03;

CTA Button

The primary button uses the accent color with a smooth hover transition:

<a
  href={ctaUrl}
  style={{
    display: "inline-flex",
    alignItems: "center",
    gap: "8px",
    padding: "0.875rem 2rem",
    borderRadius: "var(--radius-full)",
    background: "var(--color-accent)",
    color: "var(--color-foreground)",
    fontWeight: 600,
    fontSize: "0.9375rem",
    textDecoration: "none",
    transition: "background var(--duration-normal) var(--ease-out)",
  }}
  onMouseEnter={(e) => {
    (e.target as HTMLAnchorElement).style.background = "var(--color-accent-hover)";
  }}
  onMouseLeave={(e) => {
    (e.target as HTMLAnchorElement).style.background = "var(--color-accent)";
  }}
>
  {ctaLabel}
  <ArrowRight style={{ width: 16, height: 16 }} />
</a>

Accessibility Checklist

Before shipping, verify:

  • <section> has an accessible name via aria-label or a visible <h1> inside it
  • CTA links have descriptive text (not "Click here")
  • Decorative elements are aria-hidden
  • Contrast ratio meets WCAG AA (4.5:1 for normal text, 3:1 for large)
  • The component respects prefers-reduced-motion:
import { useReducedMotion } from "framer-motion";

const shouldReduceMotion = useReducedMotion();

// Use duration: 0 when shouldReduceMotion is true
transition={{ duration: shouldReduceMotion ? 0 : 0.6, ease: EASE }}

Ready-Made Hero Variants

Building a hero from scratch takes time, and conversion-rate optimization is an ongoing experiment. Instead of starting from zero, browse the Incubator hero catalog — it includes centered heroes, split-layout heroes, fullscreen video backgrounds, animated text heroes, and more, all built with the same CSS token system and Framer Motion patterns described above. Each variant ships with TypeScript props and mock data, ready to drop into your Next.js app.

VA

Victor Aubague

Developer & creator of Incubator

Full-stack developer specialized in React, Next.js, and TypeScript. I built Incubator to help developers ship beautiful interfaces faster — all components are crafted from real client projects and production code.

LinkedIn
How to Build a React Hero Section with Tailwind CSS — Incubator