Una descripción general fundamental de cómo crear un componente de migas de pan responsivo y accesible para que los usuarios naveguen por tu sitio.
En esta publicación, quiero compartir mi forma de pensar sobre cómo crear componentes de migas de pan. Prueba la demostración.
Si prefieres ver un video, aquí tienes una versión de esta publicación en YouTube:
Descripción general
Un componente de rutas de navegación muestra en qué parte de la jerarquía del sitio se encuentra el usuario. El nombre proviene de Hansel y Gretel, que dejaron migas de pan a su paso por un bosque oscuro y pudieron encontrar el camino a casa siguiendo el rastro de migas.
Las rutas de navegación de esta entrada no son estándares
rutas de navegación,
son similares a una ruta de navegación. Ofrecen funciones adicionales, ya que colocan
las páginas directamente en la navegación con un <select>
, lo que brinda acceso a varios niveles
como sea posible.
UX de fondo
En el video de demostración del componente anterior, las categorías de marcador de posición son géneros de
videojuegos. Para crear este rastro, navega por la siguiente ruta de acceso: home »
rpg » indie » on sale
, como se muestra a continuación.
Este componente de migas de pan debe permitir que los usuarios se muevan por esta jerarquía de información, salten ramas 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 migas de pan 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 puede ser un elemento si representa a otra colección. Por ejemplo, RPG es un elemento y una colección válida. Cuando se trata de un artículo, el usuario se encuentra en esa página de la colección. Por ejemplo: están en la página de RPG, que muestra una lista de juegos de RPG, incluido el subcategorías adicionales AAA, independientes y autopublicadas.
En términos de informática, este componente de migas de pan 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 (AI) 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 jerarquía también puedan incluirse en tus indicadores de ruta.
Diseños
Marca
Los componentes buenos comienzan con el HTML adecuado. En la siguiente sección, explicaré mis opciones de marcado y cómo afectan al componente general.
Esquema oscuro y claro
<meta name="color-scheme" content="dark light">
La metaetiqueta color-scheme
de la tabla anterior
El fragmento le informa al navegador que esta página desea usar el navegador claro y el oscuro.
estilos. El ejemplo de pan de migas no incluye ningún CSS para estos esquemas de colores, por lo que usará los colores predeterminados que proporciona el navegador.
Elemento de navegación
<nav class="breadcrumbs" role="navigation"></nav>
Es apropiado utilizar la
Elemento <nav>
para la navegación del sitio, que tiene un rol implícito de ARIA
navegación.
Durante las pruebas, noté que tener el atributo role
cambió la forma en que un lector de pantalla interactuaba con el elemento, en realidad, se anunciaba como navegación, por lo que decidí agregarlo.
Íconos
Cuando se repite un ícono en una página, el elemento SVG <use>
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 inconsistencias en la ruta.
Para usar esta técnica, agrega un elemento SVG oculto a la página y une 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 SVG HTML, coloca la información del ícono en la memoria y continúa con el resto de la página que hace referencia al ID para usos adicionales del ícono, como este:
<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>
Definíelos una vez y úsalos 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 alguien que navega y solo escucha el contenido, por lo que ocultarlos para esos usuarios evita que agreguen ruido innecesario.
Vínculo dividido .crumb
Aquí es donde divergen el pan de miga tradicional y el de este componente.
Por lo general, solo sería un vínculo de <a>
, pero agregué una UX de recorrido con una
seleccionar 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 seleccionar
de un elemento común. Lo llamé vínculo dividido porque sus funciones son muy similares a las de 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 especiales, pero agregan más funcionalidad a un breadcrumb simple. Agregar un title
al elemento <select>
es útil para la pantalla
usuarios lectores, brindándoles información sobre la acción del botón. Sin embargo,
brinda la misma ayuda
a todos los demás, verás que se destaca
iPad Un atributo proporciona contexto de botones a muchos usuarios.
Decoraciones de separador
<span class="crumb-separator" aria-hidden="true">→</span>
Los separadores son opcionales, agregar solo uno también funciona muy bien (mira el tercer ejemplo en el video).
arriba). Luego, le doy a cada aria-hidden="true"
, ya que son decorativos y no es algo que un lector de pantalla necesite anunciar.
La propiedad gap
, que se describe a continuación, hace que el espaciado de estos elementos sea directo.
Estilos
Dado que el color usa colores del sistema, se trata principalmente de espacios y pilas para los estilos.
Dirección y flujo del diseño
El elemento de navegación principal nav.breadcrumbs
establece una propiedad personalizada con alcance.
para que lo usen los niños y establece un espacio horizontal
. Esto garantiza que los indicadores de ruta, 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 de forma vertical con algunos
brecha, pero se orienta, en especial, a su vínculo secundario y especifica el estilo
white-space: nowrap
Esto es crucial para las rutas de navegación de varias palabras, ya que
quieren que usen 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 agrega 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 aplicamos un diseño visual al elemento para ayudar a los usuarios videntes a obtener una experiencia del usuario similar.
El componente .crumbicon
usa la 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 los elementos salgan de la pantalla horizontalmente, cuando corresponda, y creo que este componente de migas de pan se ajusta 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 menú ampliado configuran la siguiente UX:
- Desplazamiento horizontal con contención del sobredesplazamiento.
- Margen de desplazamiento horizontal.
- Un punto de ajuste en el último fragmento Esto significa que, cuando se carga la página, el primer fragmento se carga ajustado y en 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 viewports más pequeños es ocultar la etiqueta "Home" y dejar solo el ícono:
@media (width <= 480px) {
.breadcrumbs .home-label {
display: none;
}
}
Accesibilidad
Movimiento
No hay mucho movimiento en este componente, pero si envuelves la
en una comprobación de prefers-reduced-motion
, podemos evitar movimientos no deseados.
@media (prefers-reduced-motion: no-preference) {
.crumbicon {
transition: box-shadow .2s ease;
}
}
No es necesario cambiar ninguno de los otros estilos, los efectos de desplazamiento y enfoque son geniales 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 router que uses en tu sitio o aplicación, cuando un usuario cambia el pan de breadcrumbs, 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 fundamentales de la experiencia del usuario que debe controlar JavaScript: select has changed y prevención de activación de eventos de cambio <select>
anticipados.
La prevención de eventos inmediatos es necesaria debido al uso de un <select>
. En Windows Edge, y probablemente en otros navegadores también, la opción changed
seleccionada
evento 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 del mouse o un enfoque, pero aún no confirmó la elección con enter
o click
. Los ansiosos
este evento hace que la función de cambio de categoría de componente sea inaccesible porque
Abrir el cuadro de selección y navegar por un elemento activará el evento y
cambiar la página, antes de que el usuario esté listo.
Un mejor evento de cambio de <select>
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 mirar los eventos de presión del teclado en cada <select>
.
y determina si la tecla pulsada fue confirmación de navegación (Tab
o
Enter
) o la navegación espacial (ArrowUp
o ArrowDown
). Con este
el componente puede decidir esperar o salir, cuando el evento para el
Se activa 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 compilar en la Web. Crea una demo, twittea los vínculos y los agregaré a la sección de remixes de la comunidad a continuación.
Remixes de la comunidad
- Tux Solbakk como componente web: demo y código