Estilos activos con NavLink
Introducción
En esta clase vamos a mejorar la experiencia de usuario de nuestra navegación añadiendo estilos activos: indicadores visuales que muestran en qué página está el usuario actualmente.
Hasta ahora hemos implementado navegación funcional con React Router, pero nos falta un detalle importante de UX: el usuario no tiene feedback visual de dónde está dentro de la aplicación.
Vamos a solucionar esto usando NavLink, un componente especial de React Router que nos permite aplicar estilos dinámicos según si la ruta está activa o no.
Punto de partida: el problema en el header
Partimos del header de la aplicación, donde ya tenemos un enlace a la página de Empleos dentro de la navegación.
Ahora mismo hay varios problemas de experiencia de usuario:
- Cuando el usuario está en la página de Empleos, el enlace del header sigue siendo clicable
- El usuario no tiene ninguna pista visual de que ya está en esa página
- A nivel de UX, no es ideal dejar que el usuario pulse una y otra vez en la misma ruta sin ningún feedback
Por qué necesitamos estilos activos
Si ya estoy en /jobs (Empleos), volver a navegar a /jobs no tiene mucho sentido.
Es más útil que el usuario vea claramente que ya está en esa sección.
La mayoría de menús de navegación marcan la sección actual con:
- Color de texto distinto
- Subrayado o fondo especial
- Enlace deshabilitado o con menor opacidad
Beneficios de los estilos activos
- Mejor orientación dentro de la app
- Menos clics innecesarios del usuario
- Mejor experiencia en teclado y lectores de pantalla
- Feedback visual claro de la navegación
Presentando NavLink de React Router
Para conseguir estos estilos activos, React Router nos ofrece un componente especial: NavLink.
Características de NavLink
- Es muy parecido a
Link - Usa la prop
toen lugar dehref - Añade una feature clave: sabe si la ruta está activa y nos permite reaccionar a ello
Import típico
import { NavLink } from 'react-router'
Recordatorio importante
Los componentes de React Router no aceptan href.
Siempre hay que usar la prop to para indicar la ruta de destino.
Reemplazando Link por NavLink
En el header teníamos algo similar a esto:
import { Link } from 'react-router'
export function Header() {
return (
<header className="header">
<nav className="header-nav">
<Link to="/jobs" className="header-nav-link">
Empleos
</Link>
</nav>
</header>
)
}
El primer cambio: usar NavLink
Cambiamos Link por NavLink para el enlace de Empleos:
import { NavLink } from 'react-router'
export function Header() {
return (
<header className="header">
<nav className="header-nav">
<NavLink to="/jobs" className="header-nav-link">
Empleos
</NavLink>
</nav>
</header>
)
}
Así ya tenemos el componente correcto para poder trabajar con el estado de “activo”.
className como función: detectando si la ruta está activa
Lo realmente interesante de NavLink es que la prop className puede ser:
- Un string (como siempre)
- O una función que recibe un objeto con información del enlace, entre ella
isActive
Cómo funciona
Esto nos permite saber si la ruta del NavLink coincide con la ruta actual y, en función de ello, devolver unas clases u otras.
Ejemplo base
<NavLink
to="/jobs"
className={({ isActive }) => (isActive ? 'header-nav-link navLinkActive' : 'header-nav-link')}
>
Empleos
</NavLink>
Aquí:
isActiveserátruecuando la URL actual coincida con/jobs- En ese caso añadimos la clase extra
navLinkActive - Si no está activa, solo dejamos la clase normal
header-nav-link
Esto nos da control total desde CSS.
Lo que añade NavLink automáticamente
Cuando inspeccionamos el DOM en las devtools, vemos que React Router hace varias cosas por nosotros de forma automática:
- Añade la clase que hemos definido, por ejemplo
navLinkActive - Añade atributos de accesibilidad como
aria-current="page"cuando el enlace está activo
Ejemplo de HTML resultante
Cuando la ruta está activa:
<a class="header-nav-link navLinkActive" aria-current="page" href="/jobs"> Empleos </a>
Ese aria-current="page" es muy útil para lectores de pantalla, ya que indica que ese enlace representa la página actual.
Estilos básicos para resaltar la ruta activa
Ahora que tenemos la clase navLinkActive aplicándose dinámicamente, podemos definir estilos específicos para la ruta activa.
Primera prueba con color
En tu archivo de estilos globales (por ejemplo index.css):
.header-nav-link.navLinkActive {
color: yellow;
}
Con esto ya se ve claramente:
- Cuando estás en la página de Empleos, el enlace aparece en amarillo
- Si navegas a otra página (por ejemplo la home), el color vuelve al normal
Es la forma más directa de explicar visualmente el concepto de estilo activo.
Deshabilitar el enlace cuando ya estás en esa ruta
Siguiente mejora: si ya estoy en /jobs, ¿tiene sentido que pueda volver a hacer clic en “Empleos”?
Podemos usar CSS para:
- Evitar que el usuario pueda hacer clic
- Indicar visualmente que es el elemento actual (por ejemplo con menor opacidad)
Estilos completos
.header-nav-link.navLinkActive {
color: yellow;
pointer-events: none; /* Desactiva el clic */
opacity: 0.6; /* Se ve un poco más apagado */
user-select: none; /* Evita seleccionar el texto */
}
Con esto conseguimos:
- El enlace de la ruta actual ya no es clicable
- Visualmente se distingue del resto
- Se refuerza la sensación de “ya estás aquí”
Variaciones de estilos
Puedes complementar con más estilos según tu diseño:
.header-nav-link.navLinkActive {
font-weight: 600;
text-decoration: underline;
border-bottom: 2px solid currentColor;
}
La idea es que NavLink solo te da la información, y tú decides cómo usarla en estilos.
Experimentando con los cambios
Durante la implementación puedes probar varios cambios para ver cómo funciona:
- Ver cómo cambia el HTML cuando pasas de la home a la página de empleos
- Observar cómo aparece y desaparece la clase activa
- Ver cómo se añade y se quita el atributo
aria-current - Probar estilos llamativos (como el color amarillo) para que el cambio sea súper evidente
- Demostrar cómo, con
pointer-events: none, deja de poder hacerse clic en el enlace de la página actual
Todo esto ayuda a afianzar la idea de que los estilos activos son dinámicos y dependen del estado de la ruta.
Patrón de abstracción con NavLink
Si has seguido el patrón de abstracción que vimos en clases anteriores con Link y useRouter, puedes aplicar lo mismo con NavLink:
Crear tu propio componente NavLink
// components/NavLink.jsx
import { NavLink as RRNavLink } from 'react-router'
export function NavLink(props) {
return <RRNavLink {...props} />
}
Así:
- Toda tu app usa tu componente
<NavLink> - Si mañana quieres cambiar de librería de routing, solo cambias este archivo
- No tienes que revisar todos los componentes de navegación uno por uno
Este patrón es especialmente útil si quieres:
- Añadir lógica común a todos los enlaces de navegación
- Customizar el comportamiento por defecto
- Mantener una API consistente en toda tu aplicación
Comparación: Link vs NavLink
Cuándo usar Link
- Para enlaces normales que no necesitan indicar estado activo
- Botones o enlaces dentro del contenido
- Enlaces que no forman parte de la navegación principal
Cuándo usar NavLink
- Menús de navegación donde quieres mostrar la página actual
- Tabs o pestañas donde el usuario necesita ver cuál está activa
- Cualquier lista de enlaces donde importa indicar “estás aquí”
Tabla comparativa
| Característica | Link | NavLink |
|---|---|---|
| Navegación básica | ✅ | ✅ |
Prop to | ✅ | ✅ |
| Detecta ruta activa | ❌ | ✅ |
className función | ❌ | ✅ |
aria-current auto | ❌ | ✅ |
| Uso en menús | Posible | Recomendado |
| Estilos activos | Manual | Automático con callback |
Errores comunes con NavLink
Usar href en lugar de to
{
/* ❌ Incorrecto */
}
;<NavLink href="/jobs">Empleos</NavLink>
{
/* ✅ Correcto */
}
;<NavLink to="/jobs">Empleos</NavLink>
No usar la función en className
{
/* ❌ Incorrecto - no aprovecha isActive */
}
;<NavLink to="/jobs" className="header-nav-link">
Empleos
</NavLink>
{
/* ✅ Correcto */
}
;<NavLink
to="/jobs"
className={({ isActive }) => (isActive ? 'header-nav-link navLinkActive' : 'header-nav-link')}
>
Empleos
</NavLink>
Olvidar el selector de especificidad en CSS
/* ❌ Incorrecto - puede no tener suficiente especificidad */
.navLinkActive {
color: yellow;
}
/* ✅ Correcto - mayor especificidad */
.header-nav-link.navLinkActive {
color: yellow;
}
Casos de uso avanzados
Navegación con múltiples niveles
Si tienes subrutas, NavLink también puede detectarlas:
<NavLink
to="/jobs"
className={({ isActive }) => (isActive ? 'header-nav-link navLinkActive' : 'header-nav-link')}
>
Empleos
</NavLink>
Por defecto, /jobs estará activo tanto en /jobs como en /jobs/123.
Desactivar matching de subrutas
Si quieres que solo coincida exactamente:
<NavLink
to="/jobs"
end
className={({ isActive }) => (isActive ? 'header-nav-link navLinkActive' : 'header-nav-link')}
>
Empleos
</NavLink>
La prop end hace que solo coincida exactamente con /jobs, no con /jobs/123.
Usar style en lugar de className
También puedes usar style como función:
<NavLink
to="/jobs"
style={({ isActive }) => ({
color: isActive ? 'yellow' : 'white',
fontWeight: isActive ? 'bold' : 'normal',
})}
>
Empleos
</NavLink>
Aunque generalmente es mejor usar className por separación de responsabilidades.
Navegación y accesibilidad
Los estilos activos no son solo una cuestión estética, también mejoran significativamente la accesibilidad:
Beneficios para usuarios con lectores de pantalla
- El atributo
aria-current="page"indica claramente qué enlace representa la página actual - Los usuarios de lectores de pantalla reciben feedback audible sobre su ubicación
- Mejora la navegación por teclado al poder identificar la página actual
Beneficios para usuarios con visión reducida
- El contraste visual claro ayuda a orientarse
- Los estilos distintivos (subrayado, color, peso) son más fáciles de identificar
- Reduce la carga cognitiva al navegar
Buenas prácticas de accesibilidad
<NavLink
to="/jobs"
className={({ isActive }) => (isActive ? 'header-nav-link navLinkActive' : 'header-nav-link')}
aria-label="Ir a empleos"
>
Empleos
</NavLink>
React Router añade automáticamente aria-current="page" cuando está activo, así que no necesitas añadirlo manualmente.
Resumen
En esta clase hemos aprendido:
NavLinkes comoLink, pero sabe si su ruta está activa- La prop
classNamepuede ser una función que recibeisActive - Con
isActivepodemos devolver clases distintas y aplicar estilos activos - React Router añade
aria-current="page"automáticamente, mejorando la accesibilidad - Con CSS podemos:
- Cambiar color, peso, subrayado
- Desactivar clic con
pointer-events: none - Indicar visualmente que es la página actual
- Podemos aplicar el patrón de abstracción también con
NavLink - Los estilos activos mejoran tanto la UX como la accesibilidad
Con esto conseguimos: navegación más clara, accesible y con mejor experiencia para el usuario.