Navigationspfade

Dieses Muster zeigt, wie Sie eine responsive und zugängliche Breadcrumb-Komponente erstellen, mit der Nutzer auf Ihrer Website navigieren können.

Vollständiger Artikel · Video auf YouTube · Quelle auf GitHub

HTML

<nav class="breadcrumbs" role="navigation">   <a href="./home/">     <span class="crumbicon">       <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">         <use href="#icon-home" />       </svg>     </span>     <span class="home-label">Home</span>   </a>    <span class="crumb-separator" aria-hidden="true">»</span>    <span class="crumb">     <a aria-current="page">Page A</a>     <span class="crumbicon">       <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">         <use href="#icon-dropdown-arrow" />       </svg>       <select class="disguised-select" title="Navigate to another page">         <option selected>Page A</option>         <option>Page B</option>         <option>Page C</option>       </select>     </span>   </span> </nav>  <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>

CSS

         .breadcrumbs {   --nav-gap: 2ch;    display: flex;   align-items: center;   overflow-x: auto;   overscroll-behavior-x: contain;   scroll-snap-type: x proximity;    gap: var(--nav-gap);   padding: calc(var(--nav-gap) / 2);   scroll-padding-inline: calc(var(--nav-gap) / 2);    & > a:first-of-type:not(.crumb) {     display: inline-flex;     align-items: center;     gap: calc(var(--nav-gap) / 4);      @media (width <= 480px) { & > .home-label {       display: none;     }}   }    & a {     text-underline-offset: .25em;     outline-offset: 3px;      /* fix Safari inaccessible dark color scheme links */     /* https://bugs.webkit.org/show_bug.cgi?id=226893 */     @media (prefers-color-scheme: dark) {       @supports (-webkit-hyphens:none) { &[href] {         color: hsl(240 100% 81%);       }}     }   }    & > .crumb:last-of-type {     scroll-snap-align: end;   }    @supports (-webkit-hyphens:none) {     scroll-snap-type: none;   } }  .crumb {   display: inline-flex;   align-items: center;   gap: calc(var(--nav-gap) / 4);    & > a {     white-space: nowrap;      &[aria-current="page"] {       font-weight: bold;     }   }    &.tree-changed ~ * {     display: none;   } }  .crumb-separator {   color: ButtonText; }  .disguised-select {   inline-size: 100%;   block-size: 100%;   opacity: .01;   font-size: min(100%, 16px); }  .crumbicon {   --size: 3ch;    display: grid;   grid: [stack] var(--size) / [stack] var(--size);   place-items: center;   border-radius: 50%;    --icon-shadow-size: 0px;   box-shadow: inset 0 0 0 var(--icon-shadow-size) currentColor;      @media (--motionOK) { & {     transition: box-shadow .2s ease;   }}    @nest .crumb:is(:focus-within, :hover) > & {     --icon-shadow-size: 1px;   }    @nest .crumb > &:is(:focus-within, :hover) {     --icon-shadow-size: 2px;      & svg {       stroke-width: 2px;     }   }    & > * {     grid-area: stack;   }    & > svg {     max-block-size: 100%;     margin: calc(var(--nav-gap) / 4);      stroke: currentColor;     fill: none;     stroke-linecap: round;     stroke-linejoin: round;     stroke-width: 1px;   } }         

JS

         const crumbs         = document.querySelectorAll('.breadcrumbs select') const allowedKeys    = new Set(['Tab', 'Enter', ' ']) const preventedKeys  = new Set(['ArrowUp', 'ArrowDown'])  // watch crumbs for *full* changes, // ensures it's not a user exploring options via keyboard crumbs.forEach(nav => {   let ignoreChange = false    nav.addEventListener('change', e => {     if (ignoreChange) return      const option = e.target     const choice = option.value     const crumb = option.closest('.crumb')      // flag crumb so adjacent siblings can be hidden     crumb.classList.add('tree-changed')      // update crumb text to reflect the user's choice     crumb.querySelector(':scope > a').textContent = choice      routePage(choice)   })    nav.addEventListener('keydown', ({ key }) => {     if (preventedKeys.has(key))       ignoreChange = true     else if (allowedKeys.has(key))       ignoreChange = false   }) })  const routePage = route => {   console.info('change path to: ', route)   // change entire URL (window.location)   // or    // use your favorite clientside framework's router }