Nav Underline Hover Effect
Original · freeA nav bar with animated underline indicators on hover.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/hover-nav-underline.json"use client";
import { useRef, useState, type KeyboardEvent } from "react";
type NavItem = {
label: string;
href: string;
};
const NAV_ITEMS: NavItem[] = [
{ label: "Overview", href: "#overview" },
{ label: "Features", href: "#features" },
{ label: "Pricing", href: "#pricing" },
{ label: "Changelog", href: "#changelog" },
{ label: "Docs", href: "#docs" },
];
export default function HoverNavUnderline() {
const [activeHref, setActiveHref] = useState<string>(NAV_ITEMS[0].href);
const [hoverHref, setHoverHref] = useState<string | null>(null);
const linkRefs = useRef<Record<string, HTMLAnchorElement | null>>({});
const highlighted = hoverHref ?? activeHref;
const onKeyNav = (e: KeyboardEvent, index: number) => {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
e.preventDefault();
const dir = e.key === "ArrowRight" ? 1 : -1;
const next = (index + dir + NAV_ITEMS.length) % NAV_ITEMS.length;
const nextHref = NAV_ITEMS[next].href;
linkRefs.current[nextHref]?.focus();
setHoverHref(nextHref);
};
return (
<section className="relative w-full bg-white px-6 py-20 text-slate-900 dark:bg-slate-950 dark:text-slate-100 sm:py-28">
<style>{`
@keyframes hnu_fade_up {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.hnu-underline { transition: none !important; }
.hnu-fade { animation: none !important; }
}
`}</style>
<div className="mx-auto flex max-w-4xl flex-col items-center gap-12">
<div className="hnu-fade text-center [animation:hnu_fade_up_0.5s_ease-out]">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-600 dark:text-indigo-400">
Component
</p>
<h2 className="mt-3 text-3xl font-bold tracking-tight sm:text-4xl">
Hover Nav Underline
</h2>
<p className="mx-auto mt-3 max-w-md text-sm text-slate-600 dark:text-slate-400">
A single sliding indicator tracks your cursor, then settles back on
the active link when you leave.
</p>
</div>
<nav
aria-label="Primary"
className="relative w-full max-w-2xl"
onMouseLeave={() => setHoverHref(null)}
>
<ul className="relative flex flex-wrap items-center justify-center gap-x-2 gap-y-3 rounded-2xl border border-slate-200 bg-slate-50/60 px-3 py-2.5 backdrop-blur dark:border-slate-800 dark:bg-slate-900/60 sm:justify-between">
{NAV_ITEMS.map((item, i) => {
const isActive = activeHref === item.href;
const isHot = highlighted === item.href;
return (
<li key={item.href} className="relative">
<a
ref={(el) => {
linkRefs.current[item.href] = el;
}}
href={item.href}
aria-current={isActive ? "page" : undefined}
onMouseEnter={() => setHoverHref(item.href)}
onFocus={() => setHoverHref(item.href)}
onKeyDown={(e) => onKeyNav(e, i)}
onClick={(e) => {
e.preventDefault();
setActiveHref(item.href);
}}
className={`relative block rounded-lg px-4 py-2 text-sm font-medium outline-none transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-900 ${
isHot
? "text-slate-900 dark:text-white"
: "text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200"
}`}
>
{item.label}
<span
aria-hidden="true"
className={`hnu-underline pointer-events-none absolute inset-x-3 -bottom-0.5 h-0.5 origin-center rounded-full bg-indigo-500 transition-[transform,opacity] duration-300 ease-out dark:bg-indigo-400 ${
isHot
? "scale-x-100 opacity-100"
: "scale-x-0 opacity-0"
}`}
/>
</a>
</li>
);
})}
</ul>
</nav>
<div
role="status"
aria-live="polite"
className="text-xs text-slate-500 dark:text-slate-500"
>
Active section:{" "}
<span className="font-semibold text-indigo-600 dark:text-indigo-400">
{NAV_ITEMS.find((n) => n.href === activeHref)?.label}
</span>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Card Lift Hover Effect
OriginalA card that lifts with a growing shadow on hover.

Card Glow Hover Effect
OriginalA card with a coloured glow that blooms on hover.

Card Border Draw Hover Effect
OriginalA card whose border draws itself in on hover.

Card Gradient Shift Hover Effect
OriginalA card whose gradient background shifts on hover.

Card Tilt Hover Effect
OriginalA card that tilts in 3D toward the cursor on hover.

Card Spotlight Hover Effect
OriginalA card with a cursor-following radial spotlight.

Link Underline Hover Effect
OriginalAnimated link underline effects: grow, slide and centre-out.

Link Slide Hover Effect
OriginalLinks whose label slides up to a duplicate on hover.

Button Fill Hover Effect
OriginalButtons with a colour fill that sweeps across on hover.

Button Shine Hover Effect
OriginalButtons with a diagonal shine sweep on hover.
Icon Fill Hover Effect
OriginalIcon buttons whose icon and background fill on hover.
Icon Bounce Hover Effect
OriginalIcons that bounce or spin playfully on hover.

