Una panoramica di base su come creare un componente breadcrumb responsive e accessibile per consentire agli utenti di navigare nel tuo sito.
In questo post voglio condividere alcune idee su come creare componenti breadcrumb. Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
Un componente breadcrumbs mostra la posizione dell'utente nella gerarchia del sito. Il nome deriva da Hansel e Gretel, che lasciavano cadere breadcrumb dietro di loro in un bosco scuro e riuscivano a trovare la strada di casa seguendo a ritroso i breadcrumb.
I breadcrumb in questo post non sono breadcrumb standard, ma sono simili ai breadcrumb. Offrono funzionalità aggiuntive inserendo le pagine con lo stesso <select>
direttamente nella barra di navigazione, rendendo possibile l'accesso a più livelli.
UX di sfondo
Nel video demo del componente riportato sopra, le categorie segnaposto sono i generi di videogiochi. Questo percorso viene creato seguendo il seguente percorso: home »
rpg » indie » on sale
, come mostrato di seguito.
Questo componente del breadcrumb deve consentire agli utenti di spostarsi in questa gerarchia di informazioni, saltando i rami e selezionando le pagine con velocità e precisione.
Architettura dell'informazione
Trovo utile pensare in termini di raccolte ed elementi.
Raccolte
Una raccolta è un array di opzioni tra cui scegliere. Dalla home page del prototipo del breadcrumb di questo post, le raccolte sono FPS, RPG, picchiaduro, dungeon crawler, sport e puzzle.
Elementi
Un videogioco è un elemento, una raccolta specifica può essere un elemento anche se rappresenta un'altra raccolta. Ad esempio, RPG è un articolo e una raccolta valida. Se si tratta di un articolo, l'utente si trova nella pagina della collezione. Ad esempio, si trovano nella pagina RPG, che mostra un elenco di giochi di ruolo, incluse le sottocategorie aggiuntive AAA, Indie e Autopubblicato.
In termini di informatica, questo componente della cronologia 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 avrà un'architettura di informazioni (IA) personalizzata che crea un array multidimensionale diverso, ma spero che il concetto di pagine di destinazione della raccolta e di attraversamento della gerarchia possa essere inserito anche nei tuoi breadcrumb.
Layout
Segni e linee
I componenti di buona qualità iniziano con HTML appropriato. Nella sezione successiva parlerò delle mie scelte di markup e di come influiscono sul componente complessivo.
Schema scuro e chiaro
<meta name="color-scheme" content="dark light">
Il meta tag color-scheme
nello snippet riportato sopra informa il browser che questa pagina richiede gli stili di browser chiaro e scuro. I breadcrumb di esempio non includono CSS per queste combinazioni 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 nel 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 screen reader interagiva con l'elemento, che in realtà veniva annunciato come navigazione, quindi ho scelto di aggiungerlo.
Icone
Quando un'icona viene ripetuta in una pagina, l'elemento SVG
<use>
significa che puoi definire path
una volta e utilizzarlo per tutte le istanze dell'icona. In questo modo, si evita di ripetere le stesse informazioni sui percorsi, con conseguente aumento delle dimensioni dei documenti e potenziale incoerenza dei percorsi.
Per utilizzare questa tecnica, aggiungi alla pagina un elemento SVG nascosto e inserisci 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 nella memoria e prosegue 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 e utilizzala tutte le volte che vuoi, con un impatto minimo sulle prestazioni della pagina e uno stile flessibile. Nota: aria-hidden="true"
viene aggiunto all'elemento SVG.
Le icone non sono utili per chi naviga e ascolta solo i contenuti, quindi nasconderle a questi utenti impedisce loro di aggiungere rumore non necessario.
Link con suddivisione .crumb
È qui che il breadcrumb tradizionale e quelli in questo componente divergono.
Normalmente, si tratterebbe solo di un link <a>
, ma ho aggiunto l'esperienza utente di attraversamento con un pulsante di selezione mascherato. La classe .crumb
è responsabile della disposizione del link e
dell'icona, mentre .crumbicon
è responsabile dell'impilamento dell'icona e dell'elemento selezionato. L'ho chiamato link diviso perché le sue funzioni sono molto simili a quelle di un pulsante diviso, ma per la navigazione nella 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 un 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, fornisce lo stesso aiuto a tutti gli altri, quindi lo 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 l'aggiunta di un solo separatore è ottimale (vedi il terzo esempio nel video sopra). Poi li inserisco tutti, poiché sono decorativi e non devono essere annunciati da uno screen reader.aria-hidden="true"
La proprietà gap
, descritta di seguito, semplifica la spaziatura di questi elementi.
Stili
Poiché il colore utilizza i colori di sistema, si tratta principalmente di spazi e serie per gli stili.
Direzione e flusso del layout
L'elemento di navigazione principale nav.breadcrumbs
imposta una proprietà personalizzata basata sugli ambiti da utilizzare per gli elementi secondari e, in caso contrario, stabilisce un layout orizzontale allineato verticalmente. In questo modo, i link breadcrumb, i separatori 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 spaziatura, ma sceglie come target specifico i link secondari e specifica lo stilewhite-space: nowrap
. Questo è fondamentale per i breadcrumb composti da più parole, poiché non vogliamo che vengano visualizzati su più righe. Più avanti in questo post aggiungeremo gli stili per gestire il 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 agli altri. Non solo gli utenti di screen reader avranno un indicatore chiaro che indica che il link è per la pagina corrente, ma abbiamo anche stilizzato visivamente l'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 opacity: .01
in modo che l'elemento sia ancora utilizzabile.
Il risultato è una casella di selezione che si adatta perfettamente alla forma dell'icona.
Si tratta di 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 di visualizzare elementi fuori dallo schermo orizzontalmente, se opportuno, e ho ritenuto che questo componente del 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 impostano la seguente esperienza utente:
- Scorrimento orizzontale con contenimento dello scorrimento eccessivo.
- Spaziatura interna di scorrimento orizzontale.
- Un punto di aggancio sull'ultima traccia. Ciò significa che al caricamento della pagina, il primobreadcrumb viene caricato in modo da essere agganciato e visibile.
- Rimuove il punto di aggancio da Safari, che ha difficoltà con le combinazioni di scorrimento orizzontale e effetto snap.
Query sui contenuti multimediali
Un piccolo aggiustamento per le visualizzazioni più piccole è 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 inserendo 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 di passaggio del mouse e di messa a fuoco sono fantastici e significativi senza un transition
, ma se il movimento è accettabile, aggiungeremo una transizione sottile all'interazione.
JavaScript
Innanzitutto, indipendentemente dal tipo di router utilizzato nel tuo sito o nella tua applicazione, quando un utente modifica i 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 solo sfogliando le opzioni <select>
.
Due misure fondamentali dell'esperienza utente da gestire in JavaScript: seleziona ha
cambiato e la prevenzione dell'attivazione dell'evento di modifica <select>
.
La prevenzione degli eventi eager è necessaria a causa dell'utilizzo di un elemento <select>
. In Windows Edge e probabilmente anche in altri browser, l'evento select changed
viene attivato quando l'utente sfoglia le opzioni con la tastiera. Per questo motivo lo ho chiamato "eager", perché l'utente ha solo pseudo selezionato l'opzione, ad esempio con un passaggio del mouse o un'attivazione, ma non ha confermato la scelta con enter
o click
. L'evento eager
rende inaccessibile questa funzionalità di modifica della categoria del componente, perché
l'apertura della casella di selezione e la semplice visualizzazione di un elemento attiveranno l'evento e
modificheranno la pagina prima che l'utente sia pronto.
Un evento <select>
modificato 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 è la conferma di navigazione (Tab
o
Enter
) o la navigazione spaziale (ArrowUp
o ArrowDown
). Con questa
determinazione, il componente può decidere di attendere o andare, quando viene attivato l'evento per l'elemento
<select>
.
Conclusione
Ora che sai come ho fatto, come faresti? 🙂
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami i link e io la aggiungerò alla sezione dei remix della community di seguito.
Remix della community
- Tux Solbakk come componente web: demo e codice