Ver contenido
Guía de Implementación de Navegación Consistente: Construyendo Sistemas de Navegación Predecibles
Introducción
La navegación consistente es un requisito de accesibilidad de nivel AA definido en WCAG 2.2 bajo el criterio de éxito 3.2.3. Este criterio establece que los mecanismos de navegación que se repiten en múltiples páginas dentro de un sitio web deben aparecer en el mismo orden relativo cada vez que se presentan, a menos que el usuario inicie un cambio.
Cuando los usuarios navegan un sitio web, desarrollan un modelo mental de dónde encontrar cada elemento. Un menú que cambia de orden entre páginas obliga al usuario a re-aprender la interfaz en cada visita, lo que resulta especialmente problemático para personas con discapacidades cognitivas, usuarios de lectores de pantalla que memorizan la estructura, y personas con discapacidades motoras que dependen de la memoria muscular para navegar eficientemente.
Implementar navegación consistente requiere decisiones arquitectónicas que centralicen la definición de menús, garanticen el mismo orden en todas las páginas, y mantengan la posición visual estable independientemente del contenido de cada página.
Por qué importa la consistencia en la navegación
El impacto de la navegación inconsistente va más allá del cumplimiento normativo. Según estudios de usabilidad, los usuarios tardan entre un 40% y un 60% más en completar tareas cuando la navegación cambia de posición o de orden entre páginas. Para usuarios de tecnologías asistivas, el impacto es aún mayor: un lector de pantalla recorre los elementos en orden secuencial, y si el menú cambia de estructura, el usuario pierde su punto de referencia.
Los errores más comunes que rompen la consistencia incluyen:
- Reordenamiento dinámico: Algoritmos que reordenan los elementos del menú según popularidad o contexto de la página actual.
- Ocultamiento selectivo sin mantener orden: Eliminar elementos del menú en ciertas páginas, lo que altera la posición relativa de los elementos restantes.
- Navegación diferente entre móvil y escritorio: Presentar los elementos en distinto orden en el menú hamburguesa respecto al menú de escritorio.
- Inyección de elementos dinámicos: Agregar elementos promocionales o contextuales que desplazan los ítems existentes.
La regla fundamental es clara: los elementos pueden ocultarse, pero los que permanecen visibles deben conservar siempre el mismo orden relativo.
Patrón de fuente única de verdad
La base de una navegación consistente es definir la estructura del menú en un único lugar y que todos los componentes de la interfaz la consuman desde esa fuente centralizada.
Definición central de la navegación
Este patrón utiliza un archivo de configuración que actúa como fuente canónica. Cualquier cambio en la estructura de navegación se realiza exclusivamente en este archivo, garantizando que todas las páginas reflejen la misma estructura.
// navigation.config.ts - Fuente única de verdad
export interface NavItem {
id: string;
label: string;
href: string;
icon?: string;
children?: NavItem[];
requiresAuth?: boolean;
roles?: string[];
}
export const NAVEGACION_PRINCIPAL: NavItem[] = [
{ id: 'inicio', label: 'Inicio', href: '/' },
{
id: 'productos',
label: 'Productos',
href: '/productos',
children: [
{ id: 'electronica', label: 'Electrónica', href: '/productos/electronica' },
{ id: 'ropa', label: 'Ropa', href: '/productos/ropa' },
{ id: 'accesorios', label: 'Accesorios', href: '/productos/accesorios' }
]
},
{ id: 'nosotros', label: 'Nosotros', href: '/nosotros' },
{ id: 'contacto', label: 'Contacto', href: '/contacto' }
];
El aspecto más importante de este patrón es que el array define el orden canónico. Ningún componente debe reordenar estos elementos. Los componentes pueden filtrar (ocultar elementos según autenticación o roles), pero el método filter() de JavaScript preserva el orden original del array, lo que garantiza consistencia automáticamente.
Servicio de navegación con filtrado seguro
El servicio de navegación encapsula la lógica de filtrado, asegurando que nunca se reordenen los elementos:
export class NavigationService {
// Filtra por autorización pero NUNCA reordena
getNavegacionPrincipal(rolesUsuario: string[] = []): NavItem[] {
return this.filtrarPorAutorizacion(NAVEGACION_PRINCIPAL, rolesUsuario);
}
private filtrarPorAutorizacion(items: NavItem[], roles: string[]): NavItem[] {
return items.filter(item => {
if (!item.roles) return true;
return item.roles.some(role => roles.includes(role));
}).map(item => ({
...item,
children: item.children
? this.filtrarPorAutorizacion(item.children, roles)
: undefined
}));
}
}
Observa que se usa filter() y map(), nunca sort(). Esta restricción es intencional: si un desarrollador necesita cambiar el orden, debe modificar el array fuente en navigation.config.ts, no la lógica de filtrado.
Implementación del componente de navegación
Marcado semántico correcto
El elemento <nav> con aria-label descriptivo es fundamental para que los lectores de pantalla identifiquen las regiones de navegación. Cuando un sitio tiene múltiples bloques de navegación (principal, utilidades, footer), cada uno necesita un aria-label único para diferenciarse.
export function NavegacionPrincipal() {
const { navPrincipal, rutaActual } = useNavigation();
return (
<nav aria-label="Navegación principal">
<ul role="menubar">
{navPrincipal.map(item => (
<li key={item.id} role="none">
<a
href={item.href}
role="menuitem"
aria-current={item.href === rutaActual ? 'page' : undefined}
>
{item.label}
</a>
</li>
))}
</ul>
</nav>
);
}
Puntos clave del marcado:
aria-current="page"indica visualmente y a tecnologías asistivas cuál es la página activa, sin necesidad de alterar la estructura del menú.role="menubar"yrole="menuitem"proporcionan semántica de menú que los lectores de pantalla reconocen como patrón de navegación.role="none"en los<li>elimina la semántica de lista para que el lector de pantalla trate los enlaces directamente como elementos de menú.
Consistencia entre escritorio y móvil
Uno de los errores más frecuentes es presentar los elementos de navegación en distinto orden en la versión móvil. El menú hamburguesa debe contener exactamente los mismos elementos en el mismo orden que la navegación de escritorio. Solo cambia la presentación visual, nunca el contenido ni la secuencia.
export function useResponsiveNavigation() {
const [esMovil, setEsMovil] = useState(false);
const [menuAbierto, setMenuAbierto] = useState(false);
// CRÍTICO: Mismos elementos, mismo orden en ambas versiones
// La variable 'navegacion' es siempre NAVEGACION_PRINCIPAL
const navegacion = NAVEGACION_PRINCIPAL;
return { esMovil, menuAbierto, navegacion };
}
El botón del menú móvil debe implementar correctamente los atributos de accesibilidad:
aria-expandedindica si el menú está abierto o cerrado.aria-controlsvincula el botón con el panel de navegación que controla.aria-labelproporciona un nombre accesible que cambia según el estado (“Abrir menú” / “Cerrar menú”).
CSS para posición consistente
La posición visual del menú también debe ser consistente. Un menú que aparece en la parte superior en unas páginas y en el lateral en otras viola el espíritu del criterio 3.2.3. Usa position: sticky para anclar la navegación y CSS order para mantener la disposición predecible:
.header-sitio {
position: sticky;
top: 0;
z-index: 100;
background: white;
border-bottom: 1px solid #e5e5e5;
}
/* Navegación siempre en la misma posición relativa */
.nav-principal { order: 2; }
.nav-utilidades { order: 3; }
/* Móvil: cambiar layout pero mantener orden interno */
@media (max-width: 767px) {
.nav-principal .nav-lista {
flex-direction: column;
}
}
Pruebas automatizadas de consistencia
La navegación consistente es uno de los criterios de accesibilidad más fáciles de automatizar. Las pruebas deben verificar tres aspectos: mismo orden de elementos, misma posición visual, y correspondencia entre versión escritorio y móvil.
test('el orden de navegación es idéntico en todas las páginas', async ({ page }) => {
const paginas = ['/', '/productos', '/nosotros', '/contacto'];
const ordenes = [];
for (const url of paginas) {
await page.goto(url);
const items = await page
.locator('nav[aria-label="Navegación principal"] a')
.allTextContents();
ordenes.push(items);
}
// Comparar cada página con la primera
for (let i = 1; i < ordenes.length; i++) {
expect(ordenes[i]).toEqual(ordenes[0]);
}
});
test('navegación móvil tiene el mismo orden que escritorio', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
await page.goto('/');
const navEscritorio = await page
.locator('nav[aria-label="Navegación principal"] a')
.allTextContents();
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
const botonMenu = page.locator('[aria-controls="menu-movil"]');
if (await botonMenu.isVisible()) await botonMenu.click();
const navMovil = await page
.locator('nav[aria-label="Navegación principal"] a')
.allTextContents();
expect(navMovil).toEqual(navEscritorio);
});
Estas pruebas con Playwright pueden integrarse en el pipeline de CI/CD para detectar regresiones automáticamente cada vez que se modifica la estructura de navegación.
Lista de verificación
- [ ] Navegación definida en una fuente única de verdad (archivo de configuración centralizado)
- [ ] Mismos elementos de navegación presentes en todas las páginas
- [ ] Mismo orden relativo mantenido en todas las páginas
- [ ] Posición visual de la navegación consistente entre páginas
- [ ] Navegación móvil con el mismo orden que la versión de escritorio
- [ ] Solo cambios iniciados por el usuario alteran la navegación
- [ ] Cada bloque
<nav>tiene unaria-labeldescriptivo y único - [ ]
aria-current="page"marca la página activa - [ ] Pruebas automatizadas verifican consistencia de orden y posición
Preguntas frecuentes
¿Puedo ocultar elementos del menú según el rol del usuario?
Sí. Ocultar elementos es aceptable siempre que los elementos restantes mantengan su orden relativo original. Usa filter() en lugar de sort() para garantizar que el orden del array fuente se preserva.
¿El criterio 3.2.3 aplica al footer y navegación secundaria?
Sí. El criterio se aplica a cualquier mecanismo de navegación que se repita en múltiples páginas, incluyendo navegación principal, navegación de utilidades, breadcrumbs, y enlaces del footer.
¿Qué pasa si mi menú móvil agrupa los elementos de forma diferente?
Agrupar visualmente está permitido (por ejemplo, acordeones), pero el orden de los elementos dentro de cada grupo y entre grupos debe coincidir con la versión de escritorio. El contenido y la secuencia no deben cambiar; solo la presentación visual.
¿Cómo manejo elementos de navegación que solo aparecen en ciertas secciones?
Los submenús contextuales (como una barra lateral de sección) son aceptables siempre que la navegación principal global mantenga su consistencia. Los elementos contextuales deben ser claramente diferenciados mediante aria-label distinto.
Artículos relacionados
- Navegación Consistente Explicado - Requisitos WCAG 3.2.3
- Guía de Navegación por Teclado - Soporte completo de teclado
- Hub de Cumplimiento WCAG - Todos los evaluadores de accesibilidad