Tailwind Hero: 16 Hero Section Examples to Copy

Hero sections are usually the first thing people copy out of a Tailwind template, which is also where version problems show up first. Tailwind CSS v4 renamed bg-gradient-to-r to bg-linear-to-r and removed bg-opacity-*.

Below are 16 Tailwind hero examples you can copy, followed by a guide to layouts, backgrounds, dark mode, performance and accessibility.

By Bogdan Sandu ยท Updated October 29, 2026

Every example is built with Tailwind CSS v4.3 utility classes. To try one quickly, add the Play CDN tags below to your page and paste the example's HTML, CSS and JS. The dark variant line makes dark: classes follow a .dark class instead of the system setting. For production, install Tailwind with Vite or the CLI.

<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
  @custom-variant dark (&:where(.dark, .dark *));
</style>

Centered, split, full-bleed and editorial heroes

The four layouts most landing pages start from: a centered headline with two buttons, a split hero with a photo, a full-bleed photo under a solid overlay and a magazine cover with oversized type.

Centered headline with two CTAs

  • text-balance
  • text-4xl md:text-6xl
  • flex-col sm:flex-row
  • max-w-2xl mx-auto

The whole layout is text-center on one width-capped wrapper, with mx-auto on the subheadline so its max-w-2xl block stays centered. text-balance evens out the headline lines, and the size steps from text-4xl to md:text-6xl so phones get a lighter headline. The button row stacks with flex-col and turns into a row from sm:, and both buttons carry focus-visible outlines.

View code
<div class="w-full bg-[#faf8f5]">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-stone-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-orange-800">
      <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="8" fill="#9a3412"/><path d="M9 19.5c2-6 5.5-10 10-11.5-1 4.5-4 8.5-10 11.5Zm0 0 4-4" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
      Quillmark
    </a>
    <nav aria-label="Main" class="hidden items-center gap-7 text-sm font-medium text-stone-700 md:flex">
      <a href="#" class="rounded-sm hover:text-stone-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-orange-800">Product</a>
      <a href="#" class="rounded-sm hover:text-stone-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-orange-800">Templates</a>
      <a href="#" class="rounded-sm hover:text-stone-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-orange-800">Pricing</a>
    </nav>
    <a href="#" class="rounded-lg px-3 py-2 text-sm font-semibold text-stone-800 hover:bg-stone-200/60 focus-visible:outline-2 focus-visible:outline-orange-800">Sign in</a>
  </header>
  <div class="mx-auto max-w-4xl px-5 pt-14 pb-20 text-center md:pt-20 md:pb-28">
    <h1 class="text-4xl font-semibold tracking-tight text-balance text-stone-900 md:text-6xl">Write the docs your team will actually read</h1>
    <p class="mx-auto mt-6 max-w-2xl text-lg/8 text-pretty text-stone-600">Quillmark turns scattered notes into one searchable handbook, with page owners, review dates and a nudge when something goes stale.</p>
    <div class="mt-10 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-4">
      <a href="#" class="inline-flex min-h-12 w-full items-center justify-center rounded-lg bg-[#9a3412] px-6 text-base font-semibold text-white shadow-xs hover:bg-[#7c2d12] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#9a3412] sm:w-auto">Start writing free</a>
      <a href="#" class="group inline-flex min-h-12 items-center justify-center gap-1.5 rounded-lg px-4 text-base font-semibold text-stone-900 hover:bg-stone-200/60 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#9a3412]">See a live handbook <span aria-hidden="true" class="motion-safe:transition-transform group-hover:translate-x-0.5">&rarr;</span></a>
    </div>
    <p class="mt-6 text-sm text-stone-600">Free for teams up to 5. No card needed.</p>
  </div>
</div>

Split hero with a photo

  • md:grid-cols-[1.05fr_1fr]
  • aspect-[4/3] object-cover
  • fetchpriority
  • absolute bottom-4 left-4

One grid stacks the copy above the photo on phones and puts them side by side from md: with grid-cols-[1.05fr_1fr]. The copy comes first in the markup, so screen readers and phones meet the headline before the image. The photo is a real img with descriptive alt text, explicit width and height, fetchpriority high and no lazy loading, because it is the likely LCP element; aspect-[4/3] object-cover keeps its crop stable.

View code
<div class="w-full bg-white">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-sky-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-sky-800">
      <svg class="size-7 text-sky-800" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M3 21 10 8l4 7 3-4 8 10Z"/><path d="M3 24h22" stroke-linecap="round"/></svg>
      Nordvik Fjord Tours
    </a>
    <a href="#" class="hidden rounded-lg px-3 py-2 text-sm font-semibold text-sky-900 hover:bg-sky-50 focus-visible:outline-2 focus-visible:outline-sky-800 sm:inline-flex">Group bookings</a>
  </header>
  <div class="mx-auto grid max-w-6xl items-center gap-10 px-5 pt-8 pb-16 md:grid-cols-[1.05fr_1fr] md:gap-12 md:px-8 md:pt-12 md:pb-24">
    <div>
      <h1 class="text-4xl font-bold tracking-tight text-balance text-sky-950 md:text-5xl">See the fjord from the ledge, then from the water</h1>
      <p class="mt-5 max-w-xl text-lg/8 text-slate-600">A morning hike to the cliff edge, lunch in a boathouse and an afternoon on a quiet electric boat. Small groups of twelve, guides who grew up here.</p>
      <div class="mt-8 flex flex-col gap-3 sm:flex-row">
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-full bg-sky-800 px-6 text-base font-semibold text-white hover:bg-sky-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-800">Book a day trip</a>
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-full px-6 text-base font-semibold text-sky-900 ring-1 ring-sky-900/20 ring-inset hover:bg-sky-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-800">View the route</a>
      </div>
      <div class="mt-8 flex items-center gap-3">
        <div class="flex text-amber-500" aria-hidden="true">
          <svg class="size-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 14.9l-5.2 2.7 1-5.8L1.5 7.7l5.9-.9z"/></svg>
          <svg class="size-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 14.9l-5.2 2.7 1-5.8L1.5 7.7l5.9-.9z"/></svg>
          <svg class="size-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 14.9l-5.2 2.7 1-5.8L1.5 7.7l5.9-.9z"/></svg>
          <svg class="size-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 14.9l-5.2 2.7 1-5.8L1.5 7.7l5.9-.9z"/></svg>
          <svg class="size-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 14.9l-5.2 2.7 1-5.8L1.5 7.7l5.9-.9z"/></svg>
        </div>
        <p class="text-sm text-slate-600"><span class="font-semibold text-slate-900">4.9 out of 5</span> from 2,140 traveller reviews</p>
      </div>
    </div>
    <div class="relative">
      <img src="fjord.webp" width="800" height="600" fetchpriority="high" alt="A deep blue fjord between steep rock walls, seen from a pale granite ledge high above the water" class="aspect-[4/3] h-auto w-full rounded-3xl object-cover shadow-xl shadow-sky-950/15">
      <div class="absolute bottom-4 left-4 rounded-2xl bg-white/95 px-4 py-3 shadow-lg">
        <p class="text-xs font-medium text-slate-600">Next departure</p>
        <p class="text-sm font-semibold text-sky-950">Saturday, 7:30 am &middot; 4 seats left</p>
      </div>
    </div>
  </div>
</div>

Full-bleed photo with a solid overlay

  • relative isolate
  • absolute inset-0 -z-10
  • bg-stone-950/60
  • object-cover

The section is relative isolate, so the photo and the overlay can sit on -z-10 behind the text without slipping under the page. The overlay is a flat bg-stone-950/60 layer (the v4 slash syntax that replaced bg-opacity-*), which keeps white text above 4.5:1 even over the bright sky. The photo is decorative here, so it has empty alt text, and min-h-[36rem] gives the section a fixed floor instead of min-h-screen, which would be the whole viewport.

View code
<div class="relative isolate flex min-h-[36rem] w-full flex-col overflow-hidden text-white md:min-h-[38rem]">
  <img src="highland-road.webp" width="800" height="600" alt="" fetchpriority="high" class="absolute inset-0 -z-10 size-full object-cover">
  <div class="absolute inset-0 -z-10 bg-stone-950/60"></div>
  <header class="mx-auto flex w-full max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white">
      <svg class="size-7" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 22c5-1 7-9 12-9s5 5 12 4"/><path d="M8 25c3-4 6-5 9-4"/></svg>
      Ridgeway Walks
    </a>
    <a href="#" class="rounded-lg px-3 py-2 text-sm font-semibold ring-1 ring-white/50 ring-inset hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Trip finder</a>
  </header>
  <div class="mx-auto mt-auto w-full max-w-6xl px-5 pt-16 pb-14 md:px-8 md:pb-20">
    <h1 class="max-w-3xl text-4xl font-bold tracking-tight text-balance md:text-6xl">Walk the ridge before the crowds wake up</h1>
    <p class="mt-5 max-w-xl text-lg/8 text-stone-100">Five-day guided walks across the northern ridges, with luggage carried between inns and the first trail start at sunrise.</p>
    <div class="mt-8 flex flex-col gap-3 sm:flex-row">
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-white px-6 text-base font-semibold text-stone-900 hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">See spring dates</a>
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-6 text-base font-semibold text-white ring-1 ring-white/60 ring-inset hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Download the route PDF</a>
    </div>
  </div>
</div>

Editorial magazine hero

  • text-[clamp(2.6rem,8.5vw,6rem)]
  • font-serif
  • leading-[0.95]
  • md:grid-cols-[1.25fr_1fr]

The headline size is one arbitrary value, text-[clamp(2.6rem,8.5vw,6rem)], so it grows with the viewport between a floor and a ceiling without any breakpoint, and text-balance keeps the giant lines even. font-serif uses the default serif stack, leading-[0.95] tightens the display type, and border-y rules plus a two-column grid from md: give it the cover look. The photo is a figure with a figcaption credit and descriptive alt text.

View code
<div class="w-full bg-[#f4efe6] text-stone-900">
  <div class="mx-auto max-w-6xl px-5 pt-5 pb-14 md:px-8 md:pb-20">
    <header class="flex items-baseline justify-between gap-4 border-b-2 border-stone-900 pb-3">
      <a href="#" class="rounded-sm font-serif text-2xl font-bold tracking-tight focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-red-800">The Field Review</a>
      <p class="text-xs font-medium text-stone-700 sm:text-sm">Issue 47 &middot; Autumn 2026</p>
    </header>
    <h1 class="mt-8 font-serif text-[clamp(2.6rem,8.5vw,6rem)] leading-[0.95] font-semibold tracking-tight text-balance">The alley that outlived the city plan</h1>
    <div class="mt-10 grid gap-8 border-t border-stone-900/20 pt-8 md:grid-cols-[1.25fr_1fr] md:gap-10">
      <figure>
        <img src="brick-alley.webp" width="800" height="600" alt="A narrow alley between red brick walls with fire escapes and office towers at the far end" class="aspect-[4/3] h-auto w-full object-cover">
        <figcaption class="mt-2 text-xs text-stone-600">Photographed at 6 am, before the delivery vans arrive.</figcaption>
      </figure>
      <div class="flex flex-col">
        <p class="font-serif text-xl/8 text-stone-800 md:text-2xl/9">Three planners, two fires and one stubborn bakery explain why a 1890s service lane still carries more foot traffic than the boulevard beside it.</p>
        <p class="mt-6 text-sm text-stone-700">By <span class="font-semibold text-stone-900">Marta Ellison</span> &middot; 14 min read</p>
        <a href="#" class="group mt-8 inline-flex min-h-12 items-center gap-2 self-start border-b-2 border-red-800 text-base font-semibold text-red-800 hover:text-red-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-red-800">Read the story <span aria-hidden="true" class="motion-safe:transition-transform group-hover:translate-x-1">&rarr;</span></a>
      </div>
    </div>
  </div>
</div>

SaaS and product heroes

Heroes that sell software and products: an app screenshot drawn in HTML and SVG, an announcement pill, a stats row, a cloud of customer wordmarks and an app download block.

SaaS hero with an app screenshot

  • role="img"
  • md:grid-cols-[11rem_1fr]
  • preserveAspectRatio
  • tabular-nums

The product shot is HTML and inline SVG instead of a PNG, so it stays sharp at any size and costs no image request. The figure carries role="img" with an aria-label that describes the dashboard, which hides the fake interface from screen readers. The sidebar only appears from md:, the KPI row stays three columns with tabular-nums, and the chart SVG stretches with preserveAspectRatio="none".

View code
<div class="w-full overflow-hidden bg-[#f3f6f5]">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-emerald-700">
      <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="7" fill="#065f46"/><path d="M8 19h12M8 14h8M8 9h12" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
      Ledgerline
    </a>
    <a href="#" class="rounded-lg bg-slate-900 px-3.5 py-2 text-sm font-semibold text-white hover:bg-slate-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-900">Book a demo</a>
  </header>
  <div class="mx-auto max-w-3xl px-5 pt-12 text-center md:pt-16">
    <h1 class="text-4xl font-bold tracking-tight text-balance text-slate-900 md:text-5xl">Close the books in two days, not ten</h1>
    <p class="mx-auto mt-5 max-w-xl text-lg/8 text-slate-600">Ledgerline matches bank lines, chases missing receipts and flags odd spend before month end, so finance teams review instead of reconcile.</p>
    <div class="mt-8 flex flex-col justify-center gap-3 sm:flex-row">
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-emerald-700 px-6 text-base font-semibold text-white hover:bg-emerald-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-700">Start a 14-day trial</a>
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-white px-6 text-base font-semibold text-slate-900 ring-1 ring-slate-300 ring-inset hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-700">Watch the 3-minute tour</a>
    </div>
  </div>
  <div class="mx-auto mt-12 max-w-5xl px-4 md:mt-14 md:px-8">
    <figure role="img" aria-label="Ledgerline dashboard: 1,284 of 1,302 bank lines matched, 18 receipts missing, 9 days to close, and a monthly spend chart" class="overflow-hidden rounded-t-xl border border-b-0 border-slate-200 bg-white text-left shadow-2xl shadow-slate-900/15">
      <div class="flex items-center gap-2 border-b border-slate-200 bg-slate-50 px-3 py-2.5">
        <span class="size-2.5 rounded-full bg-slate-300"></span><span class="size-2.5 rounded-full bg-slate-300"></span><span class="size-2.5 rounded-full bg-slate-300"></span>
        <span class="mx-auto truncate rounded-md bg-white px-3 py-0.5 text-[11px] text-slate-500 ring-1 ring-slate-200">app.ledgerline.example/close</span>
      </div>
      <div class="grid md:grid-cols-[11rem_1fr]">
        <div class="hidden border-r border-slate-200 p-3 text-[13px] md:block">
          <div class="rounded-md bg-emerald-50 px-2.5 py-1.5 font-semibold text-emerald-800">Month-end close</div>
          <div class="px-2.5 py-1.5 text-slate-600">Bank feeds</div>
          <div class="px-2.5 py-1.5 text-slate-600">Receipts</div>
          <div class="px-2.5 py-1.5 text-slate-600">Approvals</div>
          <div class="px-2.5 py-1.5 text-slate-600">Reports</div>
        </div>
        <div class="p-3 md:p-5">
          <div class="grid grid-cols-3 gap-2 md:gap-3">
            <div class="rounded-lg border border-slate-200 p-2.5 md:p-3"><div class="text-[11px] text-slate-500 md:text-xs">Matched</div><div class="mt-1 text-base font-semibold text-slate-900 tabular-nums md:text-xl">98.6%</div></div>
            <div class="rounded-lg border border-slate-200 p-2.5 md:p-3"><div class="text-[11px] text-slate-500 md:text-xs">Missing receipts</div><div class="mt-1 text-base font-semibold text-amber-700 tabular-nums md:text-xl">18</div></div>
            <div class="rounded-lg border border-slate-200 p-2.5 md:p-3"><div class="text-[11px] text-slate-500 md:text-xs">Days to close</div><div class="mt-1 text-base font-semibold text-slate-900 tabular-nums md:text-xl">9</div></div>
          </div>
          <div class="mt-3 rounded-lg border border-slate-200 p-3 md:mt-4">
            <div class="flex items-center justify-between text-xs text-slate-500"><span class="font-semibold text-slate-800">Spend by month</span><span>2026</span></div>
            <svg class="mt-3 h-28 w-full md:h-36" viewBox="0 0 300 100" preserveAspectRatio="none">
              <path d="M0 25h300M0 50h300M0 75h300" stroke="#e2e8f0" stroke-width="1" vector-effect="non-scaling-stroke"/>
              <g fill="#a7f3d0"><rect x="6" y="52" width="16" height="48"/><rect x="31" y="44" width="16" height="56"/><rect x="56" y="58" width="16" height="42"/><rect x="81" y="38" width="16" height="62"/><rect x="106" y="47" width="16" height="53"/><rect x="131" y="30" width="16" height="70"/><rect x="156" y="41" width="16" height="59"/><rect x="181" y="34" width="16" height="66"/><rect x="206" y="50" width="16" height="50"/></g>
              <rect x="231" y="22" width="16" height="78" fill="#047857"/>
              <g fill="#e2e8f0"><rect x="256" y="70" width="16" height="30"/><rect x="281" y="76" width="16" height="24"/></g>
            </svg>
          </div>
          <div class="mt-3 space-y-1.5 text-[12px] md:mt-4 md:text-[13px]">
            <div class="flex items-center justify-between gap-2 rounded-md bg-slate-50 px-2.5 py-2"><span class="truncate text-slate-700">Harbor Cafe Supplies</span><span class="shrink-0 rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-medium text-emerald-800">Matched</span></div>
            <div class="flex items-center justify-between gap-2 rounded-md bg-slate-50 px-2.5 py-2"><span class="truncate text-slate-700">Cloud hosting, October</span><span class="shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium text-amber-800">Receipt due</span></div>
          </div>
        </div>
      </div>
    </figure>
  </div>
</div>

Hero with an announcement pill

  • rounded-full ring-1 ring-inset
  • group-hover:translate-x-0.5
  • motion-safe:transition-transform
  • text-pretty

The pill is a single link: a solid New badge, the announcement and an arrow that slides with group-hover:translate-x-0.5 inside motion-safe:. ring-1 ring-inset draws the outline without adding to the box size, and the link has its own focus-visible ring, because it is the first focusable item in the hero. The rest is a centered stack with text-balance on the headline and text-pretty on the paragraph.

View code
<div class="w-full bg-[#f7f5ff]">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-violet-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-700">
      <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><path d="M14 2 25 8.5v11L14 26 3 19.5v-11Z" fill="#6d28d9"/><path d="M14 2v24M3 8.5l22 11M25 8.5l-22 11" stroke="#ede9fe" stroke-width="1.4"/></svg>
      Tessellate
    </a>
    <a href="#" class="rounded-lg px-3 py-2 text-sm font-semibold text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-violet-700">Log in</a>
  </header>
  <div class="mx-auto max-w-3xl px-5 pt-12 pb-20 text-center md:pt-16 md:pb-24">
    <a href="#" class="group inline-flex max-w-full items-center gap-2 rounded-full bg-white py-1 pr-3 pl-1 text-sm text-violet-950 ring-1 ring-violet-200 ring-inset hover:ring-violet-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">
      <span class="shrink-0 rounded-full bg-violet-700 px-2.5 py-0.5 text-xs font-semibold text-white">New</span>
      <span class="truncate">Version 3 brings shared libraries</span>
      <svg class="size-4 shrink-0 text-violet-700 motion-safe:transition-transform group-hover:translate-x-0.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M7.2 14.8a.75.75 0 0 1 0-1.06L10.94 10 7.2 6.26a.75.75 0 1 1 1.06-1.06l4.27 4.27a.75.75 0 0 1 0 1.06l-4.27 4.27a.75.75 0 0 1-1.06 0Z" clip-rule="evenodd"/></svg>
    </a>
    <h1 class="mt-8 text-4xl font-bold tracking-tight text-balance text-violet-950 md:text-6xl">One component library for every file your team opens</h1>
    <p class="mx-auto mt-6 max-w-xl text-lg/8 text-pretty text-slate-600">Publish buttons, cards and icons once, and every design file picks up the change with a review step before anything ships.</p>
    <div class="mt-10 flex flex-col justify-center gap-3 sm:flex-row">
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-xl bg-violet-700 px-6 text-base font-semibold text-white hover:bg-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Try Tessellate free</a>
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-xl px-6 text-base font-semibold text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Read the release notes</a>
    </div>
  </div>
</div>

Hero with a stats row

  • <dl>
  • flex-col-reverse
  • grid-cols-2 md:grid-cols-4
  • border-white/15

The numbers are a description list: each dt holds the label and each dd the figure, and flex-col-reverse shows the big figure first without changing the reading order. The grid runs two columns on phones and four from md:, with border-t border-white/15 rules that read as a table on dark teal. Every text color on the #0f3b3a background stays above 4.5:1.

View code
<div class="w-full bg-[#0f3b3a] text-white">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-teal-200">
      <svg class="size-7 text-teal-200" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M3 17h22l-3 6H6Z"/><path d="M8 17V9h7v8M15 12h5v5"/></svg>
      Harborline Freight
    </a>
    <a href="#" class="hidden rounded-lg px-3 py-2 text-sm font-semibold text-teal-50 hover:bg-white/10 sm:inline-flex focus-visible:outline-2 focus-visible:outline-teal-200">Track a shipment</a>
  </header>
  <div class="mx-auto max-w-6xl px-5 pt-12 pb-16 md:px-8 md:pt-16 md:pb-20">
    <div class="max-w-3xl">
      <h1 class="text-4xl font-bold tracking-tight text-balance md:text-6xl">Freight that lands when the plan says it will</h1>
      <p class="mt-6 max-w-2xl text-lg/8 text-teal-50/85">Short-sea and rail freight across the North Sea and Baltic, with one booking, one invoice and a live position for every box.</p>
      <div class="mt-8 flex flex-col gap-3 sm:flex-row">
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-[#f5c451] px-6 text-base font-semibold text-[#1f2a1f] hover:bg-[#f8d27a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#f5c451]">Get a quote in 2 minutes</a>
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-6 text-base font-semibold text-white ring-1 ring-white/40 ring-inset hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-200">See our routes</a>
      </div>
    </div>
    <dl class="mt-14 grid grid-cols-2 gap-x-6 gap-y-8 md:mt-16 md:grid-cols-4">
      <div class="flex flex-col-reverse gap-1 border-t border-white/15 pt-5"><dt class="text-sm text-teal-50/80">On-time arrivals in 2026</dt><dd class="text-3xl font-semibold tracking-tight tabular-nums md:text-4xl">98.7%</dd></div>
      <div class="flex flex-col-reverse gap-1 border-t border-white/15 pt-5"><dt class="text-sm text-teal-50/80">Ports and rail hubs</dt><dd class="text-3xl font-semibold tracking-tight tabular-nums md:text-4xl">41</dd></div>
      <div class="flex flex-col-reverse gap-1 border-t border-white/15 pt-5"><dt class="text-sm text-teal-50/80">Containers a month</dt><dd class="text-3xl font-semibold tracking-tight tabular-nums md:text-4xl">12,400</dd></div>
      <div class="flex flex-col-reverse gap-1 border-t border-white/15 pt-5"><dt class="text-sm text-teal-50/80">Dispatch desk</dt><dd class="text-3xl font-semibold tracking-tight tabular-nums md:text-4xl">24/7</dd></div>
    </dl>
  </div>
</div>

Hero with a customer logo cloud

  • flex-wrap justify-center
  • font-serif italic
  • tracking-[0.3em]
  • font-mono lowercase

The customer wordmarks are plain text styled with font, case and tracking utilities, so the row needs no image files and every name is real text for screen readers. They sit in a list with an aria-label, wrap with flex-wrap justify-center and use slate-600 to stay readable while looking quieter than the headline. Swap them for SVG logos with alt text once you have permission to show them.

View code
<div class="w-full bg-white">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-rose-700">
      <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><circle cx="14" cy="14" r="13" fill="#be123c"/><path d="M9 15.5 12.5 19 19.5 9" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
      Fieldnote
    </a>
    <a href="#" class="rounded-lg px-3 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-rose-700">Sign in</a>
  </header>
  <div class="mx-auto max-w-5xl px-5 pt-12 pb-16 text-center md:pt-16 md:pb-20">
    <h1 class="mx-auto max-w-3xl text-4xl font-bold tracking-tight text-balance text-slate-900 md:text-6xl">The CRM your field team fills in without being asked</h1>
    <p class="mx-auto mt-6 max-w-xl text-lg/8 text-slate-600">Voice notes become visit reports, photos attach to the right account, and the pipeline updates before the van leaves the car park.</p>
    <div class="mt-9 flex flex-col justify-center gap-3 sm:flex-row">
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-rose-700 px-6 text-base font-semibold text-white hover:bg-rose-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-700">Start free for 30 days</a>
      <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-6 text-base font-semibold text-slate-900 ring-1 ring-slate-300 ring-inset hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-700">Talk to sales</a>
    </div>
    <div class="mt-16 border-t border-slate-200 pt-10">
      <p class="text-sm font-medium text-slate-700">Used every day by 3,000 field teams, including</p>
      <ul aria-label="Customers" class="mt-6 flex flex-wrap items-center justify-center gap-x-10 gap-y-5 text-slate-600">
        <li class="font-serif text-2xl italic">Halcyon</li>
        <li class="text-sm font-bold tracking-[0.3em] uppercase">Brisk&amp;Co</li>
        <li class="font-mono text-xl lowercase">orbitly</li>
        <li class="text-xl font-black tracking-tight">MARLOW<span class="font-light">supply</span></li>
        <li class="font-serif text-xl font-semibold">Pinecrest</li>
        <li class="text-lg font-semibold tracking-wide"><span class="mr-1 inline-block size-3 rotate-45 bg-slate-600 align-middle" aria-hidden="true"></span>Vantage Row</li>
      </ul>
    </div>
  </div>
</div>

App download hero with store buttons

  • rounded-[2.5rem] border-[10px]
  • aspect-[9/19]
  • md:grid-cols-[1fr_auto]
  • aria-label

The phone is a div with rounded-[2.5rem], a thick border and aspect-[9/19], holding a small HTML screen, so the mockup scales with the layout. The store buttons are generic: a device icon plus two lines of text, with an aria-label that reads the full action instead of the split lines, because store badges are trademarks with their own artwork rules. On phones the mockup moves under the copy and the buttons stack.

View code
<div class="w-full overflow-hidden bg-[#e7f3f0]">
  <div class="mx-auto grid max-w-6xl items-center gap-12 px-5 py-14 md:grid-cols-[1fr_auto] md:gap-16 md:px-8 md:py-20">
    <div>
      <a href="#" class="inline-flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-teal-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-teal-800">
        <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="8" fill="#115e59"/><path d="M5 16c2.5-2 4.5-2 7 0s4.5 2 7 0 3-1.5 4-1M5 21c2.5-2 4.5-2 7 0s4.5 2 7 0 3-1.5 4-1" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/><circle cx="17" cy="8.5" r="2.5" fill="#99f6e4"/></svg>
        Tidepool
      </a>
      <h1 class="mt-8 max-w-xl text-4xl font-bold tracking-tight text-balance text-teal-950 md:text-5xl">Count laps, not tiles</h1>
      <p class="mt-5 max-w-lg text-lg/8 text-teal-900">Tidepool logs every length, stroke and rest from your watch, then builds next week&rsquo;s sets around the ones you skipped.</p>
      <div class="mt-8 flex flex-col gap-3 sm:flex-row">
        <a href="#" aria-label="Download Tidepool for iPhone" class="inline-flex min-h-14 items-center gap-3 rounded-xl bg-slate-950 px-5 text-white hover:bg-slate-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-950">
          <svg class="size-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="6" y="2.5" width="12" height="19" rx="3"/><path d="M10.5 18.5h3" stroke-linecap="round"/></svg>
          <span class="text-left leading-tight"><span class="block text-[11px] text-slate-300">Download for</span><span class="block text-lg font-semibold">iPhone</span></span>
        </a>
        <a href="#" aria-label="Download Tidepool for Android" class="inline-flex min-h-14 items-center gap-3 rounded-xl bg-slate-950 px-5 text-white hover:bg-slate-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-950">
          <svg class="size-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M12 3v11m0 0-4-4m4 4 4-4M5 17v2.5A1.5 1.5 0 0 0 6.5 21h11a1.5 1.5 0 0 0 1.5-1.5V17" stroke-linecap="round" stroke-linejoin="round"/></svg>
          <span class="text-left leading-tight"><span class="block text-[11px] text-slate-300">Get it for</span><span class="block text-lg font-semibold">Android</span></span>
        </a>
      </div>
      <p class="mt-5 text-sm text-teal-900">Works with most swim watches. Free for 30 days, then $4.99 a month.</p>
    </div>
    <div class="mx-auto w-60 md:w-64" aria-hidden="true">
      <div class="aspect-[9/19] overflow-hidden rounded-[2.5rem] border-[10px] border-slate-950 bg-white shadow-2xl shadow-teal-950/25">
        <div class="flex h-full flex-col bg-[#f4fbf9] px-4 pt-6 pb-4">
          <div class="mx-auto h-1.5 w-16 rounded-full bg-slate-200"></div>
          <div class="mt-5 text-xs font-medium text-teal-800">Tuesday swim</div>
          <div class="text-3xl font-bold tracking-tight text-teal-950 tabular-nums">1,850 m</div>
          <div class="mt-4 grid grid-cols-3 gap-1.5 text-center">
            <div class="rounded-lg bg-white p-1.5 shadow-xs"><div class="text-sm font-semibold text-teal-950">74</div><div class="text-[10px] text-slate-500">lengths</div></div>
            <div class="rounded-lg bg-white p-1.5 shadow-xs"><div class="text-sm font-semibold text-teal-950">1:52</div><div class="text-[10px] text-slate-500">per 100</div></div>
            <div class="rounded-lg bg-white p-1.5 shadow-xs"><div class="text-sm font-semibold text-teal-950">41</div><div class="text-[10px] text-slate-500">min</div></div>
          </div>
          <div class="mt-4 flex h-24 items-end gap-1">
            <div class="h-[55%] flex-1 rounded-t bg-teal-200"></div><div class="h-[70%] flex-1 rounded-t bg-teal-200"></div><div class="h-[62%] flex-1 rounded-t bg-teal-200"></div><div class="h-[85%] flex-1 rounded-t bg-teal-600"></div><div class="h-[48%] flex-1 rounded-t bg-teal-200"></div><div class="h-[76%] flex-1 rounded-t bg-teal-200"></div><div class="h-[66%] flex-1 rounded-t bg-teal-200"></div>
          </div>
          <div class="mt-4 space-y-1.5 text-[11px]">
            <div class="flex justify-between rounded-md bg-white px-2 py-1.5"><span class="text-slate-600">Freestyle</span><span class="font-semibold text-teal-950">1,200 m</span></div>
            <div class="flex justify-between rounded-md bg-white px-2 py-1.5"><span class="text-slate-600">Backstroke</span><span class="font-semibold text-teal-950">400 m</span></div>
            <div class="flex justify-between rounded-md bg-white px-2 py-1.5"><span class="text-slate-600">Kick sets</span><span class="font-semibold text-teal-950">250 m</span></div>
          </div>
          <div class="mt-auto rounded-full bg-teal-800 py-2 text-center text-xs font-semibold text-white">Plan next week</div>
        </div>
      </div>
    </div>
  </div>
</div>

Heroes with a job to do

Heroes that do more than link somewhere: an email capture form with validation, a video poster that opens a dialog, a launch countdown and an entrance animation that respects reduced motion.

Email capture hero with validation

  • aria-invalid:
  • empty:hidden
  • role="status"
  • novalidate

Submit the form empty, then with a real-looking address.

The form uses novalidate so the script can show its own message, but it still asks the browser with checkValidity() on the type="email" required field. A failed check sets aria-invalid, which turns on the aria-invalid: red border, fills the error text linked through aria-describedby and moves focus back to the field. On success the form hides and a role="status" paragraph, kept invisible with empty:hidden until it has text, confirms the address.

View code
<div class="w-full bg-[#fdf6ec]">
  <div class="mx-auto max-w-3xl px-5 py-16 text-center md:py-24">
    <a href="#" class="inline-flex items-center gap-2 rounded-md font-serif text-xl font-semibold text-[#5c2410] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#7c2d12]">
      <svg class="size-7" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M5 17c0-6 4-10 9-10s9 4 9 10c0 3-2 4-9 4s-9-1-9-4Z"/><path d="M10 12l2 3M15 11l1.5 3.5M19 13l1 2"/></svg>
      Slow Dough Letter
    </a>
    <h1 class="mt-8 text-4xl font-bold tracking-tight text-balance text-[#3b1a0c] md:text-6xl">One bread recipe a week, tested in a home oven</h1>
    <p class="mx-auto mt-6 max-w-xl text-lg/8 text-[#6b3a22]">Every Friday: one loaf, the timings that worked, and the mistake that ruined the first two attempts. 18,000 home bakers read it.</p>
    <form class="thr-signup mx-auto mt-10 max-w-lg text-left" novalidate data-error="thr-email-error" data-done="thr-email-done">
      <label for="thr-email" class="block text-sm font-semibold text-[#3b1a0c]">Email address</label>
      <div class="mt-2 flex flex-col gap-3 sm:flex-row">
        <input id="thr-email" name="email" type="email" required autocomplete="email" placeholder="you@example.com" aria-describedby="thr-email-hint thr-email-error" class="min-h-12 w-full min-w-0 flex-1 rounded-lg border border-[#d6bfa6] bg-white px-4 text-base text-[#3b1a0c] placeholder:text-[#8a6a55] focus:border-[#7c2d12] focus:outline-2 focus:outline-offset-0 focus:outline-[#7c2d12]/30 aria-invalid:border-red-700 aria-invalid:focus:outline-red-700/30">
        <button type="submit" class="min-h-12 shrink-0 rounded-lg bg-[#7c2d12] px-6 text-base font-semibold text-white hover:bg-[#5c2410] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c2d12]">Send me Friday&rsquo;s loaf</button>
      </div>
      <p id="thr-email-hint" class="mt-2 text-sm text-[#6b3a22]">No spam, one email a week, unsubscribe in one click.</p>
      <p id="thr-email-error" class="mt-1 text-sm font-medium text-red-700"></p>
    </form>
    <p id="thr-email-done" role="status" class="mx-auto mt-10 flex max-w-lg items-center gap-3 rounded-xl bg-white px-5 py-4 text-left text-base text-[#3b1a0c] shadow-sm ring-1 ring-[#e8d6c2] empty:hidden"></p>
  </div>
</div>

Video-style hero with a dialog and transcript

  • <dialog>
  • showModal()
  • backdrop:bg-stone-950/70
  • aria-haspopup="dialog"

Click the play button.

The hero shows a still with a play button, and the button opens a native dialog with showModal(), which handles the top layer, Escape and returning focus. backdrop:bg-stone-950/70 dims the page, and the dialog holds a transcript, which a real video needs anyway. Loading the player only after the click keeps a heavy embed off the critical path, so the still photo can be the LCP element.

View code
<div class="w-full bg-stone-100">
  <div class="mx-auto grid max-w-6xl items-center gap-10 px-5 py-14 md:grid-cols-2 md:gap-12 md:px-8 md:py-20">
    <div>
      <a href="#" class="inline-flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight text-stone-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-orange-800">
        <svg class="size-7 text-orange-800" viewBox="0 0 28 28" fill="currentColor" aria-hidden="true"><path d="M14 3 26 24H2Z"/><path d="M14 11l5 9H9Z" fill="#f5f5f4"/></svg>
        Cairnline
      </a>
      <h1 class="mt-8 text-4xl font-bold tracking-tight text-balance text-stone-900 md:text-5xl">A pack that fits after six hours, not just in the shop</h1>
      <p class="mt-5 max-w-lg text-lg/8 text-stone-700">Watch three guides test the Ridge 38 on a two-day crossing, then fit yours at home with our free 60-day return.</p>
      <div class="mt-8 flex flex-col gap-3 sm:flex-row">
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-orange-800 px-6 text-base font-semibold text-white hover:bg-orange-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-800">Shop the Ridge 38</a>
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-6 text-base font-semibold text-stone-900 ring-1 ring-stone-400 ring-inset hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-800">Find your size</a>
      </div>
    </div>
    <div class="relative overflow-hidden rounded-3xl shadow-xl shadow-stone-900/20">
      <img src="mountain-hiker.webp" width="800" height="600" alt="A hiker with a large backpack stands on a rocky ledge above hazy mountain valleys" class="aspect-[4/3] h-auto w-full object-cover">
      <button type="button" class="thr-play group absolute inset-0 flex items-center justify-center focus-visible:outline-none" aria-haspopup="dialog" aria-controls="thr-film">
        <span class="flex size-18 items-center justify-center rounded-full bg-white/95 text-orange-800 shadow-lg ring-orange-800 motion-safe:transition-transform group-hover:scale-105 group-focus-visible:ring-4">
          <svg class="ml-1 size-8" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7 4.5v15a1 1 0 0 0 1.5.86l12.5-7.5a1 1 0 0 0 0-1.72L8.5 3.64A1 1 0 0 0 7 4.5Z"/></svg>
        </span>
        <span class="sr-only">Play the film: Two days on the ridge, 4 minutes 12 seconds</span>
      </button>
      <span class="pointer-events-none absolute bottom-4 left-4 rounded-full bg-stone-950/75 px-3 py-1 text-sm font-medium text-white" aria-hidden="true">Two days on the ridge &middot; 4:12</span>
    </div>
  </div>
  <dialog id="thr-film" aria-labelledby="thr-film-title" class="m-auto w-[min(40rem,calc(100%-2rem))] rounded-2xl bg-white p-0 text-stone-800 shadow-2xl backdrop:bg-stone-950/70">
    <div class="flex items-start justify-between gap-4 border-b border-stone-200 px-5 py-4">
      <div>
        <h2 id="thr-film-title" class="text-lg font-semibold text-stone-900">Two days on the ridge</h2>
        <p class="text-sm text-stone-600">4:12 &middot; Transcript</p>
      </div>
      <form method="dialog"><button class="inline-flex size-11 items-center justify-center rounded-lg text-stone-700 hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-orange-800"><span class="sr-only">Close</span><svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg></button></form>
    </div>
    <div class="max-h-[min(75vh,32rem)] space-y-4 overflow-y-auto px-5 py-5 text-base/7">
      <div class="flex aspect-[16/6] items-center justify-center rounded-xl bg-stone-900 px-4 text-center text-sm text-stone-300">The video player loads here after the click.</div>
      <p><span class="font-semibold text-stone-900">0:00</span> Dawn at the hut. Ines packs the Ridge 38 with eleven kilos: tent, stove, two days of food.</p>
      <p><span class="font-semibold text-stone-900">1:05</span> Four hours in, the hip belt carries most of the weight. She loosens the load lifters on the descent.</p>
      <p><span class="font-semibold text-stone-900">2:40</span> Rain. The lid pocket stays dry, the side pockets drain through two mesh ports.</p>
      <p><span class="font-semibold text-stone-900">3:50</span> Day two ends at the road. Nobody asks to swap packs.</p>
    </div>
  </dialog>
</div>

Product launch hero with a countdown

  • tabular-nums
  • role="timer"
  • aria-pressed:
  • grid-cols-4

Click Remind me.

The countdown reads a fixed launch time from a time element and refreshes itself with a chain of setTimeout calls that aim at the start of each second, so it never drifts and stops once the date passes. tabular-nums keeps the digits from jumping, the timer has aria-live off so screen readers are not interrupted every second, and the time element states the date in words. The reminder button is an aria-pressed toggle styled with aria-pressed: utilities.

View code
<div class="w-full bg-[#efebe4] text-stone-900">
  <div class="mx-auto grid max-w-6xl items-center gap-12 px-5 py-14 md:grid-cols-[1.1fr_1fr] md:px-8 md:py-20">
    <div>
      <a href="#" class="inline-flex items-center gap-2 rounded-md text-lg font-semibold tracking-tight focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-orange-700">
        <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><rect x="2" y="7" width="24" height="14" rx="3" fill="#1c1917"/><path d="M6 11h2M10 11h2M14 11h2M18 11h4M6 15h4M12 15h10" stroke="#f97316" stroke-width="1.6" stroke-linecap="round"/></svg>
        Halden Keys
      </a>
      <h1 class="mt-8 text-4xl font-bold tracking-tight text-balance md:text-6xl">The Halden Mk II goes on sale soon</h1>
      <p class="mt-5 max-w-lg text-lg/8 text-stone-700">A 75% aluminium keyboard with hot-swap switches, a volume knob and a battery that lasts a semester. First batch: 800 units.</p>
      <p class="mt-6 text-sm font-medium text-stone-700">Sales open <time id="thr-launch" datetime="2026-12-03T17:00:00Z">December 3, 2026 at 17:00 UTC</time></p>
      <div role="timer" aria-live="off" aria-label="Time until sales open" class="mt-3 grid max-w-md grid-cols-4 gap-2 text-center">
        <div class="rounded-xl bg-stone-900 py-3 text-white"><span id="thr-cd-d" class="block text-3xl font-bold tabular-nums md:text-4xl">--</span><span class="text-xs text-stone-300">days</span></div>
        <div class="rounded-xl bg-stone-900 py-3 text-white"><span id="thr-cd-h" class="block text-3xl font-bold tabular-nums md:text-4xl">--</span><span class="text-xs text-stone-300">hours</span></div>
        <div class="rounded-xl bg-stone-900 py-3 text-white"><span id="thr-cd-m" class="block text-3xl font-bold tabular-nums md:text-4xl">--</span><span class="text-xs text-stone-300">min</span></div>
        <div class="rounded-xl bg-stone-900 py-3 text-white"><span id="thr-cd-s" class="block text-3xl font-bold tabular-nums md:text-4xl">--</span><span class="text-xs text-stone-300">sec</span></div>
      </div>
      <p id="thr-cd-live" class="mt-3 hidden text-lg font-semibold text-orange-800">Sales are open now.</p>
      <div class="mt-8 flex flex-col gap-3 sm:flex-row">
        <button type="button" aria-pressed="false" class="thr-remind group inline-flex min-h-12 items-center justify-center gap-2 rounded-lg bg-orange-700 px-6 text-base font-semibold text-white hover:bg-orange-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-stone-900 aria-pressed:hover:bg-stone-800">
          <svg class="size-5 group-aria-pressed:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M14.9 17.1a23.8 23.8 0 0 0 5.5-1.3A8.97 8.97 0 0 1 18 9.75V9a6 6 0 0 0-12 0v.75a8.97 8.97 0 0 1-2.3 6.02c1.7.64 3.6 1.08 5.5 1.31m5.7 0a24.3 24.3 0 0 1-5.7 0m5.7 0a3 3 0 1 1-5.7 0"/></svg>
          <svg class="hidden size-5 group-aria-pressed:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m5 12.5 4.5 4.5L19 7.5"/></svg>
          <span class="thr-remind-label">Remind me</span>
        </button>
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-6 text-base font-semibold text-stone-900 ring-1 ring-stone-400 ring-inset hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">Full spec sheet</a>
      </div>
    </div>
    <div class="rounded-3xl bg-stone-300 p-3 shadow-xl shadow-stone-900/15 md:p-4" aria-hidden="true">
      <div class="grid grid-cols-10 gap-1 rounded-2xl bg-stone-800 p-2 md:gap-1.5 md:p-3">
        <div class="h-6 rounded bg-orange-500 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded-full bg-stone-500 md:h-8"></div>
        <div class="col-span-2 h-6 rounded bg-stone-400 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-400 md:h-8"></div>
        <div class="col-span-2 h-6 rounded bg-stone-400 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-100 md:h-8"></div><div class="col-span-2 h-6 rounded bg-orange-500 md:h-8"></div>
        <div class="h-6 rounded bg-stone-400 md:h-8"></div><div class="h-6 rounded bg-stone-400 md:h-8"></div><div class="col-span-6 h-6 rounded bg-stone-100 md:h-8"></div><div class="h-6 rounded bg-stone-400 md:h-8"></div><div class="h-6 rounded bg-stone-400 md:h-8"></div>
      </div>
    </div>
  </div>
</div>

Entrance animation with starting: and motion-safe:

  • motion-safe:starting:opacity-0
  • motion-safe:starting:translate-y-6
  • delay-150
  • transition-[opacity,translate]

Click Replay entrance.

Each line fades and rises in with no keyframes: starting:opacity-0 and starting:translate-y-6 compile to @starting-style, so the browser animates from those values when the element first renders. In v4 translate-y-* sets the translate property, which is why the transition is transition-[opacity,translate], and every motion class sits behind motion-safe: so reduced-motion visitors get the final layout at once. The headline only rises and never starts transparent, because it is the likely LCP element and a fade would delay that metric. delay-150 to delay-500 stagger the lines, and the Replay button hides and shows the block to run the entrance again.

View code
<div class="w-full bg-[#f3f5e7]">
  <div class="mx-auto max-w-5xl px-5 py-16 md:px-8 md:py-24">
    <div id="thr-entrance">
      <p class="text-base font-semibold text-[#3f6212] motion-safe:transition-[opacity,translate] motion-safe:duration-700 motion-safe:ease-out motion-safe:starting:translate-y-6 motion-safe:starting:opacity-0">Greenline Studio</p>
      <h1 class="mt-4 max-w-3xl text-4xl font-bold tracking-tight text-balance text-[#1f2a12] motion-safe:transition-[translate] motion-safe:delay-150 motion-safe:duration-700 motion-safe:ease-out motion-safe:starting:translate-y-6 md:text-6xl">Landscape design for gardens that look after themselves</h1>
      <p class="mt-6 max-w-xl text-lg/8 text-[#3a4a26] motion-safe:transition-[opacity,translate] motion-safe:delay-300 motion-safe:duration-700 motion-safe:ease-out motion-safe:starting:translate-y-6 motion-safe:starting:opacity-0">Native planting, rain gardens and paths that drain. We draw it, plant it and come back each season for the first two years.</p>
      <div class="mt-9 flex flex-col gap-3 motion-safe:transition-[opacity,translate] motion-safe:delay-500 motion-safe:duration-700 motion-safe:ease-out motion-safe:starting:translate-y-6 motion-safe:starting:opacity-0 sm:flex-row">
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-full bg-[#3f6212] px-6 text-base font-semibold text-white hover:bg-[#2f4a0e] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#3f6212]">Book a garden visit</a>
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-full px-6 text-base font-semibold text-[#1f2a12] ring-1 ring-[#3f6212]/40 ring-inset hover:bg-[#e6ebcf] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#3f6212]">See finished gardens</a>
      </div>
    </div>
    <button type="button" class="thr-replay mt-12 inline-flex min-h-11 items-center gap-2 rounded-lg px-3 text-sm font-semibold text-[#3f6212] hover:bg-[#e6ebcf] focus-visible:outline-2 focus-visible:outline-[#3f6212]" aria-controls="thr-entrance">
      <svg class="size-4" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M3.5 10a6.5 6.5 0 1 0 2-4.7M3.5 3.5v3h3"/></svg>
      Replay entrance
    </button>
  </div>
</div>

Dark mode, container queries and brand tokens

Three ways to adapt one hero: a dark theme switch built on the dark variant, a layout that answers to its container instead of the screen and a palette defined once in @theme.

Dark hero with a theme switch

  • dark:bg-slate-950
  • dark:text-white
  • aria-pressed
  • @custom-variant dark

Click the theme button to switch to light.

Every color comes in pairs such as bg-white dark:bg-slate-950 and text-slate-600 dark:text-slate-400. With the @custom-variant dark line in the stylesheet, dark: matches anything inside an element with the dark class, so the switch only toggles that class on the hero wrapper and updates aria-pressed. The icons swap with dark:hidden and hidden dark:block, and the code block keeps its own dark surface in both themes.

View code
<div id="thr-dark-root" class="dark w-full">
  <div class="bg-white text-slate-900 dark:bg-slate-950 dark:text-white">
    <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
      <a href="#" class="flex items-center gap-2 rounded-md font-mono text-lg font-semibold tracking-tight focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-sky-500">
        <svg class="size-7" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="7" class="fill-slate-900 dark:fill-sky-400"/><path d="m8 10 4 4-4 4M14 18h6" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="stroke-white dark:stroke-slate-950"/></svg>
        kernelspace
      </a>
      <button type="button" class="thr-theme-btn inline-flex min-h-11 items-center gap-2 rounded-lg px-3 text-sm font-semibold text-slate-700 ring-1 ring-slate-300 ring-inset hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-sky-600 dark:text-slate-200 dark:ring-slate-700 dark:hover:bg-slate-800 dark:focus-visible:outline-sky-400" aria-pressed="true" aria-controls="thr-dark-root">
        <svg class="size-5 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path stroke-linecap="round" d="M12 2.5v2M12 19.5v2M2.5 12h2M19.5 12h2M5.3 5.3l1.4 1.4M17.3 17.3l1.4 1.4M5.3 18.7l1.4-1.4M17.3 6.7l1.4-1.4"/></svg>
        <svg class="hidden size-5 dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M20.5 14.5A8.5 8.5 0 0 1 9.5 3.5a8.5 8.5 0 1 0 11 11Z"/></svg>
        Dark mode
      </button>
    </header>
    <div class="mx-auto grid max-w-6xl items-center gap-10 px-5 pt-10 pb-16 md:grid-cols-[1.1fr_1fr] md:px-8 md:pt-14 md:pb-24">
      <div>
        <h1 class="text-4xl font-bold tracking-tight text-balance md:text-5xl">Preview environments for every pull request</h1>
        <p class="mt-5 max-w-lg text-lg/8 text-slate-600 dark:text-slate-400">kernelspace spins up a full copy of your stack for each branch, seeds it with safe data and tears it down when the PR merges.</p>
        <div class="mt-8 flex flex-col gap-3 sm:flex-row">
          <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-sky-700 px-6 text-base font-semibold text-white hover:bg-sky-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700 dark:bg-sky-400 dark:text-slate-950 dark:hover:bg-sky-300 dark:focus-visible:outline-sky-400">Connect a repository</a>
          <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-6 text-base font-semibold text-slate-800 ring-1 ring-slate-300 ring-inset hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700 dark:text-slate-100 dark:ring-slate-700 dark:hover:bg-slate-800 dark:focus-visible:outline-sky-400">Read the docs</a>
        </div>
      </div>
      <div class="overflow-hidden rounded-2xl bg-slate-900 font-mono text-[13px] text-slate-300 shadow-xl ring-1 ring-slate-900/10 dark:ring-white/10">
        <div class="flex items-center gap-2 border-b border-white/10 px-4 py-2.5 text-xs text-slate-400"><span class="size-2.5 rounded-full bg-slate-600"></span><span class="size-2.5 rounded-full bg-slate-600"></span><span class="size-2.5 rounded-full bg-slate-600"></span><span class="ml-2">terminal</span></div>
        <div class="space-y-1.5 overflow-x-auto p-4 leading-relaxed whitespace-pre"><div><span class="text-sky-400">$</span> npx kernelspace up --branch fix/login</div><div class="text-slate-400">Building 4 services ...</div><div><span class="text-emerald-400">&#10003;</span> api        ready in 21s</div><div><span class="text-emerald-400">&#10003;</span> web        ready in 34s</div><div><span class="text-emerald-400">&#10003;</span> db seeded  1,200 rows</div><div class="text-slate-100">Preview: fix-login.preview.example</div></div>
      </div>
    </div>
  </div>
</div>

Container-query hero in a resizable box

  • @container
  • @xl:grid-cols-[1.1fr_1fr]
  • @md:text-4xl
  • resize-x

Drag the corner of the box or use the width buttons.

The hero answers to the box it sits in, not the screen: @container on the wrapper, then @md:text-4xl for the headline and @xl:grid-cols-[1.1fr_1fr] to put the photo beside the copy once the box reaches 36rem. resize-x with overflow-auto makes the box draggable for testing, and the width buttons do the same for keyboard users. The same markup can sit in a full-width page and in a narrow sidebar column.

View code
<div class="w-full bg-slate-100 px-4 py-8 md:px-8 md:py-12">
  <div class="mx-auto max-w-5xl">
    <div class="mb-4 flex flex-wrap items-center gap-2">
      <span class="text-sm font-medium text-slate-700">Box width:</span>
      <button type="button" class="thr-cq-size min-h-10 rounded-lg bg-white px-3 text-sm font-semibold text-slate-800 ring-1 ring-slate-300 ring-inset hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-indigo-600 aria-pressed:bg-indigo-700 aria-pressed:text-white aria-pressed:ring-indigo-700" data-width="300px" aria-pressed="false">Narrow</button>
      <button type="button" class="thr-cq-size min-h-10 rounded-lg bg-white px-3 text-sm font-semibold text-slate-800 ring-1 ring-slate-300 ring-inset hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-indigo-600 aria-pressed:bg-indigo-700 aria-pressed:text-white aria-pressed:ring-indigo-700" data-width="520px" aria-pressed="false">Medium</button>
      <button type="button" class="thr-cq-size min-h-10 rounded-lg bg-white px-3 text-sm font-semibold text-slate-800 ring-1 ring-slate-300 ring-inset hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-indigo-600 aria-pressed:bg-indigo-700 aria-pressed:text-white aria-pressed:ring-indigo-700" data-width="100%" aria-pressed="true">Full</button>
      <span id="thr-cq-width" class="ml-auto font-mono text-sm text-slate-600"></span>
    </div>
    <div id="thr-cq-box" class="@container w-full max-w-full min-w-[260px] resize-x overflow-auto rounded-2xl border-2 border-dashed border-slate-300 bg-white">
      <div class="grid items-center gap-6 p-5 @md:p-7 @xl:grid-cols-[1.1fr_1fr] @xl:gap-8">
        <div>
          <h1 class="text-2xl font-bold tracking-tight text-balance text-indigo-950 @md:text-4xl">Canal-side rooms from 89 a night</h1>
          <p class="mt-3 text-base text-slate-600 @md:text-lg/8">Twelve rooms over a 1700s merchant house, bikes included, breakfast on the water terrace.</p>
          <div class="mt-6 flex flex-col gap-2.5 @sm:flex-row">
            <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg bg-indigo-700 px-5 text-base font-semibold text-white hover:bg-indigo-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700">Check dates</a>
            <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-lg px-5 text-base font-semibold text-indigo-900 ring-1 ring-indigo-200 ring-inset hover:bg-indigo-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700">See the rooms</a>
          </div>
        </div>
        <img src="canal-houses.webp" width="800" height="600" alt="Old gabled houses along a canal, with small boats moored at a waterside terrace" class="aspect-[4/3] h-auto w-full rounded-xl object-cover @xl:aspect-square">
      </div>
    </div>
  </div>
</div>

Brand tokens with @theme

  • @theme
  • --color-thr-clay
  • --font-thr-display
  • rounded-thr-arch

The palette, the display font stack and an arched radius are declared once in @theme, and Tailwind turns each variable into utilities: bg-thr-sand, text-thr-clay-dark, font-thr-display, rounded-thr-arch. The tokens are also CSS variables at runtime, so a theme change is one edit in the stylesheet. Prefixing the names keeps them from colliding with the default palette or other demos on the same page.

View code
<div class="w-full bg-thr-sand text-thr-ink">
  <header class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-4 md:px-8">
    <a href="#" class="flex items-center gap-2 rounded-md font-thr-display text-xl font-semibold focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-thr-clay">
      <svg class="size-7 text-thr-clay" viewBox="0 0 28 28" fill="currentColor" aria-hidden="true"><path d="M3 24V14a11 11 0 0 1 22 0v10Z"/><circle cx="14" cy="14" r="4" fill="#f3ead9"/></svg>
      Mesa &amp; Ember
    </a>
    <a href="#" class="rounded-full px-3 py-2 text-sm font-semibold text-thr-clay-dark hover:bg-thr-clay/10 focus-visible:outline-2 focus-visible:outline-thr-clay">Rooms</a>
  </header>
  <div class="mx-auto grid max-w-6xl items-end gap-10 px-5 pt-8 pb-16 md:grid-cols-[1fr_0.9fr] md:gap-14 md:px-8 md:pt-12 md:pb-20">
    <div class="md:pb-6">
      <h1 class="font-thr-display text-5xl leading-[1.02] font-semibold tracking-tight text-balance md:text-7xl">Sleep where the canyon turns red</h1>
      <p class="mt-6 max-w-md text-lg/8 text-thr-ink/80">A twelve-room adobe lodge at the end of a gravel road, with sunset walks, a wood oven and no television in sight.</p>
      <div class="mt-8 flex flex-col gap-3 sm:flex-row">
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-full bg-thr-clay px-7 text-base font-semibold text-white hover:bg-thr-clay-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-thr-clay">Reserve a room</a>
        <a href="#" class="inline-flex min-h-12 items-center justify-center rounded-full px-7 text-base font-semibold text-thr-clay-dark ring-1 ring-thr-clay/40 ring-inset hover:bg-thr-clay/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-thr-clay">Plan the drive</a>
      </div>
    </div>
    <img src="red-rock-canyon.webp" width="800" height="600" alt="Evening sun lighting up a red sandstone bluff above a rocky slope" class="aspect-[4/5] h-auto w-full rounded-thr-arch object-cover shadow-xl shadow-thr-clay-dark/20">
  </div>
</div>

What a Tailwind hero is made of

The hero sits at the top of the page, in the area visitors see above the fold before they scroll.

A typical one is a container, a headline, a subheadline, some buttons and a media slot. The media slot is the part text-only layouts drop. The container caps the width and centers the content, usually with mx-auto max-w-7xl px-6. One h1 carries the main promise, and a p under it holds a line or two of supporting copy. The buttons are a primary action plus, often, a quieter secondary link. The media can be a screenshot, photo, illustration or video.

The primary button matters most. It is the call-to-action that the rest of the hero exists to support.

Component galleries add extras constantly. Creative Tim’s hero blocks put an announcement badge above the headline, and Material Tailwind’s hero set includes a lead-generation variant.

Others add an email field beside the button or lay a navbar over the top of the section.

On semantics, Flowbite’s build guide starts with a section element and calls for at least one h1 on the page. The hero is the obvious home for it.

How to build a basic Tailwind hero from scratch

The whole build is one short block of markup and no custom CSS.

  1. Install Tailwind with npm install tailwindcss @tailwindcss/vite, add the plugin to the Vite config, and put @import "tailwindcss"; in the main CSS file (Tailwind CSS installation docs).
  2. Wrap the content in a section, with a div inside it carrying mx-auto max-w-7xl px-6 py-24 text-center.
  3. Add an h1 with text-5xl font-bold tracking-tight text-balance for the headline.
  4. Add a p with mx-auto mt-6 max-w-2xl text-lg for the subheadline, so the line length stays readable.
  5. Build the button row with mt-10 flex items-center justify-center gap-x-4.
  6. Paste the markup into Tailwind Play, or resize the dev server window to a narrow width, and see how it holds up.

The finished markup looks like this:

<section class="bg-white">
  <div class="mx-auto max-w-7xl px-6 py-24 text-center">
    <h1 class="text-5xl font-bold tracking-tight text-balance text-gray-900">
      Ship the page, not the stylesheet
      </h1>
    <p class="mx-auto mt-6 max-w-2xl text-lg text-gray-600">
      One or two lines that say what the product does.
      </p>
    <div class="mt-10 flex items-center justify-center gap-x-4">
      <a href="#" class="rounded-md bg-indigo-600 px-5 py-3 text-sm font-semibold text-white hover:bg-indigo-500">Get started</a>
      <a href="#" class="text-sm font-semibold text-gray-900">Learn more</a>
    </div>
  </div>
</section>

The size jump from text-5xl to text-lg sets up the visual hierarchy. The headline gets read first, and the button gets found without any hunting.

text-balance sets text-wrap: balance, which evens out line lengths in short blocks. Use it on headlines, not body text.

The py-24 class adds 6rem of vertical padding. That’s the white space that keeps a short hero from feeling cramped.

Tailwind Labs’ v4.0 release notes (January 2025) report full builds dropping from 378ms to 100ms on the Catalyst project, and incremental rebuilds with new CSS from 44ms to 5ms.

That matters most while tuning a hero, when spacing and type classes get swapped over and over.

Which Tailwind hero layout fits your page?

Short copy and a single offer fit a centered hero. If there’s a product screenshot that needs room, go split. Full-screen is for brands carried by a photo, and a video background only makes sense when motion is the product.

LayoutKey classesBest forTrade-off
Centeredflex flex-col items-center text-centerShort copy, one CTANo room for product visuals
Splitgrid items-center gap-12 lg:grid-cols-2SaaS with a screenshotImage drops below copy on mobile
Full-screen imagerelative min-h-screen, object-cover imageBrand, travel, eventsText needs an overlay to stay readable
Video backgroundabsolute inset-0 size-full object-cover on the videoMotion-led brandsHeaviest of the four

If the headline and subheadline fit on a few short lines, go centered. If the copy needs a full paragraph, go split.

Centered hero

Horizontal centering is text-center on the wrapper, plus mx-auto on any width-capped child such as the subheadline. For vertical centering, flex min-h-[70vh] flex-col items-center justify-center on the wrapper centers the stack inside a taller block.

Centered works until the copy passes three lines. After that the ragged edges start to look messy.

Split hero

A split hero gives the copy one column and the media the other. The wrapper becomes a two-column grid layout built from grid items-center gap-12 lg:grid-cols-2, so the columns stack on small screens and sit side by side from the lg breakpoint.

The media column usually holds a product screenshot or a hero image styled with rounded-xl shadow-xl.

Put the copy first in the markup so mobile and screen readers meet it before the image, then flip the visual order on desktop with lg:order-last on the media. Give the image w-full h-auto so it never overflows its column.

Full-screen hero

Full-screen heroes are the layout most often overdone.

The wrapper is relative isolate flex min-h-screen items-center, and the photo sits inside it as an img with absolute inset-0 -z-10 size-full object-cover. The object-cover class crops the photo to fill the box without stretching it (Tailwind object-fit docs).

One catch on phones. Per the Tailwind docs, min-h-screen maps to 100vh, while min-h-dvh follows the viewport as the browser toolbar expands and contracts.

Switch to min-h-dvh when a button near the bottom must stay visible. Most free cover blocks follow this pattern with a full background image, a black overlay and two CTAs.

Video background hero

A video background reuses the full-screen structure. The video element replaces the image and takes the same absolute inset-0 size-full object-cover classes.

The video also needs autoplay, muted, loop and playsinline attributes. Browsers apply autoplay blocking to media with an active audio track, so muted is not optional (MDN autoplay guide). The same guide suggests a poster image as the fallback when autoplay is denied.

Video adds the most weight of the four layouts. Keep the file short and compressed.

Gradient and image backgrounds in Tailwind v4

Gradients in v4 use bg-linear-to-r where v3 used bg-gradient-to-r. Photo backgrounds still pair bg-cover and bg-center, with a bg-black/50 layer on top so the text stays readable.

Gradient backgrounds

The v4 upgrade guide shows the rename in its own examples, and the npx @tailwindcss/upgrade tool rewrites the old classes automatically. The old bg-gradient-to-* names are still accepted in v4 for backward compatibility, so a pasted v3 gradient usually keeps rendering. The v3 leftover that does break a hero is bg-opacity-*, which v4 removed. An overlay written as bg-black bg-opacity-50 stops being translucent.

bg-gradient-to-r becomes bg-linear-to-r, and bg-gradient-to-br becomes bg-linear-to-br. For overlays, bg-black bg-opacity-50 becomes bg-black/50.

A working v4 hero background looks like bg-linear-to-br from-indigo-600 via-violet-600 to-purple-700.

v4 also adds angle utilities such as bg-linear-45, plus bg-radial and bg-conic. An interpolation modifier like bg-linear-to-r/oklch switches the color space.

Some third-party tutorials say v4 gradients interpolate in OKLCH by default. The official Tailwind docs say the default is oklab, and treat oklch as the opt-in modifier.

One failure shows up on three-stop gradients. A state variant such as dark:from-blue-500 keeps the base via color, so add dark:via-none to drop it (Tailwind upgrade guide).

To pick stop colors before writing any classes, a CSS gradient generator gets a first draft on screen fast. Then copy the stops into from-, via- and to- utilities.

Image with overlay

<section class="relative bg-[url(/img/hero.jpg)] bg-cover bg-center">
  <div class="absolute inset-0 bg-black/50"></div>
  <div class="relative mx-auto max-w-7xl px-6 py-32 text-center text-white">
    <!-- headline, subheadline, buttons -->
  </div>
</section>

The overlay is what makes white text readable on a photo. Raise bg-black/50 to bg-black/60 when the image is bright.

For the photo itself, use an img with object-cover when it’s real content, because it takes alt text and can be loaded with priority. Use bg-cover when the picture is decoration only.

How to make a Tailwind hero responsive and add dark mode

Write the mobile layout with unprefixed classes, then add sm:, md: and lg: prefixes for wider screens. Dark mode works through dark: variants, which follow the operating system setting in v4 until you override the variant for a manual toggle.

Minimum widths for the default breakpoints come from the Tailwind CSS responsive design docs.

  • sm: 640px (40rem)
  • md: 768px (48rem)
  • lg: 1024px (64rem)
  • xl: 1280px (80rem)
  • 2xl: 1536px (96rem)

Responsive behavior

Tailwind is mobile-first. Unprefixed utilities apply at every width, and prefixed ones apply from that breakpoint upward.

The docs flag the usual slip. sm:text-center centers text only from 640px, not on phones, so write text-center sm:text-left when the mobile layout should differ.

A hero that scales cleanly looks like this:

<div class="mx-auto max-w-7xl px-6 py-16 text-center sm:py-24 lg:py-32">
  <h1 class="text-4xl font-bold tracking-tight text-balance sm:text-5xl lg:text-6xl">
    Headline
    </h1>
  <div class="mt-10 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
    <!-- buttons -->
  </div>
</div>

The headline steps up in size as the screen widens, because a fixed large size is heavy on a phone. Vertical padding grows from py-16 to lg:py-32, and the buttons stack on phones before moving into a row from sm. Decorative media can hide below lg with hidden lg:block.

None of this works without the viewport meta tag in the head, <meta name="viewport" content="width=device-width, initial-scale=1.0" />, which the Tailwind docs list as the first step.

Dark mode

Dark mode needs no setup in v4. The dark: variant follows the prefers-color-scheme media query by default, according to the Tailwind dark mode docs.

For a manual toggle, override the variant in the main CSS file:

@import "tailwindcss";
  @custom-variant dark (&:where(.dark, .dark *));

Now dark: utilities apply whenever a dark class sits higher in the HTML tree, usually on the html element.

On the hero, the section background is bg-white dark:bg-gray-950. The headline and subheadline pair text-gray-900 dark:text-white with text-gray-600 dark:text-gray-400.

Projects that already theme with attributes can use a data-theme=dark selector in the same override instead of a class.

Put the script that sets the class inline in the head. The docs warn that loading it later flashes the wrong theme.

Free and paid Tailwind hero libraries compared

Tailwind Plus was the first-party option, sold as a one-time purchase, until it closed to new customers in September 2026. HyperUI and Flowbite give you MIT-licensed code at no cost. daisyUI is the odd one out, since it swaps long utility strings for a semantic hero class.

LibraryCost and licenseApproachWatch out for
Tailwind PlusWas a one-time purchase, proprietary; closed to new customersCopy-paste markup from Tailwind LabsLicense barred resale in multi-client templates
FlowbiteFree, MIT; paid Pro tierMarkup plus optional Flowbite JSInteractive parts need the JS
HyperUIFree, MITCopy and paste, no installSome vanilla JS, uneven dark mode
Preline UIFree, MIT plus Fair Use LicenseMarkup plus Preline JSTwo license documents to read
daisyUIOpen source pluginSemantic classes: hero, hero-contentClass names need the plugin

Tailwind Plus listed 12 hero sections and was sold as a one-time purchase with lifetime access. When Tailwind Labs joined Shopify on September 9, 2026, it closed sign-ups for new customers. Existing customers keep their access (Tailwind Labs).

The license ruled out using the code in a website or template that is resold to multiple clients.

Since new licenses are no longer sold, any Tailwind Plus price quoted in older articles no longer applies.

Preline UI is free for personal and commercial projects under the MIT License and the Preline UI Fair Use License, and its free templates include landing page layouts (Preline README, 2026).

HyperUI’s README says its components need no installation. Its FAQ adds that some interactive ones use a little vanilla JavaScript, and not all have dark mode variants.

In daisyUI, the same section looks like this:

<div class="hero min-h-screen bg-base-200">
  <div class="hero-content flex-col lg:flex-row">
    <!-- image, headline, button -->
  </div>
</div>

daisyUI also ships hero-overlay, a class that covers a background image, so there is no hand-built black layer. The cost is that those class names exist only while the plugin is installed.

Pick by Tailwind version and license first, looks second. A free block written for v4 beats a prettier one written for v2.

Performance and accessibility checks for a Tailwind hero

A hero passes on speed when Largest Contentful Paint lands at 2.5 seconds or less at the 75th percentile (web.dev). On contrast, its text needs a 4.5:1 ratio, or 3:1 for large text, against the background (W3C, WCAG 2.2).

HTTP Archive’s 2024 Web Almanac found that 9% of mobile pages start their LCP from a CSS background image, which the browser discovers only after the stylesheet is processed.

A bg-cover hero has the same exposure. The web.dev fix is a preload with fetchpriority="high", or a plain img in the markup.

Lazy loading is a separate trap. The 2025 Web Almanac counts about 16% of mobile pages lazy-loading their LCP image, 10.4% natively and 5.9% through custom code. The 2024 edition had 9.5% and 6.7%. Third-party write-ups that quote about 10% match the native figure only.

Priority makes a visible difference. In web.dev’s test of the Google Flights page, fetchpriority="high" on the hero image cut LCP from 2.6s to 1.9s.

<img src="/img/hero.webp" alt="Dashboard preview" width="1200" height="600" fetchpriority="high" class="w-full h-auto">

Explicit width and height reserve the space. The 2025 Almanac calls missing image dimensions one of the most persistent and preventable causes of layout shift, with 62% of mobile pages leaving at least one image unsized. Leave loading="lazy" off this image.

web.dev lists elements that cover the full viewport among those Chromium excludes as background, so a true full-screen hero image can drop out of LCP candidacy.

Check which element Lighthouse or DevTools reports before tuning an asset. For a video background, the poster image or first frame counts, whichever is earlier.

On the accessibility side, the hero’s risk is text over imagery. A bg-black/50 layer is a color contrast fix, not decoration.

W3C’s guidance counts large-scale text as 18 point, or 14 point bold, and says to compare unrounded values, so 4.499:1 fails. Test against the lightest part of the photo behind the words, not the average.

The rest of web accessibility for a hero is short.

An informative img needs descriptive alt text, while decoration gets alt="". A CSS background has no alt at all. Buttons should carry focus-visible:outline-2 focus-visible:outline-offset-2, and an autoplaying video should get motion-reduce:hidden with a static image behind it.

When a copy-pasted Tailwind hero breaks or does not fit

A pasted hero breaks when it targets a different Tailwind version, needs a browser v4 excludes, leans on JavaScript or a plugin you did not install, or ships under a license that forbids your use.

Version is the most common culprit. A Creative Tim roundup of nine free hero sections, first published on Tailwind Components, lists versions from 1.3.4 to 3.0.18, none of them v4. Their v3-era gradient classes should get the renames from the upgrade guide, and any bg-opacity-* overlay needs the slash syntax.

Dark mode setup trips people too. A block built around darkMode: 'class' in tailwind.config.js needs the @custom-variant override in v4. Without it, dark: follows the operating system.

Heroes with a navbar or menu often need extra scripts, and HyperUI says some of its interactive components include vanilla JavaScript. Tailwind Plus code, meanwhile, was licensed with a ban on sites or templates resold to multiple clients.

The browser floor is the limit people forget. Tailwind v4 targets Safari 16.4+, Chrome 111+ and Firefox 128+, and projects that need older browsers stay on v3.4 (Tailwind upgrade guide).

If a client still has visitors on older browsers, cross-browser compatibility testing comes before the first paste.

The quickest check for the rest is a build. Drop the block into a v4 project, and any element that renders unstyled usually carries a removed or renamed class.

Tailwind Hero FAQ

Is a Tailwind hero the same thing as HeroUI, Heroicons or Hero Patterns?

No. Those are separate products with similar names: HeroUI is a component library, Heroicons is an icon set, and Hero Patterns is a collection of SVG background patterns.

A Tailwind hero is the page section itself, and any of the three can decorate it.

Can a Tailwind hero be used in React or Next.js?

Yes. Paste the markup into a component and change class to className.

Keep the hero image eager-loaded, because image components in some frameworks lazy-load by default. Setup steps for using Tailwind CSS in React cover the install.

How do you animate a Tailwind hero without JavaScript?

Combine the transition and animate-* utilities, or define custom keyframes in an @theme block.

Animate the supporting lines and buttons, not the LCP element (usually the hero image or headline), because Shopify’s performance docs warn that fade-in effects on the LCP element can delay LCP by several seconds. Add motion-reduce:animate-none for visitors who want less motion.

How do you put a navbar on top of a hero image?

Make the hero relative and pin the navbar to its top edge with absolute inset-x-0 top-0 z-10.

Then add top padding to the hero content so the headline clears the bar, and check link contrast against the photo. A Tailwind navbar guide covers the menu markup itself.

How do you make gradient text in a Tailwind hero?

Apply a gradient, clip it to the text, and make the text transparent with bg-linear-to-r from-indigo-500 to-purple-600 bg-clip-text text-transparent.

Put it on a span around the key words inside the h1, so the rest of the headline stays plain. Check contrast at both ends of the gradient.

The Fastest Safe Path to a Shipped Tailwind Hero

The fastest safe path to a shipped Tailwind hero starts with the Tailwind version and the license. Image loading and priority come next, then overlay contrast, then breakpoints and dark mode.

A version mismatch can drop classes silently, so every later check would test the wrong output. Image loading comes second because a layout built around a CSS background costs more to change once the design is approved.

Contrast comes last among the checks because it depends on the final photo and overlay.

The trade-off is a slower first preview, since checks come before visual polish.

This sequence was verified against the Tailwind CSS v4.3 documentation on October 29, 2026. A new major release that changes class names or browser support reopens the first step.