Pure TailwindMIT licensed

Tailwind CSS Navbar with Animated Hover Underline

The foundation of every site header, rebuilt in utilities: a sticky, frosted navbar whose links draw an underline with after:scale-x-*. The line grows in from the left and retracts to the right, the active page is styled from aria-current itself, and a native <details> menu takes over below md.

Live Demo
Plain CSSTailwind v4
Try it

Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149

Nucleo · Affiliate link

Get the Nucleo bundle

The code

<div class="tnv-01 flex min-h-svh w-full flex-col bg-tnv01-bg font-tnv01-sans text-tnv01-ink antialiased selection:bg-tnv01-acc selection:text-white">
  <header class="sticky top-0 z-20 border-b border-tnv01-line bg-white/80 backdrop-blur-md">
    <nav aria-label="Main" class="relative mx-auto flex h-16 max-w-6xl items-center justify-between gap-6 px-5 sm:px-8">
      <a href="#" class="flex items-center gap-2.5 rounded-md text-lg font-semibold tracking-[-0.02em] outline-none focus-visible:ring-2 focus-visible:ring-tnv01-acc/40"><span aria-hidden="true" class="size-2 rounded-full bg-tnv01-acc shadow-[0_0_0_4px_var(--color-tnv01-acc-soft)]"></span>Clarity</a>
      <ul class="hidden items-center gap-0.5 md:flex">
        <li><a href="#" aria-current="page" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Home</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Products</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Features</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Pricing</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Blog</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">About</a></li>
      </ul>
      <div class="flex items-center gap-2.5">
        <a href="#" class="hidden h-10 items-center rounded-lg border border-tnv01-line px-4 text-sm font-medium transition-colors hover:border-slate-300 hover:bg-tnv01-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tnv01-acc sm:inline-flex">Sign in</a>
        <a href="#" class="inline-flex h-10 items-center gap-1.5 rounded-lg bg-tnv01-acc px-4 text-sm font-medium text-white shadow-[0_1px_0_0_rgb(255_255_255/0.2)_inset,0_6px_16px_-6px_var(--color-tnv01-acc)] transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tnv01-acc">Get started</a>
        <details class="group/m md:hidden">
          <summary aria-label="Menu" class="grid size-11 cursor-pointer list-none place-items-center rounded-lg border border-tnv01-line bg-white outline-none focus-visible:ring-2 focus-visible:ring-tnv01-acc/40 [&::-webkit-details-marker]:hidden">
            <svg class="size-5 group-open/m:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h10"/></svg>
            <svg class="hidden size-5 group-open/m:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
          </summary>
          <ul class="absolute inset-x-3 top-[calc(100%+8px)] grid gap-0.5 rounded-2xl border border-tnv01-line bg-white p-2 text-[15px] font-medium shadow-[0_24px_48px_-16px_rgb(15_23_42/0.25)] transition-[opacity,translate] duration-200 starting:-translate-y-1 starting:opacity-0">
            <li><a href="#" aria-current="page" class="flex min-h-11 items-center rounded-lg px-3 aria-[current=page]:bg-tnv01-acc-soft aria-[current=page]:text-tnv01-acc hover:bg-tnv01-bg">Home</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Products</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Features</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Pricing</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Blog</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">About</a></li>
          </ul>
        </details>
      </div>
    </nav>
  </header>
  <main class="grid flex-1 place-items-center px-5 py-20 text-center [&_code]:rounded [&_code]:bg-slate-100 [&_code]:px-1.5 [&_code]:py-px [&_code]:font-tnv01-mono [&_code]:text-[0.85em] [&_code]:text-tnv01-ink">
    <div class="grid max-w-2xl justify-items-center">
      <span class="mb-6 inline-flex items-center gap-2 rounded-full bg-tnv01-acc-soft px-3 py-1 text-xs font-semibold tracking-[0.08em] text-tnv01-acc uppercase">Pure Tailwind · zero JS</span>
      <h1 class="text-[clamp(2.25rem,5.5vw,3.75rem)] leading-[1.06] font-semibold tracking-[-0.035em] text-balance">Hover the nav. The underline follows your intent.</h1>
      <p class="mt-5 max-w-xl text-[17px]/[1.65] text-tnv01-mut text-pretty">Each link draws its indicator with <code>after:scale-x-*</code>. It grows in from the left and retracts to the right, and keyboard focus gets exactly the same treatment.</p>
      <div class="mt-10 flex max-w-lg items-start gap-3 rounded-xl border border-tnv01-line bg-white p-4 text-left text-[13px]/[1.55] text-tnv01-mut shadow-[0_1px_2px_rgb(15_23_42/0.04)]">
        <svg class="mt-px size-[18px] flex-none text-tnv01-acc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
        <span>Press <kbd class="rounded border border-tnv01-line bg-tnv01-bg px-1.5 font-tnv01-mono text-[11px]">Tab</kbd> to walk the links. The active page is styled from <code>aria-current</code>, so the visual state and the screen-reader state can never drift apart.</span>
      </div>
    </div>
  </main>
</div>
@import "tailwindcss";

@theme {
  --color-tnv01-bg: #f8fafc;
  --color-tnv01-bg: oklch(0.984 0.003 248);
  --color-tnv01-line: #e2e8f0;
  --color-tnv01-line: oklch(0.929 0.013 256);
  --color-tnv01-ink: #1e293b;
  --color-tnv01-ink: oklch(0.279 0.041 260);
  --color-tnv01-mut: #475569;
  --color-tnv01-mut: oklch(0.446 0.043 257);
  --color-tnv01-acc: #2563eb;
  --color-tnv01-acc: oklch(0.546 0.245 263);
  --color-tnv01-acc-soft: #dbeafe;
  --color-tnv01-acc-soft: oklch(0.932 0.032 256);
  --font-tnv01-sans: 'Geist', system-ui, sans-serif;
  --font-tnv01-mono: 'Geist Mono', ui-monospace, monospace;
}

.tnv-01 {
  width: 100%;
  min-height: 100svh;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tnv01-bg: #f8fafc;
  --color-tnv01-bg: oklch(0.984 0.003 248);
  --color-tnv01-line: #e2e8f0;
  --color-tnv01-line: oklch(0.929 0.013 256);
  --color-tnv01-ink: #1e293b;
  --color-tnv01-ink: oklch(0.279 0.041 260);
  --color-tnv01-mut: #475569;
  --color-tnv01-mut: oklch(0.446 0.043 257);
  --color-tnv01-acc: #2563eb;
  --color-tnv01-acc: oklch(0.546 0.245 263);
  --color-tnv01-acc-soft: #dbeafe;
  --color-tnv01-acc-soft: oklch(0.932 0.032 256);
  --font-tnv01-sans: 'Geist', system-ui, sans-serif;
  --font-tnv01-mono: 'Geist Mono', ui-monospace, monospace;
}

.tnv-01 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&family=Geist+Mono:wght@400..600&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The .links a::after rule and its three state rules became after:, hover:after:, focus-visible:after: and aria-[current=page]:after: utilities on each anchor.
  • .nav-01__active was deleted: aria-current=&quot;page&quot; now drives the visual state, so markup can't say one thing and look like another.
  • UI upgrade from the CSS original: on phones the original simply hid every link (display:none under 600px) with no way to reach them; a native <details> menu now replaces them with 44px rows.
  • UI upgrade: directional underline (in from the left, out to the right), keyboard focus indicator, frosted sticky header, and muted copy raised from slate-500 to slate-600 (7.5:1).
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS Navbar with Animated Hover Underline
Source: https://codefronts.com/navigation/tailwind-css-navigation-menus/tailwind-css-navbar-animated-hover-underline/

The foundation of every site header, rebuilt in utilities: a sticky, frosted navbar whose links draw an underline with after:scale-x-*. The line grows in from the left and retracts to the right, the active page is styled from aria-current itself, and a native <details> menu takes over below md.
## HTML
```html
<div class="tnv-01 flex min-h-svh w-full flex-col bg-tnv01-bg font-tnv01-sans text-tnv01-ink antialiased selection:bg-tnv01-acc selection:text-white">
  <header class="sticky top-0 z-20 border-b border-tnv01-line bg-white/80 backdrop-blur-md">
    <nav aria-label="Main" class="relative mx-auto flex h-16 max-w-6xl items-center justify-between gap-6 px-5 sm:px-8">
      <a href="#" class="flex items-center gap-2.5 rounded-md text-lg font-semibold tracking-[-0.02em] outline-none focus-visible:ring-2 focus-visible:ring-tnv01-acc/40"><span aria-hidden="true" class="size-2 rounded-full bg-tnv01-acc shadow-[0_0_0_4px_var(--color-tnv01-acc-soft)]"></span>Clarity</a>
      <ul class="hidden items-center gap-0.5 md:flex">
        <li><a href="#" aria-current="page" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Home</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Products</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Features</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Pricing</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">Blog</a></li>
        <li><a href="#" class="relative flex h-11 items-center rounded-md px-3.5 text-sm font-medium text-tnv01-mut outline-none transition-colors duration-200 after:pointer-events-none after:absolute after:inset-x-3.5 after:bottom-1.5 after:h-0.5 after:origin-right after:scale-x-0 after:rounded-full after:bg-tnv01-acc after:transition-transform after:duration-300 after:ease-[cubic-bezier(.34,1.56,.64,1)] after:content-[''] hover:text-tnv01-ink hover:after:origin-left hover:after:scale-x-100 focus-visible:text-tnv01-ink focus-visible:after:origin-left focus-visible:after:scale-x-100 aria-[current=page]:text-tnv01-ink aria-[current=page]:after:scale-x-100 motion-reduce:after:transition-none">About</a></li>
      </ul>
      <div class="flex items-center gap-2.5">
        <a href="#" class="hidden h-10 items-center rounded-lg border border-tnv01-line px-4 text-sm font-medium transition-colors hover:border-slate-300 hover:bg-tnv01-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tnv01-acc sm:inline-flex">Sign in</a>
        <a href="#" class="inline-flex h-10 items-center gap-1.5 rounded-lg bg-tnv01-acc px-4 text-sm font-medium text-white shadow-[0_1px_0_0_rgb(255_255_255/0.2)_inset,0_6px_16px_-6px_var(--color-tnv01-acc)] transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tnv01-acc">Get started</a>
        <details class="group/m md:hidden">
          <summary aria-label="Menu" class="grid size-11 cursor-pointer list-none place-items-center rounded-lg border border-tnv01-line bg-white outline-none focus-visible:ring-2 focus-visible:ring-tnv01-acc/40 [&::-webkit-details-marker]:hidden">
            <svg class="size-5 group-open/m:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h10"/></svg>
            <svg class="hidden size-5 group-open/m:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
          </summary>
          <ul class="absolute inset-x-3 top-[calc(100%+8px)] grid gap-0.5 rounded-2xl border border-tnv01-line bg-white p-2 text-[15px] font-medium shadow-[0_24px_48px_-16px_rgb(15_23_42/0.25)] transition-[opacity,translate] duration-200 starting:-translate-y-1 starting:opacity-0">
            <li><a href="#" aria-current="page" class="flex min-h-11 items-center rounded-lg px-3 aria-[current=page]:bg-tnv01-acc-soft aria-[current=page]:text-tnv01-acc hover:bg-tnv01-bg">Home</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Products</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Features</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Pricing</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">Blog</a></li>
            <li><a href="#" class="flex min-h-11 items-center rounded-lg px-3 hover:bg-tnv01-bg">About</a></li>
          </ul>
        </details>
      </div>
    </nav>
  </header>
  <main class="grid flex-1 place-items-center px-5 py-20 text-center [&_code]:rounded [&_code]:bg-slate-100 [&_code]:px-1.5 [&_code]:py-px [&_code]:font-tnv01-mono [&_code]:text-[0.85em] [&_code]:text-tnv01-ink">
    <div class="grid max-w-2xl justify-items-center">
      <span class="mb-6 inline-flex items-center gap-2 rounded-full bg-tnv01-acc-soft px-3 py-1 text-xs font-semibold tracking-[0.08em] text-tnv01-acc uppercase">Pure Tailwind · zero JS</span>
      <h1 class="text-[clamp(2.25rem,5.5vw,3.75rem)] leading-[1.06] font-semibold tracking-[-0.035em] text-balance">Hover the nav. The underline follows your intent.</h1>
      <p class="mt-5 max-w-xl text-[17px]/[1.65] text-tnv01-mut text-pretty">Each link draws its indicator with <code>after:scale-x-*</code>. It grows in from the left and retracts to the right, and keyboard focus gets exactly the same treatment.</p>
      <div class="mt-10 flex max-w-lg items-start gap-3 rounded-xl border border-tnv01-line bg-white p-4 text-left text-[13px]/[1.55] text-tnv01-mut shadow-[0_1px_2px_rgb(15_23_42/0.04)]">
        <svg class="mt-px size-[18px] flex-none text-tnv01-acc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
        <span>Press <kbd class="rounded border border-tnv01-line bg-tnv01-bg px-1.5 font-tnv01-mono text-[11px]">Tab</kbd> to walk the links. The active page is styled from <code>aria-current</code>, so the visual state and the screen-reader state can never drift apart.</span>
      </div>
    </div>
  </main>
</div>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tnv01-bg: #f8fafc;
  --color-tnv01-bg: oklch(0.984 0.003 248);
  --color-tnv01-line: #e2e8f0;
  --color-tnv01-line: oklch(0.929 0.013 256);
  --color-tnv01-ink: #1e293b;
  --color-tnv01-ink: oklch(0.279 0.041 260);
  --color-tnv01-mut: #475569;
  --color-tnv01-mut: oklch(0.446 0.043 257);
  --color-tnv01-acc: #2563eb;
  --color-tnv01-acc: oklch(0.546 0.245 263);
  --color-tnv01-acc-soft: #dbeafe;
  --color-tnv01-acc-soft: oklch(0.932 0.032 256);
  --font-tnv01-sans: 'Geist', system-ui, sans-serif;
  --font-tnv01-mono: 'Geist Mono', ui-monospace, monospace;
}

.tnv-01 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

One pseudo-element per link, declared on the link. The CSS original kept the underline in a .links a::after rule; here it travels with the anchor:

<a aria-current="page" class="relative after:absolute after:inset-x-3.5 after:bottom-1.5
  after:h-0.5 after:scale-x-0 after:origin-right after:content-['']
  hover:after:origin-left hover:after:scale-x-100
  aria-[current=page]:after:scale-x-100">

Why swap the origin. Flipping transform-origin between rest and hover means the line always travels the same direction, which reads as intent instead of a flicker. scale is compositor-only, so it stays at 60fps on low-end phones.

The mobile menu is native. <details> announces expanded/collapsed for free; group-open/m: swaps the icon and starting: fades the panel in.

Make it yours

  • Centre-grow: replace the origin pair with after:origin-center.
  • Thicker indicator: after:h-[3px] after:bottom-1; pill indicator: after:inset-x-1 after:h-full after:bg-tnv01-acc/10 after:rounded-md.
  • Re-theme: edit --color-tnv01-acc — underline, logo dot, button and focus ring follow.

Gotchas — read before shipping

  • after:content-[''] is required; without it the pseudo-element never renders.
  • Keep the underline inside the link box (bottom-1.5) so the 44px hit area also covers it.
  • <details> does not close on outside click without a few lines of JS; it does close from its own summary and is fully keyboard accessible.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

starting: fades are progressive (Chrome 117 / Safari 17.5 / Firefox 129); older browsers open the menu instantly.

Techniques used in this demo

Search CodeFronts

Loading…