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 con voi la riflessione su un modo per creare componenti breadcrumb. Prova la demo.

Demo

Se preferisci i video, ecco una versione di YouTube di questo post:

Panoramica

Un componente breadcrumb mostra dove si trova l'utente nella gerarchia del sito. Il nome è preso da Hansel e Gretel, che hanno inserito i breadcrumb dietro di loro in alcuni boschi scuri e sono riusciti a ritrovare la strada di casa tracciando delle briciole all'indietro.

I breadcrumb in questo post non sono breadcrumb standard, ma sono simili a breadcrumb. Offrono funzionalità aggiuntive inserendo le pagine di pari livello direttamente nella navigazione con un <select>, rendendo possibile l'accesso a più livelli.

UX in background

Nel video dimostrativo dei componenti riportato sopra, le categorie segnaposto sono generi di videogiochi. Questo sentiero si crea seguendo il seguente percorso: home » rpg » indie » on sale, come mostrato di seguito.

Questo componente breadcrumb dovrebbe consentire agli utenti di spostarsi nella gerarchia di informazioni, saltare i rami e selezionare le pagine in modo rapido e preciso.

Architettura delle informazioni

Trovo utile pensare in termini di raccolte e oggetti.

Raccolte

Una raccolta è un array di opzioni tra cui scegliere. Nella home page del prototipo di breadcrumb di questo post, le raccolte sono FPS, RPG, picchiaduro, dungeon crawler, sport e rompicapi.

Elementi

Un videogioco è un articolo; una collezione specifica può essere un articolo anche se rappresenta un'altra collezione. Ad esempio, RPG è un elemento e una raccolta valida. Quando si tratta di un articolo, l'utente si trova nella pagina della raccolta. Ad esempio, si trovano nella pagina RPG, in cui è visualizzato un elenco di giochi RPG, comprese le sottocategorie aggiuntive AAA, Indie e Autopubblicazione.

In informatica, questo componente di 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 avrà un'architettura delle informazioni personalizzata (IA) che crea un array multidimensionale diverso, ma spero che anche il concetto di pagine di destinazione delle raccolte e l'attraversamento gerarchico possa comparire nei tuoi breadcrumb.

Layout

Markup

I componenti efficaci iniziano con il codice HTML appropriato. Nella prossima sezione illustrerò le opzioni di markup e il modo in cui influiscono sul componente complessivo.

Parete con luci e scuri

<meta name="color-scheme" content="dark light">

Il meta tag color-scheme nello snippet riportato sopra indica al browser che questa pagina richiede gli stili del browser chiaro e scuro. I breadcrumb di esempio non includono CSS per queste combinazioni di colori, pertanto i breadcrumb utilizzeranno i colori predefiniti forniti dal browser.

<nav class="breadcrumbs" role="navigation"></nav>

È opportuno utilizzare l'elemento <nav> per la navigazione nel sito, che ha un ruolo di navigazione ARIA implicito. Durante il test, ho notato che l'attributo role modificava il modo in cui uno screen reader interagiva con l'elemento, che era stato annunciato come navigazione e ho quindi deciso di aggiungerlo.

Icone

Quando un'icona viene ripetuta in una pagina, l'elemento SVG <use> significa che puoi definire path una sola volta e utilizzarlo per tutte le istanze dell'icona. Ciò impedisce che le stesse informazioni sul percorso vengano ripetute, causando documenti più grandi e potenziali incongruenze.

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 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, in questo modo:

<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 di utilizzo SVG sottoposto a rendering.

Definisci l'opzione una sola volta e utilizzala tutte le volte che vuoi, con un impatto minimo sul rendimento delle pagine e uno stile flessibile. Nota che aria-hidden="true" è stato aggiunto all'elemento SVG. Le icone non sono utili agli utenti che navigano e che ascolta solo i contenuti. Se le nascondono, gli utenti non aggiungono più rumori inutili.

È qui che divergono il breadcrumb tradizionale e quelli di questo componente. Normalmente si tratta di un link <a>, ma ho aggiunto un'esperienza utente di attraversamento con un elemento di selezione camuffato. La classe .crumb è responsabile del layout del link e dell'icona, mentre la classe .crumbicon si occupa della sovrapposizione dell'icona e della selezione dell'elemento. L'ho chiamato split-link 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 ulteriori funzionalità a un breadcrumb semplice. L'aggiunta di 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, noterai che è in primo piano su iPad. Un attributo fornisce il contesto del pulsante a molti utenti.

Screenshot con l&#39;elemento di selezione invisibile al passaggio del mouse e la relativa descrizione comando contestuale visualizzata.

Decorazioni separatore

<span class="crumb-separator" aria-hidden="true">→</span>

I separatori sono facoltativi, quindi anche aggiungerne uno è molto utile (vedi il terzo esempio nel video in alto). Do poi ogni aria-hidden="true" perché sono decorativi e non sono qualcosa che uno screen reader deve annunciare.

La proprietà gap, di cui parleremo di seguito, rende la spaziatura tra le due cose lineare.

Stili

Dal momento che il colore utilizza i colori di sistema, sono principalmente lacune e serie per gli stili.

Direzione e flusso del layout

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

L'elemento di navigazione principale nav.breadcrumbs imposta una proprietà personalizzata con ambito che gli elementi secondari possono utilizzare, altrimenti stabilisce un layout allineato verticalmente. In questo modo briciole, divisori e icone saranno allineati.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

Un breadcrumb mostrato verticalmente con overlay flexbox.

Ogni .crumb stabilisce anche un layout orizzontale allineato verticalmente con un certo margine, ma ha come target in modo specifico i relativi link secondari e specifica lo stile white-space: nowrap. Questo è fondamentale per i breadcrumb con più parole, perché non vanno su più righe. Più avanti in questo post aggiungeremo gli 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;
    }
  }
}

Viene aggiunto aria-current="page" per mettere in risalto il link della pagina corrente da tutti gli altri. Non solo gli utenti di screen reader avranno un chiaro indicatore che il link rimanda alla pagina corrente, ma abbiamo anche applicato stili visivi all'elemento per aiutare gli utenti vedenti a ottenere un'esperienza utente simile.

Il componente .crumbicon utilizza una griglia per sovrapporre un'icona SVG a un elemento <select> "quasi invisibile".

Gli strumenti DevTools a griglia sono visualizzati in sovrapposizione a un pulsante, in cui la riga e la colonna sono entrambe chiamate 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 è in cima all'elenco e è interattivo. Aggiungi uno stile di opacity: .01 in modo che l'elemento sia ancora utilizzabile. Il risultato sarà una casella di selezione che si adatta perfettamente alla forma dell'icona. Questo è un buon 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 */
}

Extra

I breadcrumb devono poter rappresentare un percorso molto lungo. Adoro lasciare che gli elementi vengano fuori schermo orizzontalmente, quando opportuno, e ho ritenuto che questo componente dei breadcrumb fosse qualificato bene.

.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 extra configurano la seguente UX:

  • Scorrimento orizzontale con contenimento tramite scorrimento orizzontale.
  • Spaziatura interna a scorrimento orizzontale.
  • Uno scatto sull'ultima fetta. Ciò significa che, al caricamento della pagina, i primi caricamenti di crumb sono agganciati e visibili.
  • Rimuove il punto di agganciamento da Safari, che presenta difficoltà con le combinazioni di effetti di scorrimento orizzontale e aggancio.

Query supporti

Un piccolo aggiustamento per le aree visibili più piccole consiste nel nascondere l'etichetta "Home", lasciando solo l'icona:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

Affiancato ai breadcrumb con e senza l&#39;etichetta Home, per il confronto.

Accessibilità

Movimento

Non c'è molto movimento in questo componente, ma se invii 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 del passaggio del mouse e di messa a fuoco sono straordinari e significativi senza transition, ma se il movimento è accettabile, aggiungeremo una leggera transizione all'interazione.

JavaScript

Innanzitutto, indipendentemente dal tipo di router utilizzato nel sito o nell'applicazione, quando un utente modifica i breadcrumb, l'URL deve essere aggiornato e l'utente deve mostrare la pagina appropriata. In secondo luogo, per normalizzare l'esperienza utente, assicurati che non avvengano navigazioni impreviste quando gli utenti si limitano a sfogliare le opzioni <select>.

Due misure fondamentali per l'esperienza utente che devono essere gestite da JavaScript: la selezione ha cambiato e la prevenzione dell'attivazione degli eventi di modifica di <select>.

La prevenzione degli eventi eager è necessaria a causa dell'uso di un elemento <select>. Su Windows Edge e probabilmente anche in altri browser, l'evento selezionato changed viene attivato quando l'utente sfoglia le opzioni con la tastiera. Questo è il motivo per cui l'ho definita "entusiasmo", dato che l'utente ha selezionato solo pseudo l'opzione, ad esempio un passaggio del mouse o un focus, ma non ha ancora confermato la scelta con enter o click. L'evento eager rende inaccessibile questa funzionalità di modifica della categoria dei componenti, perché l'apertura della casella di selezione e la semplice esplorazione di un elemento attiva l'evento e cambia la pagina prima che l'utente sia pronto.

Un evento di modifica di <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 per farlo è controllare gli eventi da tastiera su ogni elemento <select> e determinare se il tasto premuto era la conferma della navigazione (Tab o Enter) o la navigazione spaziale (ArrowUp o ArrowDown). Con questa determinazione, il componente può decidere di attendere o di uscire quando viene attivato l'evento per l'elemento <select>.

Conclusione

Ora che sai come ci sono riuscito, come faresti? 🙂

Diversifica i nostri approcci e scopriamo tutti i modi per creare sul web. Crea una demo, inviami un tweet con i link e lo aggiungerò alla sezione Remix della community di seguito.

Remix della community