Tailwind Footer: 16 Examples, From Minimal to Multi-Column

Tailwind CSS has no footer component, so every Tailwind footer is a column grid that collapses on small screens, a bottom bar for the copyright line and text that meets the 4.5:1 contrast minimum in WCAG 2.2.

Below are 16 Tailwind footer examples you can copy, followed by a guide to layout, sticky footers, dark mode, accessibility and fixing copied code.

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>

Sitemap and multi-column footers

Footers for sites with many destinations: a five-column grid with a brand column, a mega footer that folds into disclosure sections on phones, a SaaS footer with a status badge and a local business footer with opening hours.

Five-column footer with a brand column

  • grid-cols-2 md:grid-cols-5
  • col-span-2 md:col-span-1
  • md:flex-row
  • wrap-break-word

The link area is grid grid-cols-2 md:grid-cols-5: the brand column spans both mobile columns with col-span-2 and drops back to one track from 768px, so the four link lists sit in a 2x2 block on phones and in one row on desktop. Each list is a nav-less ul under a real h2, and the bottom bar is a flex row that stacks with flex-col below md.

View code
<footer class="w-full border-t border-slate-200 bg-white text-slate-600">
  <div class="mx-auto max-w-6xl px-5 py-12 md:px-8 md:py-14">
    <div class="grid grid-cols-2 gap-x-6 gap-y-10 md:grid-cols-5">
      <div class="col-span-2 md:col-span-1 md:pr-4">
        <a href="#" class="inline-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-teal-700">
          <svg class="size-8" viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="9" fill="#0f766e"/><path d="M9 20.5c3-6 11-6 14 0M12 15.5a4 4 0 0 1 8 0" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round"/></svg>
          Ferngrid
        </a>
        <p class="mt-4 max-w-xs text-sm leading-6">Shift planning for clinics and labs. Built in Rotterdam, used in 31 countries.</p>
        <a href="#" class="mt-4 inline-block wrap-break-word rounded-sm text-sm font-medium text-teal-800 underline underline-offset-4 hover:text-teal-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">support@ferngrid.example.com</a>
      </div>
      <div>
        <h2 class="text-sm font-semibold text-slate-900">Product</h2>
        <ul class="mt-4 space-y-3 text-sm">
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Scheduling</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Time off</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Payroll export</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Pricing</a></li>
        </ul>
      </div>
      <div>
        <h2 class="text-sm font-semibold text-slate-900">Resources</h2>
        <ul class="mt-4 space-y-3 text-sm">
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Help center</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Rota templates</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Webinars</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">API docs</a></li>
        </ul>
      </div>
      <div>
        <h2 class="text-sm font-semibold text-slate-900">Company</h2>
        <ul class="mt-4 space-y-3 text-sm">
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">About</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Careers</a> <span class="ml-1 rounded-full bg-teal-50 px-2 py-0.5 text-xs font-medium text-teal-800">4 open</span></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Press</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Contact</a></li>
        </ul>
      </div>
      <div>
        <h2 class="text-sm font-semibold text-slate-900">Legal</h2>
        <ul class="mt-4 space-y-3 text-sm">
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Privacy</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Terms</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Data processing</a></li>
          <li><a href="#" class="rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">Security</a></li>
        </ul>
      </div>
    </div>
    <div class="mt-12 flex flex-col gap-3 border-t border-slate-200 pt-6 text-sm md:flex-row md:items-center md:justify-between">
      <p>&copy; 2026 Ferngrid B.V. All rights reserved.</p>
      <p>KvK 81234567 &middot; VAT NL001234567B01</p>
    </div>
  </div>
</footer>

Mega footer that collapses on phones

  • details
  • group-open:rotate-180
  • md:grid-cols-4
  • matchMedia

On a phone, tap a section heading to open it. From 768px every section is open.

Each column is a details element, so phones get native disclosure sections with group-open:rotate-180 on the chevron, and md:grid md:grid-cols-4 lays them out as columns on desktop where md:hidden removes the chevron. A closed details hides its content in every browser, so a short script watches matchMedia('(min-width: 48rem)'): from md up it opens every section, takes the summary out of the tab order and ignores clicks on it; below md it closes them again.

View code
<footer class="tft-mega w-full bg-[#f6f1e9] text-stone-700">
  <div class="mx-auto max-w-6xl px-5 py-10 md:px-8 md:py-14">
    <div class="flex flex-col gap-4 border-b border-stone-300 pb-8 md:flex-row md:items-end md:justify-between">
      <a href="#" class="w-fit rounded-md text-2xl font-semibold tracking-tight text-stone-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-amber-800">Oak &amp; Ember</a>
      <p class="max-w-sm text-sm leading-6">Hand-thrown stoneware and linen, shipped from our studio in Ghent.</p>
    </div>
    <div class="divide-y divide-stone-300 md:grid md:grid-cols-4 md:gap-8 md:divide-y-0 md:pt-10">
      <details class="group">
        <summary class="flex min-h-12 cursor-pointer list-none items-center justify-between py-3 text-sm font-semibold tracking-wide text-stone-900 uppercase focus-visible:outline-2 focus-visible:outline-amber-800 md:min-h-0 md:cursor-default md:py-0 [&::-webkit-details-marker]:hidden">
          Shop
          <svg class="size-5 text-stone-500 group-open:rotate-180 motion-safe:transition-transform md:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6"/></svg>
        </summary>
        <ul class="space-y-3 pb-5 text-sm md:mt-4 md:pb-0">
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Tableware</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Vases</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Linen</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Seconds sale</a></li>
        </ul>
      </details>
      <details class="group">
        <summary class="flex min-h-12 cursor-pointer list-none items-center justify-between py-3 text-sm font-semibold tracking-wide text-stone-900 uppercase focus-visible:outline-2 focus-visible:outline-amber-800 md:min-h-0 md:cursor-default md:py-0 [&::-webkit-details-marker]:hidden">
          Help
          <svg class="size-5 text-stone-500 group-open:rotate-180 motion-safe:transition-transform md:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6"/></svg>
        </summary>
        <ul class="space-y-3 pb-5 text-sm md:mt-4 md:pb-0">
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Shipping</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Returns</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Care guide</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Track an order</a></li>
        </ul>
      </details>
      <details class="group">
        <summary class="flex min-h-12 cursor-pointer list-none items-center justify-between py-3 text-sm font-semibold tracking-wide text-stone-900 uppercase focus-visible:outline-2 focus-visible:outline-amber-800 md:min-h-0 md:cursor-default md:py-0 [&::-webkit-details-marker]:hidden">
          Studio
          <svg class="size-5 text-stone-500 group-open:rotate-180 motion-safe:transition-transform md:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6"/></svg>
        </summary>
        <ul class="space-y-3 pb-5 text-sm md:mt-4 md:pb-0">
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Our makers</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Workshops</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Wholesale</a></li>
          <li><a href="#" class="rounded-sm hover:text-amber-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Journal</a></li>
        </ul>
      </details>
      <details class="group">
        <summary class="flex min-h-12 cursor-pointer list-none items-center justify-between py-3 text-sm font-semibold tracking-wide text-stone-900 uppercase focus-visible:outline-2 focus-visible:outline-amber-800 md:min-h-0 md:cursor-default md:py-0 [&::-webkit-details-marker]:hidden">
          Visit
          <svg class="size-5 text-stone-500 group-open:rotate-180 motion-safe:transition-transform md:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6"/></svg>
        </summary>
        <div class="pb-5 text-sm leading-6 md:mt-4 md:pb-0">
          <p>Sleepstraat 14, 9000 Ghent</p>
          <p>Thu to Sat, 11:00 to 18:00</p>
          <a href="#" class="mt-2 inline-block rounded-sm font-medium text-amber-900 underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-800">Book a studio visit</a>
        </div>
      </details>
    </div>
    <p class="mt-8 border-t border-stone-300 pt-6 text-sm md:mt-12">&copy; 2026 Oak &amp; Ember Studio</p>
  </div>
</footer>

SaaS footer with a status badge

  • motion-safe:animate-ping
  • ring-inset
  • grid-cols-[repeat(3,minmax(0,1fr))]
  • md:col-span-2

The status badge is a link with a pill made from rounded-full, ring-1 ring-inset and an emerald tint; its dot stacks an animate-ping span behind a solid one, and motion-safe: keeps the ping off for people who reduce motion. The brand block takes md:col-span-2 of a five-track grid and the three link lists share the other three tracks, and the version string uses font-mono tabular-nums.

View code
<footer class="w-full bg-[#f7f7fb] text-slate-600">
  <div class="mx-auto max-w-6xl px-5 py-12 md:px-8">
    <div class="grid gap-10 md:grid-cols-5">
      <div class="md:col-span-2">
        <a href="#" class="inline-flex items-center gap-2 rounded-md font-semibold text-slate-900 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"><rect width="28" height="28" rx="7" fill="#5b21b6"/><path d="M8 18V10l6 4 6-4v8" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"/></svg>
          Mailvane
        </a>
        <p class="mt-4 max-w-xs text-sm leading-6">Transactional email for product teams. 99.99% uptime over the last 90 days.</p>
        <a href="#" class="mt-5 inline-flex items-center gap-2 rounded-full bg-emerald-50 px-3 py-1.5 text-sm font-medium text-emerald-800 ring-1 ring-emerald-600/25 ring-inset hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-700">
          <span class="relative flex size-2.5" aria-hidden="true"><span class="absolute inline-flex size-full rounded-full bg-emerald-400 opacity-75 motion-safe:animate-ping"></span><span class="relative inline-flex size-2.5 rounded-full bg-emerald-500"></span></span>
          All systems operational
        </a>
      </div>
      <div class="grid grid-cols-[repeat(3,minmax(0,1fr))] gap-6 md:col-span-3">
        <div>
          <h2 class="text-sm font-semibold text-slate-900">Platform</h2>
          <ul class="mt-4 space-y-3 text-sm">
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Sending</a></li>
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Templates</a></li>
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Webhooks</a></li>
          </ul>
        </div>
        <div>
          <h2 class="text-sm font-semibold text-slate-900">Developers</h2>
          <ul class="mt-4 space-y-3 text-sm">
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">API reference</a></li>
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">SDKs</a></li>
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Changelog</a></li>
          </ul>
        </div>
        <div>
          <h2 class="text-sm font-semibold text-slate-900">Company</h2>
          <ul class="mt-4 space-y-3 text-sm">
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">About</a></li>
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Security</a></li>
            <li><a href="#" class="rounded-sm hover:text-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Pricing</a></li>
          </ul>
        </div>
      </div>
    </div>
    <div class="mt-12 flex flex-col gap-3 border-t border-slate-200 pt-6 text-sm md:flex-row md:items-center md:justify-between">
      <p>&copy; 2026 Mailvane Inc.</p>
      <p class="font-mono text-xs tabular-nums text-slate-600">API v2026-09 &middot; Region eu-west</p>
    </div>
  </div>
</footer>

Restaurant footer with hours and address

  • address not-italic
  • dl grid-cols-[auto_1fr]
  • tabular-nums
  • tel: link

The contact block is a real address element, which browsers render in italics until not-italic resets it, and the phone number is a tel: style link with a large tap target. Opening hours are a dl laid out with grid grid-cols-[auto_1fr], so days and times line up without a table, and tabular-nums keeps the digits in even columns.

View code
<footer class="w-full bg-[#2a1d16] text-[#eadfce]">
  <div class="mx-auto grid max-w-6xl gap-10 px-5 py-12 md:grid-cols-3 md:px-8 md:py-14">
    <div>
      <p class="font-serif text-3xl text-[#fff7ea]">Trattoria Lume</p>
      <p class="mt-3 max-w-xs text-sm leading-6 text-[#d9c9b3]">Wood-fired bread, hand-rolled pasta and a short wine list from small Ligurian producers.</p>
      <a href="#" class="mt-6 inline-flex min-h-11 items-center rounded-full bg-[#f2b25c] px-5 text-sm font-semibold text-[#2a1d16] hover:bg-[#f5c47d] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#f2b25c]">Book a table</a>
    </div>
    <div>
      <h2 class="text-sm font-semibold tracking-wide text-[#fff7ea] uppercase">Find us</h2>
      <address class="mt-4 space-y-1 text-sm leading-6 not-italic">
        <p>Via dei Fornai 22</p>
        <p>Portland, OR 97209</p>
        <p class="pt-3"><a href="#" class="inline-flex min-h-11 items-center gap-2 rounded-sm font-medium text-[#fff7ea] underline underline-offset-4 hover:text-[#f2b25c] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#f2b25c]"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.28 6.72 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.37c0-.52-.35-.97-.85-1.09l-4.42-1.1c-.44-.11-.9.05-1.17.41l-.97 1.29a1.13 1.13 0 0 1-1.21.38 12.04 12.04 0 0 1-7.14-7.14 1.13 1.13 0 0 1 .38-1.21l1.29-.97c.36-.27.52-.73.41-1.17l-1.1-4.42a1.13 1.13 0 0 0-1.09-.85H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z"/></svg>(503) 555-0142</a></p>
        <p><a href="#" class="rounded-sm underline underline-offset-4 hover:text-[#f2b25c] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#f2b25c]">ciao@lume.example.com</a></p>
      </address>
    </div>
    <div>
      <h2 class="text-sm font-semibold tracking-wide text-[#fff7ea] uppercase">Opening hours</h2>
      <dl class="mt-4 grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 text-sm tabular-nums">
        <dt class="text-[#d9c9b3]">Mon</dt><dd>Closed</dd>
        <dt class="text-[#d9c9b3]">Tue to Thu</dt><dd>17:00 to 22:00</dd>
        <dt class="text-[#d9c9b3]">Fri and Sat</dt><dd>17:00 to 23:30</dd>
        <dt class="text-[#d9c9b3]">Sun</dt><dd>12:00 to 21:00</dd>
      </dl>
      <p class="mt-4 text-sm text-[#d9c9b3]">Walk-ins welcome at the bar.</p>
    </div>
  </div>
  <div class="border-t border-[#4a382c]">
    <p class="mx-auto max-w-6xl px-5 py-5 text-sm text-[#d9c9b3] md:px-8">&copy; 2026 Trattoria Lume &middot; <a href="#" class="rounded-sm underline underline-offset-4 hover:text-[#fff7ea] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#f2b25c]">Allergen menu</a></p>
  </div>
</footer>

Minimal, centered and dark footers

Short footers for portfolios, blogs and small sites, plus the legal row that sits under bigger ones, with language and currency pickers and a cookie settings popover.

Minimal one-line footer with an auto year

  • md:flex-row md:justify-between
  • gap-x-6 flex-wrap
  • textContent year
  • text-pretty

One flex container does the whole layout: flex-col items-center on phones, md:flex-row md:justify-between from 768px, and flex-wrap gap-x-6 on the link list so it never overflows. A two-line script writes new Date().getFullYear() into the year span with textContent, and the static year in the markup stays as a fallback.

View code
<footer class="w-full border-t border-neutral-200 bg-white">
  <div class="mx-auto flex max-w-6xl flex-col items-center gap-4 px-5 py-6 text-sm text-neutral-600 md:flex-row md:justify-between md:px-8">
    <p class="text-center text-pretty">&copy; <span class="tft-year">2026</span> Mira Okafor, product designer in Lisbon</p>
    <ul class="flex flex-wrap justify-center gap-x-6 gap-y-2">
      <li><a href="#" class="rounded-sm hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-900">Work</a></li>
      <li><a href="#" class="rounded-sm hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-900">About</a></li>
      <li><a href="#" class="rounded-sm hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-900">Reading list</a></li>
      <li><a href="#" class="rounded-sm hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-900">Email</a></li>
    </ul>
  </div>
</footer>

Centered footer with social icon links

  • text-center
  • size-11 rounded-full
  • aria-label
  • hover:-translate-y-0.5

Everything is centered with flex flex-col items-center and text-center, and the link row uses flex-wrap justify-center so it breaks into two rows on narrow screens. The icon links are size-11 circles (44px tap targets) with an aria-label naming each destination and aria-hidden SVGs, and the lift on hover sits behind motion-safe:.

View code
<footer class="w-full bg-rose-50 text-rose-950">
  <div class="mx-auto flex max-w-3xl flex-col items-center px-5 py-12 text-center">
    <a href="#" class="rounded-md font-serif text-3xl italic focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-rose-800">Slow Pantry</a>
    <p class="mt-3 max-w-md text-sm leading-6 text-rose-900">Weeknight recipes with five ingredients or fewer. New posts every Sunday.</p>
    <nav aria-label="Footer" class="mt-7">
      <ul class="flex flex-wrap justify-center gap-x-6 gap-y-2 text-sm font-medium">
        <li><a href="#" class="rounded-sm hover:text-rose-700 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800">Recipes</a></li>
        <li><a href="#" class="rounded-sm hover:text-rose-700 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800">Meal plans</a></li>
        <li><a href="#" class="rounded-sm hover:text-rose-700 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800">Cookbook</a></li>
        <li><a href="#" class="rounded-sm hover:text-rose-700 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800">About</a></li>
      </ul>
    </nav>
    <ul class="mt-8 flex flex-wrap justify-center gap-3">
      <li><a href="#" aria-label="Video channel" class="flex size-11 items-center justify-center rounded-full bg-white text-rose-800 ring-1 ring-rose-200 hover:bg-rose-800 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800 motion-safe:transition motion-safe:hover:-translate-y-0.5"><svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="2.5" y="5" width="19" height="14" rx="4"/><path d="m10 9.2 5 2.8-5 2.8z" fill="currentColor" stroke-linejoin="round"/></svg></a></li>
      <li><a href="#" aria-label="Photo feed" class="flex size-11 items-center justify-center rounded-full bg-white text-rose-800 ring-1 ring-rose-200 hover:bg-rose-800 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800 motion-safe:transition motion-safe:hover:-translate-y-0.5"><svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.3" cy="6.7" r="1" fill="currentColor"/></svg></a></li>
      <li><a href="#" aria-label="Community forum" class="flex size-11 items-center justify-center rounded-full bg-white text-rose-800 ring-1 ring-rose-200 hover:bg-rose-800 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800 motion-safe:transition motion-safe:hover:-translate-y-0.5"><svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linejoin="round" d="M4 5.5h16v10H9l-5 4z"/><path stroke-linecap="round" d="M8 9.5h8M8 12.5h5"/></svg></a></li>
      <li><a href="#" aria-label="Pin boards" class="flex size-11 items-center justify-center rounded-full bg-white text-rose-800 ring-1 ring-rose-200 hover:bg-rose-800 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800 motion-safe:transition motion-safe:hover:-translate-y-0.5"><svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linejoin="round" d="M12 21s-6.5-6.1-6.5-11a6.5 6.5 0 0 1 13 0c0 4.9-6.5 11-6.5 11Z"/><circle cx="12" cy="10" r="2.3"/></svg></a></li>
      <li><a href="#" aria-label="RSS feed" class="flex size-11 items-center justify-center rounded-full bg-white text-rose-800 ring-1 ring-rose-200 hover:bg-rose-800 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-800 motion-safe:transition motion-safe:hover:-translate-y-0.5"><svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linecap="round" d="M5 4.5a14.5 14.5 0 0 1 14.5 14.5M5 10.5a8.5 8.5 0 0 1 8.5 8.5"/><circle cx="6" cy="18" r="1.5" fill="currentColor"/></svg></a></li>
    </ul>
    <p class="mt-8 text-xs text-rose-900">&copy; 2026 Slow Pantry. Photos and recipes by the author.</p>
  </div>
</footer>

Newsletter, CTA and app footers

Footers that ask for something: an email sign-up with validation, a call-to-action band that overlaps the footer, app download buttons and a portfolio contact block.

Newsletter footer with validation and a success state

  • aria-invalid:
  • sm:flex-row
  • checkValidity()
  • role="status"

Submit an empty or invalid address, then a valid one.

The form uses novalidate so the script can show its own message: on submit it runs checkValidity() on the type="email" field, sets aria-invalid and fills an error paragraph linked with aria-describedby, and aria-invalid: utilities turn the border and ring red. A valid address hides the form and reveals a role="status" message; the label stays in the markup as sr-only so the field keeps its name.

View code
<footer class="w-full bg-emerald-950 text-emerald-100">
  <div class="mx-auto grid max-w-6xl gap-10 px-5 py-12 md:grid-cols-2 md:items-center md:px-8 md:py-16">
    <div>
      <h2 class="text-2xl font-semibold tracking-tight text-white">The Thursday Field Guide</h2>
      <p class="mt-3 max-w-md text-sm leading-6 text-emerald-200">One trail, one gear tip and one weather note every week. 18,400 hikers read it. Unsubscribe in one click.</p>
    </div>
    <div class="tft-news">
      <form class="tft-news-form" novalidate>
        <label for="tft-news-email" class="sr-only">Email address</label>
        <div class="flex flex-col gap-3 sm:flex-row">
          <input id="tft-news-email" type="email" name="email" required autocomplete="email" placeholder="you@example.com" aria-describedby="tft-news-msg" class="min-h-12 w-full min-w-0 rounded-lg border border-emerald-700 bg-emerald-900 px-4 text-white placeholder:text-emerald-300 focus:outline-2 focus:outline-offset-1 focus:outline-emerald-300 aria-invalid:border-rose-400 aria-invalid:outline-rose-300">
          <button type="submit" class="min-h-12 shrink-0 rounded-lg bg-white px-5 text-sm font-semibold text-emerald-950 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Subscribe</button>
        </div>
        <p id="tft-news-msg" class="mt-2 min-h-5 text-sm text-rose-200"></p>
      </form>
      <div class="tft-news-done hidden rounded-lg border border-emerald-700 bg-emerald-900 p-4" role="status">
        <p class="font-semibold text-white">Check your inbox</p>
        <p class="mt-1 text-sm text-emerald-200">We sent a confirmation link to <span class="tft-news-addr font-medium text-white"></span>.</p>
      </div>
    </div>
  </div>
  <div class="border-t border-emerald-900">
    <div class="mx-auto flex max-w-6xl flex-col gap-2 px-5 py-5 text-sm text-emerald-200 md:flex-row md:justify-between md:px-8">
      <p>&copy; 2026 Ridgeline Outdoors</p>
      <p><a href="#" class="rounded-sm underline underline-offset-4 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Privacy</a> &middot; <a href="#" class="rounded-sm underline underline-offset-4 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Past issues</a></p>
    </div>
  </div>
</footer>

Footer with a CTA band above it

  • relative z-10 -mb-20
  • pt-32
  • bg-sky-700
  • md:flex-row

The call to action is a rounded card that hangs over the footer edge: relative z-10 -mb-20 pulls the footer up underneath it, and pt-32 on the footer makes room so nothing hides behind the card. The card is a flex row from md: and a stack on phones, with a solid primary button and an outlined secondary one.

View code
<div class="w-full bg-white pt-6">
  <div class="relative z-10 mx-auto -mb-20 max-w-5xl px-5 md:px-8">
    <div class="flex flex-col gap-6 rounded-3xl bg-sky-700 px-6 py-8 text-white shadow-lg md:flex-row md:items-center md:justify-between md:px-10 md:py-10">
      <div>
        <h2 class="text-2xl font-semibold tracking-tight">Ship your first survey today</h2>
        <p class="mt-2 max-w-md text-sm leading-6 text-sky-100">Free for up to 300 responses a month. No card needed.</p>
      </div>
      <div class="flex flex-col gap-3 sm:flex-row">
        <a href="#" class="inline-flex min-h-11 items-center justify-center rounded-lg bg-white px-5 text-sm font-semibold text-sky-800 hover:bg-sky-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Create free account</a>
        <a href="#" class="inline-flex min-h-11 items-center justify-center rounded-lg px-5 text-sm font-semibold text-white ring-1 ring-white/60 ring-inset hover:bg-sky-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Talk to sales</a>
      </div>
    </div>
  </div>
  <footer class="bg-slate-900 pt-32 text-slate-300">
    <div class="mx-auto max-w-5xl px-5 pb-10 md:px-8">
      <div class="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
        <a href="#" class="w-fit rounded-md text-lg font-semibold text-white focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-sky-400">Polldeck</a>
        <ul class="flex flex-wrap gap-x-6 gap-y-2 text-sm">
          <li><a href="#" class="rounded-sm hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-400">Features</a></li>
          <li><a href="#" class="rounded-sm hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-400">Templates</a></li>
          <li><a href="#" class="rounded-sm hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-400">Pricing</a></li>
          <li><a href="#" class="rounded-sm hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-400">Privacy</a></li>
        </ul>
      </div>
      <p class="mt-8 border-t border-slate-800 pt-6 text-sm text-slate-400">&copy; 2026 Polldeck Ltd. Made in Leeds.</p>
    </div>
  </footer>
</div>

Footer with app download buttons

  • inline-flex gap-3
  • leading-none
  • grid-cols-[auto_1fr]
  • max-md:order-first

The download buttons are generic two-line links: an outline device icon, a small leading-none caption and a larger platform line, inside inline-flex rows with min-h-14 targets. On phones max-md:order-first moves the app block above the link columns, because that is the action mobile visitors came for; from md it returns to the right side of the grid.

View code
<footer class="w-full bg-orange-50 text-stone-700">
  <div class="mx-auto grid max-w-6xl gap-10 px-5 py-12 md:grid-cols-[1fr_1fr_auto] md:px-8">
    <div>
      <h2 class="text-sm font-semibold text-stone-900">Ride</h2>
      <ul class="mt-4 space-y-3 text-sm">
        <li><a href="#" class="rounded-sm hover:text-orange-800 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">City passes</a></li>
        <li><a href="#" class="rounded-sm hover:text-orange-800 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">Station map</a></li>
        <li><a href="#" class="rounded-sm hover:text-orange-800 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">Business accounts</a></li>
      </ul>
    </div>
    <div>
      <h2 class="text-sm font-semibold text-stone-900">Support</h2>
      <ul class="mt-4 space-y-3 text-sm">
        <li><a href="#" class="rounded-sm hover:text-orange-800 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">Report a bike</a></li>
        <li><a href="#" class="rounded-sm hover:text-orange-800 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">Fares and refunds</a></li>
        <li><a href="#" class="rounded-sm hover:text-orange-800 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700">Safety</a></li>
      </ul>
    </div>
    <div class="max-md:order-first">
      <h2 class="text-lg font-semibold text-stone-900">Unlock bikes from your phone</h2>
      <p class="mt-1 text-sm text-stone-600">Scan, ride, park. First 30 minutes free.</p>
      <div class="mt-5 flex flex-wrap gap-3">
        <a href="#" class="inline-flex min-h-14 items-center gap-3 rounded-xl bg-stone-900 px-4 text-white hover:bg-stone-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-stone-900">
          <svg class="size-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="6.5" y="2.5" width="11" height="19" rx="2.5"/><path stroke-linecap="round" d="M10.5 18.5h3"/></svg>
          <span class="flex flex-col"><span class="text-[11px] leading-none text-stone-300">Download for</span><span class="mt-1 text-base leading-none font-semibold">iPhone</span></span>
        </a>
        <a href="#" class="inline-flex min-h-14 items-center gap-3 rounded-xl bg-stone-900 px-4 text-white hover:bg-stone-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-stone-900">
          <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="2"/><path stroke-linecap="round" d="M6 5.5h12M6 18h12"/></svg>
          <span class="flex flex-col"><span class="text-[11px] leading-none text-stone-300">Download for</span><span class="mt-1 text-base leading-none font-semibold">Android</span></span>
        </a>
      </div>
    </div>
  </div>
  <div class="border-t border-orange-200">
    <p class="mx-auto max-w-6xl px-5 py-5 text-sm text-stone-600 md:px-8">&copy; 2026 Spokeway Mobility. Available in 12 cities.</p>
  </div>
</footer>

Portfolio footer with a big contact line

  • text-[clamp(1.75rem,8vw,4.5rem)]
  • wbr + wrap-break-word
  • decoration-2
  • tracking-tighter

The email address is the design: an arbitrary text-[clamp(1.75rem,8vw,4.5rem)] size scales it with the screen, and a wbr after the @ gives the long address a clean break point on phones, with wrap-break-word as the safety net. The underline thickens and shifts color on hover with decoration-2 and decoration utilities, and the small print uses a two-column grid that becomes a row from md.

View code
<footer class="w-full bg-[#f1ece2] text-[#1d1b17]">
  <div class="mx-auto max-w-6xl px-5 py-14 md:px-8 md:py-20">
    <p class="text-lg text-[#4b463c]">Have a brand or a book cover in mind?</p>
    <a href="#" class="mt-3 inline-block rounded-md text-[clamp(1.75rem,8vw,4.5rem)] leading-[1.05] font-semibold tracking-tighter wrap-break-word underline decoration-[#1d1b17]/30 decoration-2 underline-offset-[0.15em] hover:decoration-[#c2410c] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#c2410c]">studio@<wbr>hanne.example.com</a>
    <div class="mt-14 grid grid-cols-2 gap-6 border-t border-[#1d1b17]/20 pt-6 text-sm md:flex md:items-center md:justify-between">
      <p>Hanne Vik, type and identity design</p>
      <p class="text-right md:text-left">Bergen, Norway</p>
      <ul class="col-span-2 flex flex-wrap gap-x-5 gap-y-2">
        <li><a href="#" class="rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#c2410c]">Selected work</a></li>
        <li><a href="#" class="rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#c2410c]">Type foundry</a></li>
        <li><a href="#" class="rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#c2410c]">CV</a></li>
      </ul>
    </div>
  </div>
</footer>

Layout, theming and behavior

The parts around the footer: keeping it at the bottom of short pages, a back-to-top link, a footer themed with @theme tokens and one that follows a dark mode switch.

Sticky footer layout with mt-auto

  • flex min-h-full flex-col
  • mt-auto
  • min-h-dvh
  • aria-live

Add paragraphs and watch the footer move from the bottom edge to after the content.

The fixed-height box stands in for the browser window. Its page wrapper is flex min-h-full flex-col (min-h-dvh flex flex-col on a real body), and the footer has mt-auto, so on a short page the auto margin soaks up the spare height and the footer sits on the bottom edge. Add content with the buttons and the footer follows it once the page is taller than the box.

View code
<div class="tft-sticky w-full bg-slate-100 px-4 py-8 md:px-8">
  <div class="mx-auto max-w-3xl">
    <div class="mb-3 flex flex-wrap items-center gap-2">
      <button type="button" class="tft-add min-h-11 rounded-lg bg-indigo-700 px-4 text-sm font-semibold text-white hover:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700">Add paragraph</button>
      <button type="button" class="tft-remove min-h-11 rounded-lg bg-white px-4 text-sm font-semibold text-slate-800 ring-1 ring-slate-300 hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700">Remove</button>
      <p class="tft-count text-sm text-slate-600" aria-live="polite">1 paragraph</p>
    </div>
    <div class="h-[22rem] overflow-y-auto rounded-xl bg-white ring-1 ring-slate-300" tabindex="0" aria-label="Page preview">
      <div class="flex min-h-full flex-col">
        <header class="border-b border-slate-200 px-5 py-3 text-sm font-semibold text-slate-900">Lumen Docs</header>
        <main class="tft-main space-y-3 px-5 py-5 text-sm leading-6 text-slate-700">
          <p>This page is short, so the footer is pushed to the bottom edge of the window.</p>
        </main>
        <footer class="mt-auto border-t border-slate-200 bg-slate-50 px-5 py-4 text-sm text-slate-600">
          <div class="flex flex-col gap-1 sm:flex-row sm:justify-between">
            <p>&copy; 2026 Lumen Docs</p>
            <a href="#" class="w-fit rounded-sm hover:text-slate-900 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700">Edit this page</a>
          </div>
        </footer>
      </div>
    </div>
  </div>
</div>

Back-to-top link in the footer

  • href="#tft-top"
  • scroll-smooth
  • motion-reduce:scroll-auto
  • scroll-mt-6

Scroll the article to the footer and use Back to top.

The link is a plain in-page anchor to the article heading, so it works without JavaScript; on a real page href="#top" alone jumps to the start of the document. scroll-smooth on the scroll container animates the jump, motion-reduce:scroll-auto turns that off for reduced motion, and scroll-mt-6 keeps a little space above the heading when it lands. The heading has tabindex="-1" and the small script moves focus to it, so keyboard users continue from the top too.

View code
<div class="tft-btt w-full bg-indigo-50 px-4 py-8 md:px-8">
  <div class="tft-btt-scroll mx-auto h-[24rem] max-w-3xl scroll-smooth overflow-y-auto rounded-xl bg-white ring-1 ring-indigo-200 motion-reduce:scroll-auto" tabindex="0" aria-label="Article preview">
    <article class="px-5 py-6 text-sm leading-7 text-slate-700 md:px-8">
      <h2 id="tft-top" tabindex="-1" class="scroll-mt-6 text-xl font-semibold tracking-tight text-slate-900 focus:outline-none">Packing list for a three-day hut trek</h2>
      <p class="mt-3">Hut-to-hut routes let you leave the tent at home, but the bag still fills up fast. These notes come from six summers on the same route.</p>
      <h3 class="mt-6 font-semibold text-slate-900">Clothing</h3>
      <p class="mt-2">One set to walk in, one dry set for the hut. Wool base layers dry overnight on a hut line; cotton does not.</p>
      <h3 class="mt-6 font-semibold text-slate-900">Hut essentials</h3>
      <p class="mt-2">A silk sleeping bag liner is required in most huts. Bring earplugs, a headlamp with a red mode and cash for the dinner bill.</p>
      <h3 class="mt-6 font-semibold text-slate-900">Water and food</h3>
      <p class="mt-2">Carry two litres between huts and a lunch for each day. Huts sell bread and cheese, but not always before 8 in the morning.</p>
      <h3 class="mt-6 font-semibold text-slate-900">Weather</h3>
      <p class="mt-2">Check the forecast at every hut. Afternoon storms build quickly above the tree line, so start early and plan to arrive by two.</p>
    </article>
    <footer class="border-t border-indigo-100 bg-indigo-50/60 px-5 py-5 text-sm text-slate-600 md:px-8">
      <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
        <p>&copy; 2026 Switchback Journal</p>
        <a href="#tft-top" class="tft-btt-link inline-flex min-h-11 w-fit items-center gap-2 rounded-lg bg-indigo-700 px-4 font-semibold text-white hover:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700">
          <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 19V5m-6 6 6-6 6 6"/></svg>
          Back to top
        </a>
      </div>
    </footer>
  </div>
</div>

Footer themed with @theme tokens

  • @theme --color-tft-*
  • font-tft-display
  • bg-tft-paper
  • text-tft-ink/70

Six @theme variables (four colors, a display font stack and a radius) turn into real utilities: bg-tft-paper, text-tft-ink, text-tft-ink/70, border-tft-rule, font-tft-display and rounded-tft. Rebranding the footer means editing six lines of CSS, not every class attribute. The font is a system serif stack, so nothing extra loads.

View code
<footer class="w-full bg-tft-paper text-tft-ink">
  <div class="mx-auto max-w-6xl px-5 py-12 md:px-8 md:py-16">
    <div class="grid gap-10 md:grid-cols-[1.4fr_1fr_1fr]">
      <div>
        <p class="font-tft-display text-4xl leading-none">The Margin</p>
        <p class="mt-4 max-w-sm text-sm leading-6 text-tft-ink/80">An independent quarterly on cities, housing and the people who plan them. Issue 27 is out now.</p>
        <a href="#" class="mt-6 inline-flex min-h-11 items-center rounded-tft bg-tft-ink px-5 text-sm font-semibold text-tft-paper hover:bg-tft-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Subscribe from 9 a year</a>
      </div>
      <div>
        <h2 class="border-b border-tft-rule pb-2 font-tft-display text-lg">Sections</h2>
        <ul class="mt-4 space-y-3 text-sm">
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Essays</a></li>
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Interviews</a></li>
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Photo stories</a></li>
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Back issues</a></li>
        </ul>
      </div>
      <div>
        <h2 class="border-b border-tft-rule pb-2 font-tft-display text-lg">The magazine</h2>
        <ul class="mt-4 space-y-3 text-sm">
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Masthead</a></li>
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Pitch a story</a></li>
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Stockists</a></li>
          <li><a href="#" class="rounded-sm hover:text-tft-accent hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tft-accent">Advertise</a></li>
        </ul>
      </div>
    </div>
    <p class="mt-12 border-t border-tft-rule pt-6 text-sm text-tft-ink/70">&copy; 2026 The Margin Press. Printed on recycled paper in Utrecht.</p>
  </div>
</footer>

Footer that follows a dark mode switch

  • dark:bg-slate-900
  • @custom-variant dark
  • aria-pressed
  • dark:hidden

Press the theme button in the footer.

Every color class has a dark: partner (bg-white dark:bg-slate-900, text-slate-600 dark:text-slate-400, border-slate-200 dark:border-slate-800), and the @custom-variant line makes dark: follow a .dark class instead of the system setting. The button is an aria-pressed toggle that adds dark to the demo wrapper; on a real site the class goes on html. dark:hidden and dark:block swap the sun and moon icons.

View code
<div id="tft-theme" class="w-full">
  <footer class="border-t border-slate-200 bg-white text-slate-600 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400">
    <div class="mx-auto max-w-6xl px-5 py-12 md:px-8">
      <div class="grid grid-cols-2 gap-8 md:grid-cols-4">
        <div class="col-span-2">
          <p class="text-lg font-semibold text-slate-900 dark:text-white">Quillstack</p>
          <p class="mt-3 max-w-xs text-sm leading-6">A writing app for long documents, with outlines, footnotes and offline sync.</p>
        </div>
        <div>
          <h2 class="text-sm font-semibold text-slate-900 dark:text-white">App</h2>
          <ul class="mt-4 space-y-3 text-sm">
            <li><a href="#" class="rounded-sm hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:hover:text-white dark:focus-visible:outline-blue-400">Download</a></li>
            <li><a href="#" class="rounded-sm hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:hover:text-white dark:focus-visible:outline-blue-400">What is new</a></li>
            <li><a href="#" class="rounded-sm hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:hover:text-white dark:focus-visible:outline-blue-400">Roadmap</a></li>
          </ul>
        </div>
        <div>
          <h2 class="text-sm font-semibold text-slate-900 dark:text-white">Help</h2>
          <ul class="mt-4 space-y-3 text-sm">
            <li><a href="#" class="rounded-sm hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:hover:text-white dark:focus-visible:outline-blue-400">Guides</a></li>
            <li><a href="#" class="rounded-sm hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:hover:text-white dark:focus-visible:outline-blue-400">Shortcuts</a></li>
            <li><a href="#" class="rounded-sm hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:hover:text-white dark:focus-visible:outline-blue-400">Contact</a></li>
          </ul>
        </div>
      </div>
      <div class="mt-10 flex flex-col gap-4 border-t border-slate-200 pt-6 text-sm sm:flex-row sm:items-center sm:justify-between dark:border-slate-800">
        <p>&copy; 2026 Quillstack Software</p>
        <button type="button" class="tft-theme-btn inline-flex min-h-11 w-fit items-center gap-2 rounded-full px-4 font-medium text-slate-800 ring-1 ring-slate-300 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:text-slate-100 dark:ring-slate-700 dark:hover:bg-slate-800 dark:focus-visible:outline-blue-400" aria-pressed="false">
          <svg class="size-4 dark: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="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"/></svg>
          <svg class="hidden size-4 dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path stroke-linecap="round" d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
          Dark theme
        </button>
      </div>
    </div>
  </footer>
</div>

How to build a basic Tailwind footer

Everything happens through utility classes on a footer element, so spacing, color and layout live in the markup and no separate stylesheet is involved.

The base is a footer tag, a centered container, a nav with a handful of links, and a copyright line.

Every class below exists in both Tailwind v3 and v4. Tailwind Labs shipped v4.0 in January 2025, and the v4.3 line is current as of October 2026 (the latest release, v4.3.3, came out in July).

Anyone who has built footers in plain CSS will recognize the structure. Only the styling moves from a stylesheet into class attributes.

  1. Open a sandbox. Tailwind Play runs in the browser with no install, and the Tailwind CSS IntelliSense extension for VS Code adds class autocomplete and linting in a local project.
  2. Add the footer tag as a direct child of body, after main, with a background and a text color.
  3. Wrap the content in a container: mx-auto max-w-7xl px-6 py-12.
  4. Put the links in a nav element and space them with flex flex-wrap gap-x-8 gap-y-3.
  5. Add the copyright line under a border-t divider.
  6. Resize the preview to 320px and to a desktop width, then fix whatever wraps badly.
<footer class="bg-gray-900 text-gray-300">
  <div class="mx-auto max-w-7xl px-6 py-12">
    <nav aria-label="Footer" class="flex flex-wrap gap-x-8 gap-y-3 text-sm">
      <a href="/about" class="hover:text-white">About</a>
      <a href="/pricing" class="hover:text-white">Pricing</a>
      <a href="/contact" class="hover:text-white">Contact</a>
      <a href="/privacy" class="hover:text-white">Privacy</a>
    </nav>
    <p class="mt-10 border-t border-gray-800 pt-8 text-sm text-gray-400">
      &copy; 2026 Example Inc. All rights reserved.
      </p>
  </div>
</footer>

That shell works on its own. Columns, icons and a newsletter form get added on top of it.

How to lay out and stack footer columns with grid and flex

Grid handles the link columns. The bottom bar is a flex job. Once a footer has 3 or more labelled link columns (Product, Company, Legal), grid is the right tool, while a copyright row with 2 items doesn’t need it.

Grid for link columns

Sitemap footers usually start from grid grid-cols-2 gap-8 md:grid-cols-4, which gives equal columns that collapse on small screens.

The grid class makes the wrapper a grid container, and grid-cols-2 gives two equal columns on mobile. From the md breakpoint up, md:grid-cols-4 takes over with four columns. gap-8 leaves 2rem between columns and rows.

The Tailwind documentation shows grid-cols-4 generating grid-template-columns: repeat(4, minmax(0, 1fr)). Those tracks stay equal and can shrink below their content width.

That shrinking is why a long email address overflows its column instead of widening it. Add wrap-break-word to any link text that holds long strings (the older break-words name still compiles in v4).

Flex for the bottom bar

Two or three items in one row is a flex job.

Stack them on mobile and spread them from md up with flex flex-col gap-4 md:flex-row md:items-center md:justify-between.

Copyright text goes on the left and social icons at the far end, with the privacy and terms links somewhere between.

Flex wins here because the items have different widths, and nobody wants them forced into equal tracks. Once the row grows past 4 items, move it into the grid above.

Breakpoint stacking

The Tailwind documentation lists 5 default breakpoints: sm, md, lg, xl and 2xl. In v4 they are defined in rem, with lg at 64rem, xl at 80rem and 2xl at 96rem.

Tailwind takes a mobile-first approach. Unprefixed classes set the small-screen layout, and a prefix such as md: applies from that width and stays applied above it.

Each prefix compiles to one of the media queries in the generated stylesheet, so no JavaScript is involved.

Below md the footer shows two link columns with the copyright stacked under them. From md up it switches to four columns, and the bottom bar spreads across the full width.

Two columns is the better mobile default. One column holding every sitemap link turns the footer into a long scroll.

Which footer pattern fits which kind of site?

Match the pattern to how many destinations the site links to, not to whichever design looks best in a gallery. A portfolio needs a one-row bar, a SaaS product needs a sitemap.

Tailwind Plus listed 7 footer components built by the Tailwind team before it closed to new customers in September 2026. Material Tailwind’s PRO blocks add centered, country-selector and complex dark variants with a newsletter field, and the designs from both catalogs end up in the table below.

PatternBlocks includedBest for
Simple one-rowCopyright, a few linksPortfolios, small business sites
CenteredLogo, links, social icons, copyrightPersonal brands, blogs
Multi-column sitemapLogo, 4 to 5 link columns, legal rowSaaS, documentation, content sites
Dark with newsletterEmail field, button, link columnsProduct launches, marketing sites
App footerStore badges, country selector, legal linksMobile apps, international sites

Those 5 patterns cover nearly everything in either catalog.

A dark footer with an email field and a button carries a single call to action. It suits pages built to collect sign-ups and wastes space on a five-page brochure site.

The blocks themselves are navigation links, legal links (privacy policy, terms of service), contact details, social icons, a newsletter form and the copyright line. Pick only the ones the site needs. A small site with no mailing list should skip the newsletter form instead of filling the space.

Social icons need a brand set. Heroicons, the free SVG set Tailwind Labs designed for its own components, covers interface icons like navigation and actions, so brand logos come from somewhere else.

Font Awesome Free icons are CC BY 4.0, so attribution applies, though the downloaded files carry comments that satisfy it (Font Awesome Free License). Simple Icons ships SVG brand icons under CC0 and asks for no attribution at all.

Brand logos stay trademarks of their owners under either set, and Font Awesome’s license says to use each one only for the brand it shows. Give every icon link an aria-label that names the platform.

How do you keep a Tailwind footer at the bottom of the page?

Make the page wrapper a flex column that is at least as tall as the screen, then let main grow or push the footer down with mt-auto. The footer lands on the bottom edge of short pages and follows the content on long ones.

<body class="flex min-h-screen flex-col">
  <header>...</header>
  <main class="grow">...</main>

  <footer class="mt-auto">...</footer>
</body>

Either grow on main or mt-auto on the footer is enough, and using both does no harm. Older tutorials write flex-grow for the same effect, but Tailwind v4 removed that class in favor of grow.

Auto margins only push when the parent is a flex or grid container with spare height. Put mt-auto on a footer inside a plain block wrapper and nothing happens.

The class min-h-screen sets min-height: 100vh, the full height of the viewport.

On phones, 100vh equals the largest possible viewport, so the footer can sit just below the visible edge while the address bar is showing. Tailwind’s min-h-dvh utility (min-height: 100dvh) is the fix, because dvh follows the browser interface as it expands and contracts.

The phrase sticky footer gets stretched over several different behaviors. A flex column with mt-auto pushes the footer down, so it sits at the bottom of short pages and scrolls away on long ones. sticky bottom-0 pins it on screen while the page scrolls, but only inside a parent tall enough to scroll. And fixed bottom-0 takes the footer out of the document flow, which means main needs bottom padding or the last lines vanish behind it.

Tutorials mix these up. Some posts sold as a sticky footer build a regular one, which stops staying on screen once the content is taller than the window.

For a site footer, pushed down is the right default. A pinned footer holding a full sitemap eats screen space at every scroll position, so keep pinning for app toolbars.

Dark mode in a Tailwind footer

Tailwind v4 applies dark: classes when the operating system prefers a dark color scheme, and one line of CSS switches that to a class you control. Add @custom-variant dark (&:where(.dark, .dark *)); after the Tailwind import (Tailwind documentation).

VersionToggle by classWhere it lives
Before v3.4.1darkMode: ‘class’tailwind.config.js
v3.4.1 and laterdarkMode: ‘selector’tailwind.config.js
v4@custom-variant darkMain CSS file

Every class that carries color needs a dark value. The background becomes bg-gray-100 dark:bg-gray-900, and text goes from text-gray-600 to dark:text-gray-400. Link hover is hover:text-gray-900 dark:hover:text-white, and dividers use border-gray-200 dark:border-gray-800.

To follow the system until a visitor picks a theme, toggle the class from a small script that reads localStorage and window.matchMedia. Run it inline in the head so the page does not flash the wrong theme first.

Plenty of sites keep the footer dark in both themes. A fixed bg-gray-900 footer needs no dark: variants at all, and I’d go that way on most sites, since it removes a whole set of color pairs to test.

Making a Tailwind footer accessible

A footer becomes the page’s contentinfo landmark only when it belongs to body. Nested inside main, article, section, aside or nav, it falls back to a generic element with no landmark role (MDN Web Docs).

Screen reader users jump between landmarks, so a missing or duplicated contentinfo costs them a shortcut. MDN’s page on the contentinfo role says a page should have only one, and asks for an aria-label on each landmark when a document holds more than one. More on landmarks in the guide to ARIA.

In practice that means one top-level footer per page, kept outside main. Footers inside articles or sections shouldn’t get a role attribute, and every nav in the footer (Footer, Legal, Social) needs its own aria-label.

Sources split on one detail. MDN notes a known VoiceOver issue and suggests role="contentinfo" for older Safari, while validators such as Rocket Validator flag that same attribute on a footer as redundant.

Skip the role by default. Add it only when a test on an old Safari build shows the landmark missing.

WCAG 2.2 success criterion 1.4.3 sets the minimum at 4.5:1 for normal text and 3:1 for large text. A ratio of 4.499:1 does not round up to a pass (W3C).

Dark footers are where this slips, because the soft gray that looks calm is often the one that fails. Check the color contrast of every text class in the footer.

The ratios below are calculated from Tailwind v4’s default gray palette values with the WCAG relative luminance formula.

Text classOn bg-gray-900Passes 4.5:1
text-gray-30012.06:1Yes
text-gray-4006.82:1Yes
text-gray-5003.67:1No

Moving the background to bg-gray-950 lifts text-gray-500 to 4.16:1, still short. Keep that shade for dividers, not for links or the copyright line.

Free or paid: where to get ready-made Tailwind footers

Start free. HyperUI, Flowbite, daisyUI and Preline UI cover most footer patterns at no cost, and Tailwind Plus no longer sells to new customers.

SourceLicense and costDelivery
Tailwind PlusPaid, closed to new customers since September 2026Copy-paste examples in React, Vue and HTML
HyperUIMIT, freeCopy-paste HTML, no install
FlowbiteMIT, freeComponents plus React and Svelte packages
daisyUIMIT, freePlugin with footer class names
Preline UIMIT and Fair Use License, freenpm package and Tailwind plugin

Tailwind Labs announced on September 9, 2026 that it is joining Shopify and closing sign-ups for new customers, while existing customers keep their access to Tailwind Plus (Tailwind blog). Every Tailwind Plus page now redirects to a login screen.

For the quickest copy-paste, HyperUI wins because there is nothing to install. daisyUI has the shortest markup, since footer, footer-title and footer-center replace long utility strings (the v5 docs add footer-horizontal and footer-vertical). Flowbite reaches the most frameworks, with a documented footer component and one listed in its React and Svelte packages. Tailwind Plus only matters if you already own a license.

Preline UI is free for personal and commercial projects under its dual license, and Preline Pro is the paid tier above it.

Two older kits still turn up in searches. Meraki UI is MIT-licensed, built on flexbox and grid, with dark mode and RTL support, while Tailblocks ships 60+ copy-paste blocks under MIT with a 2020 copyright in its README.

Framework layouts

A footer belongs in the layer every page shares.

In Next.js that is the root layout, next to {children}. It is required, wraps every page, and does not re-render on navigation (Next.js documentation). Astro uses a layout component with a slot, and its documentation lists footers among the shared UI. React just needs a Footer component imported into App or the page layout. Vue works the same way with a single-file component imported into App.vue, where class attributes paste in unchanged.

Pasted HTML needs class changed to className in React and Next.js. An HTML to JSX converter does that along with self-closing tags.

Tailwind has to be set up in the project first, and the walkthrough on using Tailwind CSS in React covers it.

When a copied Tailwind footer breaks

Most broken pastes come down to a version mismatch, class names built from variables, dark: classes that ignore the toggle, or a landmark role the page already has.

Version mismatch

Creative Tim’s collection of free Tailwind footers filters its examples by Tailwind version 1, 2 and 3, with no v4 option.

The reverse problem exists too. Tailwind v4 is designed for Safari 16.4+, Chrome 111+ and Firefox 128+, and the upgrade guide tells projects with stricter cross-browser compatibility needs to stay on v3.4.

The border is a visible example. A bare border-t divider drew gray-200 in v3 and draws currentColor in v4 (Tailwind upgrade guide), so footers pasted from v3 kits need an explicit border color.

Class names that never generate

Tailwind reads source files as plain text, so it only generates classes that appear as complete strings (Tailwind documentation).

Something like bg-${color}-900 breaks, because bg-red-900 never exists anywhere in the source. A lookup object that holds full strings like bg-gray-900 and bg-white, mapped from the prop, works fine.

Footers pulled from a component package in node_modules are skipped by default, since Tailwind ignores files listed in .gitignore. Register the package path with @source.

Dark classes that ignore the toggle

When the footer stays light while the html element carries the dark class, go through these in order.

  1. Open the compiled CSS and look for @media (prefers-color-scheme: dark) around the dark: rules. If they sit inside it, the custom variant is missing.
  2. Confirm the variant line comes after the Tailwind import, as in the documentation example.
  3. Confirm the script puts the class on html, not on body or the footer.

Duplicate landmarks

Forcing role="contentinfo" on a footer inside main creates a second landmark the page does not expect. Axe-core’s landmark-contentinfo-is-top-level rule flags it, and a second contentinfo on the page also trips axe-core’s landmark-no-duplicate-contentinfo rule.

Move the footer out of main and delete the role attribute.

Tailwind Footer FAQ

Do I need JavaScript for a Tailwind footer?

No. Columns, hover states and breakpoints are all handled by classes in the markup.

Scripts only come in for a theme toggle or a self-updating year, and both are optional.

How do I update the copyright year automatically?

Render it from code instead of typing it. In plain HTML, a one-line script that sets textContent to new Date().getFullYear() does it.

In React, Next.js or Astro, put the same expression inside the footer component.

How do I add a newsletter form to a footer?

Put a form in one grid column with an email input and a submit button. Add a label as well, and hide it with sr-only if the design has no room, so the field keeps an accessible name.

Use flex-col on phones and sm:flex-row from the small breakpoint up.

How do I center everything in the footer?

Add text-center for text, and flex justify-center on the row that holds the links.

Keep mx-auto max-w-7xl on the inner wrapper so the content stays centered on wide screens too.

Can I use the Tailwind Play CDN for a production footer?

No. Tailwind’s documentation describes the Play CDN as built for development only, not for production use.

Compile the CSS with the CLI, the Vite plugin or PostCSS before the site goes live.

How do I add a back-to-top link?

Use an anchor with href="#top". Browsers treat that fragment as the start of the document, so no id is needed.

Add scroll-smooth to the html element if the jump should animate.

Building a Tailwind Footer in the Right Order

A Tailwind footer holds up best when the work runs from structure to style to behavior. Each step shrinks what the next one has to check.

  • Footer element and landmark placement
  • Responsive grid and bottom bar
  • Text and background pairs, tested for contrast
  • Dark variants, then the framework component

Color comes before dark mode because every dark variant adds a second set of color pairs to verify. A gray that fails in the light theme gets fixed once, not twice.

Starting from a ready-made block trades setup time for an audit. The copied markup carries its author’s Tailwind version, so border colors, the dark variant line and class names need a check before launch.

This order was verified against the Tailwind v4.3 documentation on October 29, 2026. A new major release that changes the CSS-first configuration would send the dark mode variant line to the top of that check.