Creazione di un componente breadcrumb

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.

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.

<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>

DevTools che mostra un elemento use SVG sottoposto a rendering.

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.

È 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.

Screenshot con l&#39;elemento di selezione invisibile su cui è stato passato il mouse e la relativa
descrizione comando contestuale visualizzata.

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

DevTools che mostra l&#39;allineamento della navigazione breadcrumb con la funzionalità di overlay flexbox.

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);
}

Una breadcrumb mostrata in allineamento verticale con overlay flexbox.

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".

Grid DevTools mostrato in overlay su un pulsante in cui la riga e la colonna sono entrambe
denominate stack.

.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;
  }
}

Confronto affiancato delle breadcrumb con e senza un&#39;etichetta Home.

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