Una panoramica di base su come creare un componente breadcrumb reattivo e accessibile per consentire agli utenti di navigare nel tuo sito.
In questo post voglio condividere il mio pensiero su un modo per creare componenti breadcrumb. Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
Un componente breadcrumb mostra la posizione dell'utente nella gerarchia del sito. Il nome deriva da Hansel e Gretel, che lasciavano briciole di pane dietro di sé in un bosco buio e riuscivano a ritrovare la strada di casa seguendo le briciole a ritroso.
I breadcrumb in questo post non sono standard, ma simili. Offrono funzionalità aggiuntive inserendo le pagine
correlate direttamente nella navigazione con un <select>, rendendo possibile
l'accesso a più livelli.
UX in background
Nel video dimostrativo del componente riportato sopra, le categorie segnaposto sono generi di
videogio. Questo percorso viene creato seguendo il percorso home »
rpg » indie » on sale, come mostrato di seguito.
Questo componente breadcrumb deve consentire agli utenti di spostarsi all'interno di questa gerarchia di informazioni, saltando i rami e selezionando le pagine in modo rapido e preciso.
Architettura delle informazioni
Trovo utile pensare in termini di raccolte e articoli.
Raccolte
Una raccolta è un insieme di opzioni tra cui scegliere. Dalla home page del prototipo della breadcrumb di questo post, le raccolte sono FPS, RPG, picchiaduro, dungeon crawler, sport e puzzle.
Elementi
Un videogioco è un elemento, ma anche una raccolta specifica può essere un elemento se rappresenta un'altra raccolta. Ad esempio, RPG è un elemento e una raccolta valida. Se si tratta di un elemento, l'utente si trova nella pagina della raccolta. Ad esempio, si trova nella pagina RPG, che mostra un elenco di giochi di ruolo, tra cui le sottocategorie aggiuntive AAA, Indie e Self Published.
In termini di informatica, questo componente breadcrumb rappresenta un array multidimensionale:
const rawBreadcrumbData = {
"FPS": {...},
"RPG": {
"AAA": {...},
"indie": {
"new": {...},
"on sale": {...},
"under 5": {...},
},
"self published": {...},
},
"brawler": {...},
"dungeon crawler": {...},
"sports": {...},
"puzzle": {...},
}
La tua app o il tuo sito web avranno un'architettura delle informazioni (AI) personalizzata che crea un array multidimensionale diverso, ma spero che il concetto di pagine di destinazione delle raccolte e di attraversamento della gerarchia possa essere incluso anche nel percorso di navigazione.
Layout
Segni e linee
I componenti validi iniziano con un HTML appropriato. Nella sezione successiva tratterò le mie scelte di markup e il loro impatto sul componente complessivo.
Combinazione scura e chiara
<meta name="color-scheme" content="dark light">
Il meta tag color-scheme nello snippet
sopra indica al browser che questa pagina vuole gli stili chiaro e scuro del browser. Le breadcrumb di esempio non includono CSS per questi schemi di colori,
pertanto utilizzeranno i colori predefiniti forniti dal browser.
Elemento di navigazione
<nav class="breadcrumbs" role="navigation"></nav>
È opportuno utilizzare l'elemento
<nav>
per la navigazione del sito, che ha un ruolo ARIA implicito di
navigazione.
Durante i test, ho notato che l'attributo role ha modificato il modo in cui un
lettore di schermo interagiva con l'elemento, che veniva annunciato come
navigazione, quindi ho scelto di aggiungerlo.
Icone
Quando un'icona viene ripetuta su una pagina, l'elemento SVG
<use>
significa che puoi definire path una sola volta e utilizzarlo per tutte le istanze dell'icona. In questo modo si evita la ripetizione delle stesse informazioni sul percorso, causando
documenti più grandi e la potenziale incoerenza del percorso.
Per utilizzare questa tecnica, aggiungi un elemento SVG nascosto alla pagina e racchiudi le icone
in un elemento <symbol> con un ID univoco:
<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>
Il browser legge il codice HTML SVG, inserisce le informazioni sull'icona in memoria e continua con il resto della pagina facendo riferimento all'ID per ulteriori utilizzi dell'icona, ad esempio:
<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>

Definisci una volta, usa tutte le volte che vuoi, con un impatto minimo sulle prestazioni della pagina
e uno stile flessibile. La notifica aria-hidden="true" viene aggiunta all'elemento SVG.
Le icone non sono utili per chi naviga e sente solo i contenuti, nasconderle
per questi utenti evita di aggiungere rumore inutile.
Split-link .crumb
È qui che il breadcrumb tradizionale e quelli di questo componente divergono.
Normalmente, questo sarebbe solo un link <a>, ma ho aggiunto l'esperienza utente di attraversamento con una
selezione mascherata. La classe .crumb è responsabile della disposizione del link e dell'icona, mentre la classe .crumbicon è responsabile dell'impilamento dell'icona e dell'elemento di selezione. L'ho chiamato split-link perché le sue funzioni sono molto
simili a un pulsante diviso,
ma per la navigazione della pagina.
<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 link e alcune opzioni non sono niente di speciale, ma aggiungono più funzionalità a una
semplice breadcrumb. L'aggiunta di un title all'elemento <select> è utile per gli utenti di screen reader, in quanto fornisce informazioni sull'azione del pulsante. Tuttavia, offre lo stesso aiuto anche a tutti gli altri, come vedrai in primo piano su iPad. Un attributo fornisce il contesto del pulsante a molti utenti.

Decorazioni separatore
<span class="crumb-separator" aria-hidden="true">→</span>
I separatori sono facoltativi, ma anche aggiungerne uno solo funziona benissimo (vedi il terzo esempio nel video
sopra). Poi assegno a ciascun aria-hidden="true" perché sono decorativi e non
qualcosa che uno screen reader deve annunciare.
La proprietà gap, trattata di seguito, semplifica la spaziatura.
Stili
Poiché il colore utilizza i colori di sistema, si tratta principalmente di spazi e pile per gli stili.
Direzione e flusso del layout

L'elemento di navigazione principale nav.breadcrumbs imposta una proprietà personalizzata con ambito
che i figli possono utilizzare e stabilisce un layout orizzontale allineato verticalmente. In questo modo, le briciole, i divisori e le icone sono allineati.
.breadcrumbs {
--nav-gap: 2ch;
display: flex;
align-items: center;
gap: var(--nav-gap);
padding: calc(var(--nav-gap) / 2);
}

Ogni .crumb stabilisce anche un layout orizzontale allineato verticalmente con un po' di spazio, ma ha come target specifico i suoi elementi secondari di collegamento e specifica lo stile white-space: nowrap. Questo è fondamentale per i breadcrumb composti da più parole, in quanto non
vogliamo che vadano su più righe. Più avanti in questo post aggiungeremo stili per gestire l'overflow orizzontale causato da questa proprietà 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;
}
}
}
aria-current="page" viene aggiunto per far risaltare il link della pagina corrente rispetto
al resto. Gli utenti di screen reader non solo avranno un indicatore chiaro che il link è
per la pagina corrente, ma abbiamo anche applicato uno stile visivo all'elemento per aiutare gli utenti vedenti
a ottenere un'esperienza utente simile.
Il componente .crumbicon utilizza la griglia per impilare un'icona SVG con un elemento <select> "quasi
invisibile".

.crumbicon {
--crumbicon-size: 3ch;
display: grid;
grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
place-items: center;
& > * {
grid-area: stack;
}
}
L'elemento <select> è l'ultimo nel DOM, quindi si trova in cima alla pila
ed è interattivo. Aggiungi uno stile di opacity: .01 in modo che l'elemento sia ancora utilizzabile
e il risultato sia una casella di selezione che si adatta perfettamente alla forma dell'icona.
Questo è un ottimo modo per personalizzare l'aspetto di un elemento <select> mantenendo la funzionalità integrata.
.disguised-select {
inline-size: 100%;
block-size: 100%;
opacity: .01;
font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}
Overflow
I breadcrumb devono essere in grado di rappresentare un percorso molto lungo. Mi piace consentire che gli elementi escano dallo schermo orizzontalmente, quando appropriato, e ho ritenuto che questo componente breadcrumb fosse adatto.
.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;
}}
}
Gli stili di overflow configurano la seguente esperienza utente:
- Scorrimento orizzontale con contenimento dell'overscroll.
- Spaziatura interna dello scorrimento orizzontale.
- Un punto di aggancio sull'ultima briciola. Ciò significa che al caricamento della pagina il primo segmento viene caricato in modo rapido e in visualizzazione.
- Rimuove il punto di aggancio da Safari, che ha difficoltà con le combinazioni di scorrimento orizzontale e effetto di aggancio.
Query supporti
Una piccola modifica per le finestre più piccole consiste nel nascondere l'etichetta "Home", lasciando solo l'icona:
@media (width <= 480px) {
.breadcrumbs .home-label {
display: none;
}
}

Accessibilità
Movimento
In questo componente non c'è molto movimento, ma racchiudendo la
transizione in un controllo prefers-reduced-motion, possiamo evitare movimenti indesiderati.
@media (prefers-reduced-motion: no-preference) {
.crumbicon {
transition: box-shadow .2s ease;
}
}
Nessuno degli altri stili deve essere modificato, gli effetti al passaggio del mouse e di messa a fuoco sono ottimi
e significativi senza un transition, ma se il movimento è accettabile, aggiungeremo una transizione
delicata all'interazione.
JavaScript
Innanzitutto, indipendentemente dal tipo di router utilizzato nel tuo sito o nella tua applicazione,
quando un utente modifica la breadcrumb, l'URL deve essere aggiornato e all'utente
deve essere mostrata la pagina appropriata. In secondo luogo, per normalizzare l'esperienza utente, assicurati che non si verifichino navigazioni impreviste quando gli utenti stanno semplicemente sfogliando le opzioni <select>.
Due misure critiche dell'esperienza utente da gestire con JavaScript: la selezione è cambiata e la prevenzione dell'attivazione dell'evento di modifica <select>.
La prevenzione degli eventi eager è necessaria a causa dell'utilizzo di un elemento <select>. Su Windows Edge e probabilmente anche su altri browser, l'evento select changed
viene attivato quando l'utente sfoglia le opzioni con la tastiera. Per questo motivo l'ho chiamata "ansiosa", perché l'utente ha selezionato l'opzione solo in modo pseudo, come un passaggio del mouse o una messa a fuoco, ma non ha confermato la scelta con enter o un click. L'evento
eager rende inaccessibile questa funzionalità di modifica della categoria dei componenti, perché
l'apertura della casella di selezione e la semplice navigazione di un elemento attivano l'evento e
cambiano la pagina prima che l'utente sia pronto.
Un evento modificato <select> migliore
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 strategia consiste nell'osservare gli eventi di pressione dei tasti su ogni elemento <select> e determinare se il tasto premuto è di conferma della navigazione (Tab o Enter) o di navigazione spaziale (ArrowUp o ArrowDown). In base a questa determinazione, il componente può decidere se attendere o procedere quando viene attivato l'evento per l'elemento <select>.
Conclusione
Ora che sai come ho fatto, come faresti tu?‽ 🙂
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare contenuti sul web. Crea una demo, inviami un tweet con i link e la aggiungerò alla sezione dei remix della community qui sotto.
Remix della community
- Tux Solbakk come componente web: demo e codice