Creazione di un componente breadcrumb

Una panoramica di base su come creare un componente di breadcrumb reattivo e accessibile per consentire agli utenti di navigare sul tuo sito.

In questo post voglio parlare di un modo per creare componenti dei breadcrumb. Prova la demo.

Demo

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

Panoramica

Un componente breadcrumb mostra dove si trova l'utente nella gerarchia del sito. 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, 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 in background

Nel video dimostrativo dei componenti riportato sopra, le categorie segnaposto sono generi di videogiochi. Questo sentiero 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 di breadcrumb di questo post, le raccolte sono FPS, RPG, brawler, dungeon crawler, sport e rompicapi.

Elementi

Un videogioco è un elemento; anche una raccolta specifica potrebbe 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 trovano nella pagina RPG, in cui viene visualizzato un elenco di giochi RPG, incluse le sottocategorie aggiuntive AAA, Indie e Self Publication.

In termini informatici, 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 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

Markup

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 comunica 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, perciò utilizzeranno i colori predefiniti forniti dal browser.

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

Per la navigazione del sito, è appropriato utilizzare l'elemento <nav>, che ha un ruolo di navigazione ARIA implicito. Durante il test, ho notato che l'attributo role ha modificato il modo in cui uno screen reader interagisce con l'elemento, in realtà è stato annunciato come navigazione, quindi ho scelto di aggiungerlo.

Icone

Quando un'icona viene ripetuta su una pagina, per l'elemento SVG <use> puoi definire path una volta e utilizzarlo per tutte le istanze dell'icona. In questo modo, le informazioni sul percorso non vengono ripetute, causando documenti di dimensioni maggiori e possibile incoerenza del percorso.

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>

DevTools che mostra un elemento di utilizzo SVG sottoposto a rendering.

Da definire una volta sola e utilizzare tutte le volte che vuoi, con un impatto minimo sulle prestazioni della pagina e uno stile flessibile. Nota che aria-hidden="true" viene aggiunto all'elemento SVG. Le icone non sono utili a chi naviga e ascolta solo i contenuti; nasconderle per impedire agli utenti di aggiungere rumore non necessario.

È qui che divergono il breadcrumb tradizionale e quelli in questo componente. Normalmente, si tratta di un solo link <a>, ma ho aggiunto un'esperienza utente di attraversamento con una selezione camuffata. La classe .crumb si occupa della disposizione del link e dell'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 un pulsante-split, 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, che forniscono 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.

Screenshot con l&#39;elemento di selezione invisibile che passa il mouse sopra e la descrizione comando contestuale visualizzata.

Decorazioni separatore

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

I separatori sono facoltativi, quindi anche aggiungerne uno funziona molto bene (guarda il terzo esempio del video sopra). Poi do ogni aria-hidden="true" perché sono decorativi e non qualcosa che uno screen reader deve annunciare.

La proprietà gap, trattata di seguito, semplifica la spaziatura di questi elementi.

Stili

Poiché il colore utilizza i colori del sistema, si tratta principalmente di spazi vuoti e impilati per gli stili.

Direzione e flusso del layout

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

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 le briciole, i divisori e le 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 gli overlay flexbox.

Ogni .crumb stabilisce anche un layout orizzontale allineato verticalmente con alcuni gap, ma sceglie come target i relativi elementi secondari dei link e specifica lo stile white-space: nowrap. Questo è fondamentale per i breadcrumb di più parole, perché non vogliamo che passino 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;
    }
  }
}

Il link aria-current="page" viene aggiunto per aiutare il link alla pagina corrente a distinguersi dal resto. Non solo gli utenti di screen reader noteranno un chiaro indicatore del fatto che il link si riferisce alla pagina corrente, ma abbiamo stilizzato visivamente l'elemento in modo da 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 griglia mostrati sono sovrapposti a un pulsante, in cui la riga e la colonna sono entrambe
stilate con nomi.

.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 allo stack ed è interattivo. Aggiungi uno stile opacity: .01 in modo che l'elemento sia ancora utilizzabile e 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 al contempo 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 dovrebbero essere in grado di rappresentare una scia molto lunga. Mi piace lasciare che i contenuti vadano fuori schermo orizzontalmente, quando opportuno, e ho ritenuto che questo componente dei breadcrumb fosse adeguato.

.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 overscroll.
  • Spaziatura interna di scorrimento orizzontale.
  • Un punto di scatto sull'ultima briciola. Ciò significa che al caricamento della pagina la prima crumb è 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 supporti

Una piccola modifica alle aree visibili più piccole consiste nel nascondere l'etichetta "Casa", lasciando solo l'icona:

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

Affiancato ai breadcrumb con e senza etichetta home, per il confronto.

Accessibilità

Movimento

Non c'è molto movimento in questo componente, 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 di passaggio del mouse e di messa a fuoco sono eccezionali 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 sito o nell'applicazione, quando un utente modifica i breadcrumb, l'URL deve essere aggiornato e l'utente mostra la pagina appropriata. In secondo luogo, per normalizzare l'esperienza utente, assicurati che non avvengano navigazioni impreviste quando gli utenti stanno solo sfogliando le opzioni di <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>. Su Windows Edge e probabilmente anche in altri browser, l'evento changed selezionato si attiva mentre 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> migliore modificato

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 nel controllare gli eventi con la tastiera su ogni elemento <select> e determinare se il tasto premuto corrisponde alla conferma della navigazione (Tab o Enter) o alla navigazione spaziale (ArrowUp o ArrowDown). Con questa determinazione, il componente può decidere di attendere o di andare, quando si attiva l'evento per l'elemento <select>.

Conclusione

Ora che sai come ci ho fatto, come faresti‽ 🙂

Diversificaamo 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