Descripción general básica sobre cómo crear un componente de rutas de navegación adaptable y accesible para que los usuarios naveguen por tu sitio.
En esta publicación, quiero compartir ideas sobre cómo crear componentes de rutas de navegación. Prueba la demostración.
Si prefieres un video, aquí tienes una versión de este artículo en YouTube:
Descripción general
Un componente de ruta de navegación muestra en qué parte de la jerarquía del sitio se encuentra el usuario. El nombre proviene de Hansel y Gretel, quienes dejaron migas de pan detrás de ellos en un bosque oscuro y pudieron encontrar el camino a casa siguiendo las migas hacia atrás.
Las rutas de navegación de esta publicación no son rutas de navegación estándar, sino que se parecen a ellas. Ofrecen funcionalidad adicional al colocar páginas secundarias directamente en la navegación con un <select>, lo que permite el acceso de varios niveles.
UX de segundo plano
En el video de demostración del componente anterior, las categorías de marcador de posición son géneros de videojuegos. Este registro se crea navegando por la siguiente ruta: home »
rpg » indie » on sale, como se muestra a continuación.
Este componente de rutas de navegación debe permitir que los usuarios se desplacen por esta jerarquía de información, salten de una rama a otra y seleccionen páginas con rapidez y precisión.
Arquitectura de la información
Creo que es útil pensar en términos de colecciones y elementos.
Colecciones
Una colección es un array de opciones para elegir. En la página principal del prototipo de ruta de navegación de esta publicación, las colecciones son FPS, RPG, brawler, dungeon crawler, deportes y rompecabezas.
Elementos
Un videojuego es un elemento, y una colección específica también podría ser un elemento si representa otra colección. Por ejemplo, RPG es un elemento y una colección válida. Cuando es un elemento, el usuario se encuentra en la página de la colección. Por ejemplo, se encuentran en la página de RPG, que muestra una lista de juegos de rol, incluidas las subcategorías adicionales AAA, Indie y Autoeditado.
En términos de informática, este componente de rutas de navegación representa un array multidimensional:
const rawBreadcrumbData = {
"FPS": {...},
"RPG": {
"AAA": {...},
"indie": {
"new": {...},
"on sale": {...},
"under 5": {...},
},
"self published": {...},
},
"brawler": {...},
"dungeon crawler": {...},
"sports": {...},
"puzzle": {...},
}
Tu app o sitio web tendrá una arquitectura de la información (IA) personalizada que creará un array multidimensional diferente, pero espero que el concepto de páginas de destino de la colección y el recorrido de la jerarquía también puedan incluirse en tu ruta de navegación.
Diseños
Marca
Los buenos componentes comienzan con un código HTML adecuado. En la siguiente sección, explicaré mis opciones de marcado y cómo afectan al componente general.
Esquema claro y oscuro
<meta name="color-scheme" content="dark light">
La etiqueta meta color-scheme del fragmento anterior informa al navegador que esta página desea los estilos de navegador claros y oscuros. Las rutas de navegación de ejemplo no incluyen CSS para estos esquemas de color, por lo que usarán los colores predeterminados que proporciona el navegador.
Elemento de navegación
<nav class="breadcrumbs" role="navigation"></nav>
Es adecuado usar el elemento <nav> para la navegación del sitio, que tiene un rol de navegación de ARIA implícito.
En las pruebas, noté que tener el atributo role cambiaba la forma en que un lector de pantalla interactuaba con el elemento. De hecho, se anunciaba como navegación, por lo que decidí agregarlo.
Íconos
Cuando un ícono se repite en una página, el elemento <use> SVG significa que puedes definir el path una vez y usarlo para todas las instancias del ícono. Esto evita que se repita la misma información de ruta, lo que genera documentos más grandes y la posibilidad de que haya incoherencias en la ruta.
Para usar esta técnica, agrega un elemento SVG oculto a la página y envuelve los íconos en un elemento <symbol> con un ID único:
<svg style="display: none;">
<symbol id="icon-home">
<title>A home icon</title>
<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</symbol>
<symbol id="icon-dropdown-arrow">
<title>A down arrow</title>
<path d="M19 9l-7 7-7-7"/>
</symbol>
</svg>
El navegador lee el código HTML de SVG, coloca la información del ícono en la memoria y continúa con el resto de la página haciendo referencia al ID para usos adicionales del ícono, de la siguiente manera:
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<use href="#icon-home" />
</svg>
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<use href="#icon-dropdown-arrow" />
</svg>

Definir una vez y usar tantas veces como quieras, con un impacto mínimo en el rendimiento de la página y un diseño flexible Observa que se agregó aria-hidden="true" al elemento SVG.
Los íconos no son útiles para las personas que solo escuchan el contenido, por lo que ocultarlos evita que se agregue información innecesaria.
Vínculo dividido .crumb
Aquí es donde divergen la ruta de navegación tradicional y la de este componente.
Normalmente, este sería solo un vínculo <a>, pero agregué una UX de recorrido con una selección disfrazada. La clase .crumb se encarga de diseñar el vínculo y el ícono, mientras que .crumbicon se encarga de apilar el ícono y el elemento de selección. Lo llamé vínculo dividido porque sus funciones son muy similares a un botón dividido, pero para la navegación de páginas.
<span class="crumb">
<a href="#sub-collection-b">Category B</a>
<span class="crumbicon">
<svg>...</svg>
<select class="disguised-select" title="Navigate to another category">
<option>Category A</option>
<option selected>Category B</option>
<option>Category C</option>
</select>
</span>
</span>
Un vínculo y algunas opciones no son nada especial, pero agregan más funcionalidad a una ruta de navegación simple. Agregar un title al elemento <select> es útil para los usuarios de lectores de pantalla, ya que les brinda información sobre la acción del botón. Sin embargo, también brinda la misma ayuda a todos los demás, por lo que la verás en primer plano en el iPad. Un atributo proporciona contexto del botón a muchos usuarios.

Decoraciones de separadores
<span class="crumb-separator" aria-hidden="true">→</span>
Los separadores son opcionales. Agregar solo uno también funciona muy bien (consulta el tercer ejemplo en el video anterior). Luego, le doy a cada aria-hidden="true", ya que son decorativos y no son algo que un lector de pantalla necesite anunciar.
La propiedad gap, que se explica a continuación, facilita el espaciado de estos elementos.
Estilos
Dado que el color usa colores del sistema, se trata principalmente de espacios y pilas para los diseños.
Dirección y flujo del diseño

El elemento de navegación principal nav.breadcrumbs establece una propiedad personalizada con alcance para que la usen los elementos secundarios y, de lo contrario, establece un diseño horizontal alineado verticalmente. Esto garantiza que las rutas de navegación, los divisores y los íconos se alineen.
.breadcrumbs {
--nav-gap: 2ch;
display: flex;
align-items: center;
gap: var(--nav-gap);
padding: calc(var(--nav-gap) / 2);
}

Cada .crumb también establece un diseño horizontal alineado verticalmente con cierta brecha, pero se dirige especialmente a sus elementos secundarios de vínculo y especifica el estilo white-space: nowrap. Esto es fundamental para las rutas de navegación de varias palabras, ya que no queremos que se extiendan a varias líneas. Más adelante en esta publicación, agregaremos estilos para controlar el desbordamiento horizontal que causó esta propiedad white-space.
.crumb {
display: inline-flex;
align-items: center;
gap: calc(var(--nav-gap) / 4);
& > a {
white-space: nowrap;
&[aria-current="page"] {
font-weight: bold;
}
}
}
Se agregó aria-current="page" para ayudar a que el vínculo de la página actual se destaque del resto. Los usuarios de lectores de pantalla no solo tendrán un indicador claro de que el vínculo es para la página actual, sino que también le dimos un estilo visual al elemento para ayudar a los usuarios con visión a obtener una experiencia del usuario similar.
El componente .crumbicon usa una cuadrícula para apilar un ícono SVG con un elemento <select> "casi invisible".

.crumbicon {
--crumbicon-size: 3ch;
display: grid;
grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
place-items: center;
& > * {
grid-area: stack;
}
}
El elemento <select> es el último en el DOM, por lo que está en la parte superior de la pila y es interactivo. Agrega un estilo de opacity: .01 para que el elemento siga siendo utilizable, y el resultado sea un cuadro de selección que se ajuste perfectamente a la forma del ícono.
Esta es una buena manera de personalizar el aspecto de un elemento <select> y, al mismo tiempo, mantener la funcionalidad integrada.
.disguised-select {
inline-size: 100%;
block-size: 100%;
opacity: .01;
font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}
Menú ampliado
Las rutas de navegación deben poder representar un recorrido muy largo. Me gusta permitir que las cosas se salgan de la pantalla horizontalmente, cuando corresponde, y sentí que este componente de rutas de navegación calificaba bien.
.breadcrumbs {
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x proximity;
scroll-padding-inline: calc(var(--nav-gap) / 2);
& > .crumb:last-of-type {
scroll-snap-align: end;
}
@supports (-webkit-hyphens:none) { & {
scroll-snap-type: none;
}}
}
Los estilos de desbordamiento configuran la siguiente UX:
- Desplazamiento horizontal con contención de sobredesplazamiento.
- Padding de desplazamiento horizontal.
- Un punto de ajuste en la última miga. Esto significa que, cuando se carga la página, la primera ruta de navegación se carga ajustada y a la vista.
- Quita el punto de ajuste de Safari, que tiene problemas con las combinaciones de desplazamiento horizontal y efecto de ajuste.
Consultas de medios
Un ajuste sutil para los puertos de visualización más pequeños es ocultar la etiqueta "Principal" y dejar solo el ícono:
@media (width <= 480px) {
.breadcrumbs .home-label {
display: none;
}
}

Accesibilidad
Movimiento
No hay mucho movimiento en este componente, pero, si incluimos la transición en una verificación de prefers-reduced-motion, podemos evitar movimientos no deseados.
@media (prefers-reduced-motion: no-preference) {
.crumbicon {
transition: box-shadow .2s ease;
}
}
Ninguno de los otros estilos necesita cambiar. Los efectos de enfoque y desplazamiento son excelentes y significativos sin un transition, pero si el movimiento está bien, agregaremos una transición sutil a la interacción.
JavaScript
En primer lugar, independientemente del tipo de enrutador que uses en tu sitio o aplicación, cuando un usuario cambia las rutas de navegación, se debe actualizar la URL y se le debe mostrar la página adecuada. En segundo lugar, para normalizar la experiencia del usuario, asegúrate de que no se produzcan navegaciones inesperadas cuando los usuarios solo estén explorando las opciones de <select>.
Dos medidas críticas de la experiencia del usuario que se deben controlar con JavaScript: la selección ha cambiado y la prevención de la activación de eventos de cambio <select> anticipados.
La prevención de eventos anticipada es necesaria debido al uso de un elemento <select>. En Edge para Windows, y probablemente en otros navegadores también, el evento de selección changed se activa cuando el usuario explora las opciones con el teclado. Por eso, la llamé "ansiosa", ya que el usuario solo pseudoseleccionó la opción, como un desplazamiento o un enfoque, pero aún no confirmó la elección con enter o click. El evento eager hace que esta función de cambio de categoría de componente sea inaccesible, ya que abrir el cuadro de selección y simplemente navegar por un elemento activará el evento y cambiará la página antes de que el usuario esté listo.
Un mejor evento de <select> cambiado
const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])
// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
let ignoreChange = false
nav.addEventListener('change', e => {
if (ignoreChange) return
// it's actually changed!
})
nav.addEventListener('keydown', ({ key }) => {
if (preventedKeys.has(key))
ignoreChange = true
else if (allowedKeys.has(key))
ignoreChange = false
})
})
La estrategia para esto es observar los eventos de teclado hacia abajo en cada elemento <select> y determinar si la tecla presionada fue de confirmación de navegación (Tab o Enter) o de navegación espacial (ArrowUp o ArrowDown). Con esta determinación, el componente puede decidir esperar o ir cuando se activa el evento para el elemento <select>.
Conclusión
Ahora que sabes cómo lo hice, ¿cómo lo harías tú? 🙂
Diversifiquemos nuestros enfoques y aprendamos todas las formas de crear contenido en la Web. Crea una demostración, envíame por Twitter los vínculos y la agregaré a la sección de remixes de la comunidad que se encuentra a continuación.
Remixes de la comunidad
- Tux Solbakk como componente web: demostración y código