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 breadcrumb mostra in quale punto della gerarchia del sito si trova l'utente. Il nome proviene da Hansel e Gretel, che hanno lasciato i breadcrumb dietro di loro in alcuni legni scuri e sono riusciti a ritrovare la strada di casa tracciando le briciole al contrario.
I breadcrumb in questo post non sono breadcrumb standard, ma sono simili ai breadcrumb. Offrono funzionalità aggiuntive inserendo pagine di pari livello direttamente nella navigazione con un <select>
, rendendo possibile l'accesso a più livelli.
UX di sfondo
Nel video dimostrativo dei componenti riportato sopra, le categorie segnaposto sono generi di videogiochi. Questo percorso viene creato seguendo il seguente percorso: home »
rpg » indie » on sale
, come mostrato di seguito.
Questo componente di breadcrumb dovrebbe consentire agli utenti di spostarsi nella gerarchia di informazioni, saltando rami e selezionando pagine in modo veloce e preciso.
Architettura dell'informazione
Penso che sia utile pensare in termini di raccolte ed elementi.
Raccolte
Una raccolta è un insieme 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 potrebbe essere un elemento anche se rappresenta un'altra raccolta. Ad esempio, RPG è un elemento e una raccolta validi. 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 avranno un'architettura delle informazioni personalizzata (IA) che crea un array multidimensionale diverso, ma spero che il concetto di pagine di destinazione della raccolta e attraversamento della gerarchia possa essere incluso anche nei tuoi breadcrumb.
Layout
Aumento
I componenti efficaci iniziano con un codice HTML appropriato. Nella prossima sezione tratterò le scelte di markup e l'impatto che hanno sul componente generale.
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 un elemento SVG nascosto alla pagina e aggrega 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 l'HTML SVG, memorizza le informazioni dell'icona 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 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 di 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
si occupa della disposizione del link e
l'icona, mentre la classe .crumbicon
si occupa della sovrapposizione e della selezione
dell'icona. L'ho definito link divisi perché le sue funzioni sono molto
simili a split-button,
ma per la navigazione nelle pagine.
<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 speciali, 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 anche a tutti gli altri, vedrai che è 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.
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 con ambito
che i bambini possono utilizzare e stabilisce in altro modo 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 stili per gestire
l'overflow orizzontale causato dalla 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 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 dovrebbero essere in grado di rappresentare una scia molto lunga. 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 overscroll.
- Spaziatura interna di scorrimento orizzontale.
- Un punto di aggancio sull'ultima traccia. Ciò significa che al caricamento della pagina la prima crumb viene caricata agganciata e visualizzata.
- Rimuove il punto di aggancio da Safari, che presenta problemi con le combinazioni di effetti di scorrimento orizzontale e di aggancio.
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 al passaggio del mouse e di messa a fuoco sono fantastici e significativi
senza un transition
, ma se il movimento va bene, aggiungeremo una leggera
transizione 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 per l'esperienza utente che devono essere gestite da JavaScript: select è cambiato e la prevenzione dell'attivazione degli eventi 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. Ecco perché l'ho definito eager: l'utente ha solo pseudo selezionato l'opzione, ad esempio un passaggio del mouse o lo stato attivo, ma non ha ancora confermato la scelta con enter
o click
. L'evento "eager" rende inaccessibile la funzionalità di modifica della categoria del componente, perché l'apertura della casella di selezione e la semplice navigazione 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 con i link e io la aggiungerò alla sezione dei remix della community qui sotto.
Remix della community
- Tux Solbakk come componente web: demo e codice