EmpireUI
Get Pro
← Blog7 min read#tailwind-css#404-page#error-page

Tailwind 404 Page: Not-Found UI That Keeps Users on Site

Build a Tailwind CSS 404 page that recovers lost visitors instead of sending them away. Real code, real patterns, zero fluff.

Dark browser window showing a stylized 404 error page with glowing text and a navigation prompt

Your 404 Page Is Probably Losing You Real Users

Honestly, the default Next.js or Vite 404 screen is one of the most neglected pieces of any project. It's plain white, says "404 | Not Found" in a small font, and offers exactly zero help. Users hit it and bounce immediately — not because your product is bad, but because the page gave them nothing to hold onto.

A 404 page isn't just an error message. It's a fork in the road. You can either let the user leave, or you can give them a reason to stay — a search bar, navigation links, a way back home. The design of that screen matters far more than most teams realize.

This article walks through building a production-quality 404 page with Tailwind CSS (v4.0.2) and React. We'll cover layout, typography, animations, and the UX decisions that actually keep users on site. No magic tricks — just pragmatic UI work.

The Anatomy of a Good Tailwind 404 Page

There are four things every effective not-found page needs: a clear signal that something went wrong, a human message that doesn't feel robotic, at least one meaningful action the user can take, and a visual design that stays consistent with your brand. That's it. Don't overthink it.

The "clear signal" doesn't have to be a giant "404" in the middle of the screen — though that can work. It can be an illustration, an icon, or even just a headline like "Looks like this page moved." What matters is that the user immediately understands what happened.

The action matters most. A single "Go Home" button is fine, but a search input plus a few curated links is significantly better. Studies consistently show that users who are given a path forward on error pages convert at much higher rates than those dropped on a dead end.

Setting Up the 404 Component in Next.js App Router

In Next.js 13+ with the App Router, you create a not-found.tsx file in your app/ directory. That file renders whenever you call notFound() from a server component, or when the router can't match a route. It's the right place to build your custom 404 UI.

Here's a solid starting component using Tailwind v4.0.2 utility classes. It uses a centered layout, a large muted number, a short headline, and two action buttons.

// app/not-found.tsx
import Link from 'next/link';

export default function NotFound() {
  return (
    <main className="min-h-screen bg-zinc-950 flex flex-col items-center justify-center px-6 text-center">
      <p className="text-8xl font-black text-zinc-800 select-none leading-none">404</p>
      <h1 className="mt-4 text-2xl font-semibold text-zinc-100">
        Page not found
      </h1>
      <p className="mt-2 text-sm text-zinc-400 max-w-xs">
        That URL doesn't exist — it may have moved, or you followed a bad link.
      </p>
      <div className="mt-8 flex gap-3">
        <Link
          href="/"
          className="rounded-lg bg-white px-5 py-2.5 text-sm font-medium text-zinc-900 hover:bg-zinc-100 transition-colors"
        >
          Go home
        </Link>
        <Link
          href="/blog"
          className="rounded-lg border border-zinc-700 px-5 py-2.5 text-sm font-medium text-zinc-300 hover:border-zinc-500 hover:text-zinc-100 transition-colors"
        >
          Browse articles
        </Link>
      </div>
    </main>
  );
}

The select-none on the "404" text prevents accidental text selection when users double-click in frustration. Small detail, but it cleans up the interaction. The bg-zinc-950 gives a near-black background that contrasts well with the muted text-zinc-800 number — it reads as a watermark rather than the main content.

Adding a Glassmorphism Card for a More Polished Look

If your brand leans darker or more premium, wrapping the 404 content in a glass card makes a big difference. It adds depth without requiring a custom illustration. You can pair this with a gradient or particle background for a real statement page.

The trick with glassmorphism on error pages is restraint. One frosted panel, not three. If you haven't read the full breakdown on what is glassmorphism, the short version is: backdrop-blur, a semi-transparent background, and a subtle border. Tailwind makes this straightforward.

<div
  className="
    rounded-2xl border border-white/10
    bg-white/5 backdrop-blur-md
    px-10 py-12 max-w-md w-full
    shadow-xl shadow-black/40
  "
  style={{ background: 'rgba(255,255,255,0.04)' }}
>
  {/* 404 content here */}
</div>

Notice the inline style override. Tailwind's bg-white/5 resolves to rgba(255,255,255,0.02) at some scales, and sometimes you want exactly rgba(255,255,255,0.04) or rgba(255,255,255,0.06) for a specific depth. Don't be afraid to mix a one-liner inline style with utility classes — it's not cheating, it's pragmatic. For deeper glass effects, check out tailwind glassmorphism advanced.

Animating the 404 Number Without a Heavy Library

You don't need Framer Motion for a 404 animation. Tailwind's animate- utilities plus a tiny bit of custom CSS will get you 90% of the way there. The goal is to make the page feel alive without being distracting.

A subtle pulse or float on the large "404" text works well. Add this to your global CSS file:

/* globals.css */
@keyframes float {
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(-8px); }
}

.animate-float {
  animation: float 4s ease-in-out infinite;
}

Then apply className="animate-float" to the <p> element containing your 404 number. The 8px vertical travel is enough to register as motion without feeling busy. You can also scope this with @media (prefers-reduced-motion: no-preference) to respect accessibility settings — something Tailwind v4 makes easier with its new variant system, which is covered in detail in tailwind v4 features.

Search Bar Integration: The Highest-Value Addition

Here's the thing: a search bar on your 404 page can recover users who would otherwise be gone. It takes maybe 20 minutes to add, and it's the single highest-ROI improvement to any not-found screen. Why do so many teams skip it?

For a Next.js app, you can use a simple client component with a router push. Keep it uncontrolled to avoid hydration issues on the not-found boundary:

'use client';
import { useRouter } from 'next/navigation';

export function NotFoundSearch() {
  const router = useRouter();

  function handleSearch(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const q = new FormData(e.currentTarget).get('q') as string;
    if (q.trim()) router.push(`/search?q=${encodeURIComponent(q.trim())}`);
  }

  return (
    <form onSubmit={handleSearch} className="mt-6 flex gap-2 w-full max-w-sm">
      <input
        name="q"
        type="search"
        placeholder="Search the site…"
        className="
          flex-1 rounded-lg bg-zinc-800 border border-zinc-700
          px-4 py-2 text-sm text-zinc-100 placeholder:text-zinc-500
          focus:outline-none focus:ring-2 focus:ring-zinc-500
        "
      />
      <button
        type="submit"
        className="rounded-lg bg-zinc-700 px-4 py-2 text-sm font-medium text-zinc-100 hover:bg-zinc-600 transition-colors"
      >
        Search
      </button>
    </form>
  );
}

Drop <NotFoundSearch /> into your not-found.tsx layout and you've immediately given users a path forward. If you don't have a /search route yet, you can redirect to a DuckDuckGo or Algolia search with a site: filter as a quick interim solution.

Theming: Dark, Light, and System-Preference Aware

Your 404 page should respect whatever theme toggle you've set up across the rest of the site. Nothing looks more broken than a dark-mode site where the error page snaps to white. In Tailwind v4, the dark: variant with prefers-color-scheme works out of the box if you've configured darkMode: 'media' — or you can use the class strategy to tie it to your existing toggle.

The easiest pattern is to just use semantic color tokens defined as CSS custom properties. Define --color-bg as zinc-950 in dark and zinc-50 in light, then reference it everywhere. That way the 404 page picks up theme changes for free without any extra conditional logic in the component.

If you're exploring how to set up a proper token system, tailwind component patterns covers the architecture side in depth. The short answer for a 404 page: use bg-white dark:bg-zinc-950 and text-zinc-900 dark:text-zinc-100 on the root element and you'll cover 95% of cases without any custom CSS.

Common Mistakes and What to Do Instead

One mistake is making the 404 number so large it dominates the entire viewport on mobile. On a 375px screen, a text-9xl element can push everything else off screen. Use responsive prefixes: text-6xl md:text-9xl. Always check on a real 375px viewport, not just a desktop browser narrowed down.

Another mistake is linking to too many things. Five navigation links, a search bar, social icons, and a newsletter form is too much. It creates decision paralysis. Pick two or three actions maximum. Home link, search bar if you have one, and maybe one contextual link like your docs or blog.

Finally: don't forget the <title> tag. By default Next.js will set the document title to "404" which is fine, but setting it to something like "Page not found — Your App Name" is cleaner and shows up better in browser history. In the App Router you do this via the metadata export in your not-found.tsx file: export const metadata = { title: 'Page not found' };. Takes ten seconds.

FAQ

How do I create a custom 404 page in Next.js App Router?

Create a file at app/not-found.tsx. The App Router automatically renders this component when a route isn't matched or when you call notFound() from a server component. You can export a metadata object from this file to set the page title.

Can I use Tailwind dark mode classes on a Next.js 404 page?

Yes. The not-found.tsx file is a standard React component, so all Tailwind utilities including dark: variants work normally. If you're using the class dark mode strategy, make sure your root layout adds the dark class to <html> before this component renders.

How do I animate the 404 text without Framer Motion?

Define a custom CSS @keyframes animation in your globals.css, then create a custom utility class like .animate-float and apply it to the element. Tailwind v4 also supports arbitrary animation values inline via animate-[name_duration_easing_infinite] syntax if you prefer to stay in the className.

Should a 404 page return a 404 HTTP status code?

Yes, absolutely. In Next.js, the not-found.tsx file automatically returns a 404 status. If you're using a custom redirect to a 404 route instead, make sure you're not accidentally returning a 200 — that confuses crawlers and can cause soft 404 warnings in Google Search Console.

What's the best background for a Tailwind 404 page?

It depends on your brand. A solid bg-zinc-950 is safe and fast. A radial gradient (bg-[radial-gradient(ellipse_at_top,_#18181b,_#09090b)]) adds depth with zero overhead. If you want particles or animated backgrounds, keep them in a separate client component so they don't block the initial server render.

How do I redirect users from my 404 page to the closest matching route?

There's no built-in fuzzy matching in Next.js. The common pattern is to use a search index (like Algolia or Fuse.js) to suggest similar pages, or to implement a redirect map in next.config.js for known moved URLs. For marketing sites, a simple redirect map covering renamed pages covers most cases.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Tailwind Avatar Component: Image, Initials, Group, StatusTailwind CSS Mastery: Every Utility, Plugin, and Pattern in One GuideDark Pagination Component: Page Navigation for Data TablesDrag-and-Drop Sortable Lists in React: No Library Required