oward

NavLink

Composant wrapper de lien de navigation qui détecte et met en évidence l'état actif en fonction de l'URL courante.

newbeta

Installation

npx shadcn@latest add @oward/navlink

Introduction

Dans la plupart des applications React, gérer l'état actif des liens de navigation nécessite d'utiliser des hooks comme usePathname() ou useLocation() dans chaque composant, puis d'écrire de la logique de comparaison d'URL pour appliquer les styles appropriés. Cela devient vite répétitif et difficile à maintenir.

NavLink résout ce problème en centralisant toute cette logique dans un composant réutilisable. Il détecte automatiquement si le lien correspond à l'URL actuelle et applique les classes CSS appropriées, sans que vous ayez à écrire de hooks ou de logique conditionnelle.

La détection se fait côté client après le rendu, ce qui permet de fonctionner correctement avec des composants comme Link de next-intl ou tout autre système qui réécrit les URLs. Le composant analyse l'URL finale rendue dans le DOM, garantissant ainsi une correspondance précise quelle que soit la bibliothèque de routing utilisée.

Cela améliore considérablement l'expérience développeur tout en garantissant la cohérence du comportement de navigation dans toute votre application.

Fonctionnalités

  • SSR et SSG compatibles : Fonctionne parfaitement avec le rendu côté serveur et le rendu statique.
  • Supporte tous les systèmes de routing : Compatible avec Next.js, React Router, Next-intl...
  • Modes de correspondance flexibles : "auto", "exact" ou boolean pour un contrôle total.
  • Options avancées : Inclure le hash et les paramètres de requête dans la correspondance.
  • Accessibilité intégrée : Ajoute automatiquement aria-current="page" pour une meilleure accessibilité.
  • Optimisé pour la performance : Utilise la Navigation API

Utilisation

Le composant NavLink enveloppe n'importe quel élément de lien (Link de Next.js, React Router, etc.) et applique automatiquement une classe CSS lorsque l'URL correspond au lien.

Exemple de base

import { NavLink } from "@/ui/navlink";
import Link from "next/link";

export default function Navigation() {
  return (
    <nav className="flex gap-4">
      <NavLink currentClassName="text-blue-600 font-semibold">
        <Link href="/about">À propos</Link>
      </NavLink>
      <NavLink currentClassName="text-blue-600 font-semibold">
        <Link href="/contact">Contact</Link>
      </NavLink>
    </nav>
  );
}

Props

PropTypeDéfautDescription
current"auto" | "exact" | boolean"auto"Mode de correspondance de l'URL
currentClassNamestring-Classe CSS à appliquer lorsque le lien est actif
matchHashbooleanfalseInclure le hash dans la correspondance d'URL
matchQuerybooleanfalseInclure les paramètres de requête dans la correspondance d'URL
classNamestring-Classes CSS supplémentaires à appliquer

Modes de correspondance

Mode "auto" (par défaut)

Correspond si l'URL actuelle commence par le href du lien.

<NavLink current="auto" currentClassName="text-blue-600">
  <Link href="/docs">Documentation</Link>
</NavLink>
URL actuellehref du lienActif ?
/docs/docs✅ Oui
/docs/installation/docs✅ Oui
/documentation/docs❌ Non
//✅ Oui
/home/✅ Oui
/about/✅ Oui

Mode "exact"

Correspond uniquement si l'URL actuelle est exactement identique au href du lien.

<NavLink current="exact" currentClassName="text-blue-600">
  <Link href="/docs">Documentation</Link>
</NavLink>
URL actuellehref du lienActif ?
/docs/docs✅ Oui
/docs/installation/docs❌ Non
/docs?tab=api/docs❌ Non

Mode boolean

Force l'état actif/inactif manuellement, ignorant l'URL.

<NavLink current={isActive} currentClassName="text-blue-600">
  <Link href="/profile">Profil</Link>
</NavLink>

Options avancées

Inclure le hash dans la correspondance

Cas d'usage

Par défaut, le hash est ignoré. Activez cette option si vous voulez que chaque ancre soit considérée comme une route distincte.

<NavLink matchHash currentClassName="font-bold">
  <Link href="/docs#api">Documentation</Link>
</NavLink>
URL actuellehref du lienPar défaut (matchHash=false)matchHash={true}
/docs#api/docs#api✅ Actif✅ Actif
/docs#intro/docs#api✅ Actif❌ Inactif

Inclure les paramètres de requête dans la correspondance

Cas d'usage

Par défaut, les paramètres de requête sont ignorés. Activez cette option si vous voulez que chaque combinaison de paramètres soit considérée comme une route distincte.

<NavLink matchQuery currentClassName="font-bold">
  <Link href="/products">Produits</Link>
</NavLink>
URL actuellehref du lienPar défaut (matchQuery=false)matchQuery={true}
/products/products✅ Actif✅ Actif
/products?category=shoes/products✅ Actif❌ Inactif

Accessibilité

Le composant ajoute automatiquement l'attribut aria-current="page" lorsque le lien est actif, conformément aux standards WCAG 2.1.

// Rendu HTML lorsque actif
<a href="/about" aria-current="page" className="text-blue-600">
  À propos
</a>

Performance

Optimisations de performance

Le composant utilise un système de suivi de localisation global partagé entre toutes les instances de NavLink pour minimiser les re-renders :

  • Sur les navigateurs modernes, il utilise la Navigation API .
  • Sur les anciens navigateurs, un polling léger (250ms) est utilisé en fallback. Ce fallback sera supprimé dans une future version.

Exemples pratiques

Composition avec asChild

NavLink est compatible avec le pattern asChild de Radix UI. Vous pouvez l'envelopper dans des composants comme Button, DropdownMenuItem, ou tout autre composant supportant asChild pour combiner leurs styles et comportements.

import { NavLink } from "@/ui/navlink";
import { Button } from "@/ui/button";
import Link from "next/link";

export function MainNav() {
  return (
    <nav className="flex gap-6">
      <Button asChild variant="ghost">
        <NavLink currentClassName="text-primary font-semibold border-b-2 border-primary">
          <Link href="/">Accueil</Link>
        </NavLink>
      </Button>
      <Button asChild variant="ghost">
        <NavLink currentClassName="text-primary font-semibold border-b-2 border-primary">
          <Link href="/contact">Contact</Link>
        </NavLink>
      </Button>
    </nav>
  );
}

Limitation

Le composant enfant de NavLink doit être un élément React valide avec une prop href. En mode développement, une erreur sera levée si cette condition n'est pas respectée.