Skip to content
WANCRAFT

D.168Navigation & Menus

Hover Highlight Nav

Nav links with one highlight pill that springs to the hovered or focused link and rests on the active one.

NewReactMotionTailwindintermediate

Overview

Hover Highlight Nav gives a row or column of links a single shared highlight. Hover a link, or Tab to it, and the pill springs over to it; move the pointer away and it glides back to the active link (or fades out, if you prefer). A separate accent underline, or a side bar in vertical navs, marks the active page and follows it when it changes. Link boxes are measured once and again on resize, so hovering only starts springs on motion values: no layout reads and no React renders per pointer move. Touch input never moves the pill, it simply follows the active link, and the active link carries aria-current="page".

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/hover-highlight-nav.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/hover-highlight-nav.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/hover-highlight-nav.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { HoverHighlightNav } from "@/components/ui/hover-highlight-nav";

export function HoverHighlightNavExample() {
  const links = [
    { id: "overview", label: "Overview", href: "/" },
    { id: "components", label: "Components", href: "/components" },
    { id: "pricing", label: "Pricing", href: "/pricing" },
    { id: "docs", label: "Docs", href: "/docs" },
  ];

  return (
    <HoverHighlightNav
      items={links}
      defaultActiveId="components"
      label="Main"
    />
  );
}

03Props

PropTypeDefaultDescription
itemsHoverHighlightNavItem[]—Required. { id, label, href?, icon?, badge? }. Items with href render <a>, others <button>.
activeIdstring—Active item id (controlled), e.g. derived from the current route.
defaultActiveIdstring—Initially active item id (uncontrolled). Defaults to the first item.
onActiveChange(id: string, event: MouseEvent) => void—Called on click. Call event.preventDefault() for client-side routing.
orientation"horizontal" | "vertical""horizontal"Row of links or a full-width column.
variant"soft" | "outline" | "tint""soft"Highlight pill style.
showUnderlinebooleantrueAccent underline (side bar when vertical) under the active link.
returnToActivebooleantrueWhen the pointer leaves, the pill rests on the active link; when false it fades out.
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 420, damping: 34, mass: 0.6 }Spring for the pill and the underline.
accentstring"#FF7448"Underline, focus ring and tint colour.
labelstring"Main"Accessible name of the <nav> landmark.
...propsHTMLAttributes<HTMLElement>—Forwarded to the <nav>.

04Features

  • 01One shared pill that springs between hovered and focused links
  • 02Rests on the active link when the pointer leaves, or fades out
  • 03Separate active underline / side bar that follows route changes
  • 04Measured offsets + motion values: no layout reads or renders per hover
  • 05Horizontal and vertical orientations with icons and badges
  • 06Touch-friendly: no hover state on touch, active-only highlight
motion · springinput · hoverinput · focusinput · clickinput · keyboard

05Accessibility

  • A labelled <nav> landmark with a list of real links (or buttons when there is no href); the active one has aria-current="page".
  • Keyboard focus (focus-visible) moves the pill like hover does, and every link has a visible focus ring.
  • Links are reached with Tab in DOM order; nothing is hidden behind hover.
  • The pill and underline are decorative (aria-hidden) and never intercept pointer events.
  • With prefers-reduced-motion the pill and underline jump instead of springing.