<nav aria-labelledby="sidebarLabel" id="sideNav">
    <h2 id="sidebarLabel" class="n7-content-05 pb-6 border-b n7-border-gray-01 text-base font-medium uppercase ">
        Titolo navigazione
    </h2>
    <ul class="n7-side-navigation has-nav-dropdown">

        <li class="n7-side-navigation__item bg-white flex flex-wrap items-stretch text-base font-medium is-current-trail">
            <span class="is-first-level flex flex-1 is-current">
                <a href="#" class="n7-nav-link flex flex-1 items-center gap-2 p-4 transition transition-all hover:n7-background-gray-02 hover:underline is-current" aria-current="page">

                    Current trail and current page
                </a>

                <button class="n7-nav-toggle flex items-center justify-center w-8 hover:n7-background-gray-02 group  is-current" aria-expanded="true" aria-controls="sideNav-subnav-1">
                    <span class="sr-only">Dettaglio Current trail and current page</span>

                    <svg class="inline-block align-middle fill-current w-4 h-4 transition-all duration-200 ease-out group-aria-expanded:-rotate-180" aria-hidden="true" focusable="false" role="img">
                        <use xlink:href="../../icons.svg#mini--chevron-down" />
                    </svg>

                </button>
            </span>
            <ul class="n7-side-navigation__submenu block w-full" id="sideNav-subnav-1">

                <li>
                    <a href="" class="block p-4 transition bg-white text-sm xl:text-base hover:underline hover:n7-background-gray-02 focus:n7-background-gray-02">Subitem 2</a>
                </li>

            </ul>

        </li>

        <li class="n7-side-navigation__item bg-white flex flex-wrap items-stretch text-base font-medium is-current-trail">
            <span class="is-first-level flex flex-1">
                <a href="#" class="n7-nav-link flex flex-1 items-center gap-2 p-4 transition transition-all hover:n7-background-gray-02 hover:underline">

                    Current trail
                </a>

                <button class="n7-nav-toggle flex items-center justify-center w-8 hover:n7-background-gray-02 group " aria-expanded="true" aria-controls="sideNav-subnav-2">
                    <span class="sr-only">Dettaglio Current trail</span>

                    <svg class="inline-block align-middle fill-current w-4 h-4 transition-all duration-200 ease-out group-aria-expanded:-rotate-180" aria-hidden="true" focusable="false" role="img">
                        <use xlink:href="../../icons.svg#mini--chevron-down" />
                    </svg>

                </button>
            </span>
            <ul class="n7-side-navigation__submenu block w-full" id="sideNav-subnav-2">

                <li>
                    <a href="" class="block p-4 transition bg-white text-sm xl:text-base hover:underline hover:n7-background-gray-02 focus:n7-background-gray-02 is-current" aria-current="page">Subitem 1 current page</a>
                </li>

            </ul>

        </li>

        <li class="n7-side-navigation__item bg-white flex flex-wrap items-stretch text-base font-medium">
            <span class="is-first-level flex flex-1">
                <a href="#" class="n7-nav-link flex flex-1 items-center gap-2 p-4 transition transition-all hover:n7-background-gray-02 hover:underline">

                    Item 2
                </a>

                <button class="n7-nav-toggle flex items-center justify-center w-8 hover:n7-background-gray-02 group " aria-expanded="false" aria-controls="sideNav-subnav-3">
                    <span class="sr-only">Dettaglio Item 2</span>

                    <svg class="inline-block align-middle fill-current w-4 h-4 transition-all duration-200 ease-out group-aria-expanded:-rotate-180" aria-hidden="true" focusable="false" role="img">
                        <use xlink:href="../../icons.svg#mini--chevron-down" />
                    </svg>

                </button>
            </span>
            <ul class="n7-side-navigation__submenu hidden w-full" id="sideNav-subnav-3">

                <li>
                    <a href="" class="block p-4 transition bg-white text-sm xl:text-base hover:underline hover:n7-background-gray-02 focus:n7-background-gray-02">Subitem 2</a>
                </li>

            </ul>

        </li>

        <li class="n7-side-navigation__item bg-white flex flex-wrap items-stretch text-base font-medium">
            <span class="is-first-level flex flex-1">
                <a href="#" class="n7-nav-link flex flex-1 items-center gap-2 p-4 transition transition-all hover:n7-background-gray-02 hover:underline">

                    Item 4
                </a>

            </span>

        </li>

        <li class="n7-side-navigation__item bg-white flex flex-wrap items-stretch text-base font-medium">
            <span class="is-first-level flex flex-1 is-current">
                <a href="#" class="n7-nav-link flex flex-1 items-center gap-2 p-4 transition transition-all hover:n7-background-gray-02 hover:underline is-current" aria-current="page">

                    Item 5 current page
                </a>

            </span>

        </li>

    </ul>
</nav>
<nav aria-labelledby="{{ labelId }}"{% if id %} id="{{ id }}"{% endif %}>
  <h2 id="{{ labelId }}" class="n7-content-05 pb-6 border-b n7-border-gray-01 text-base font-medium uppercase {% if hiddenLabel %} sr-only{% endif %}">
    {{ label }}
  </h2>
  <ul class="n7-side-navigation has-nav-dropdown">
    {% for firstLevel in firstLevels %}
      <li class="n7-side-navigation__item bg-white flex flex-wrap items-stretch text-base font-medium{% if itemClasses %} {{ itemClasses }}{% endif %}{% if firstLevel.isCurrentTrail %} is-current-trail{% endif %}">
        <span class="is-first-level flex flex-1{% if firstLevel.isCurrentPage %} is-current{% endif %}">
          <a href="#" class="n7-nav-link flex flex-1 items-center gap-2 p-4 transition transition-all hover:n7-background-gray-02 hover:underline{% if firstLevelLinkClasses %} {{ firstLevelLinkClasses }}{% endif %}{% if firstLevel.isCurrentPage %} is-current{% endif %}"{% if firstLevel.isCurrentPage %} aria-current="page"{% endif %}>
            {% if firstLevel.iconLeft %}
              {% render '@icon--small', { id: firstLevel.iconLeft, size: 'w-4 h-4' }, true %}
            {% endif %}
            {{ firstLevel.label }}
          </a>
        
          {% if not firstLevel.subItems %}
          </span>
        {% endif %}

      {% if firstLevel.subItems %}
        <button
          class="n7-nav-toggle flex items-center justify-center w-8 hover:n7-background-gray-02 group {% if firstLevel.isCurrentPage %} is-current{% endif %}"
          aria-expanded="{% if firstLevel.isCurrentTrail %}true{% else %}false{% endif %}"
          aria-controls="{{ id }}-subnav-{{ loop.index }}"
        >
          <span class="sr-only">Dettaglio {{ firstLevel.label }}</span>
          {% set buttonIcon = "" %}
          {% if firstLevel.iconRight %}
           {% set buttonIcon = firstLevel.iconRight %}
          {% else %}
           {% set buttonIcon = "mini--chevron-down" %}
          {% endif %}
          {% render '@icon--small', { id: buttonIcon, classes: 'transition-all duration-200 ease-out group-aria-expanded:-rotate-180', size: 'w-4 h-4' }, true %}
        </button>
        </span>
        <ul class="n7-side-navigation__submenu {% if firstLevel.isCurrentTrail %}block {% else %}hidden {% endif %}w-full{% if subnavClasses %} {{ subnavClasses }}{% endif %}" id="{{ id }}-subnav-{{ loop.index }}">
          {% for item in firstLevel.subItems %}
            <li>
              <a href="" class="block p-4 transition bg-white text-sm xl:text-base hover:underline hover:n7-background-gray-02 focus:n7-background-gray-02{% if subnavLinkClasses %} {{ subnavLinkClasses }}{% endif %}{% if item.isCurrentPage %} is-current{% endif %}"{% if item.isCurrentPage %} aria-current="page"{% endif %}>{{ item.subitemLabel }}</a>
            </li>
          {% endfor %}
        </ul>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>
{
  "label": "Titolo navigazione",
  "labelId": "sidebarLabel",
  "id": "sideNav",
  "firstLevels": [
    {
      "label": "Current trail and current page",
      "isCurrentTrail": true,
      "isCurrentPage": true,
      "subItems": [
        {
          "subitemLabel": "Subitem 2"
        }
      ]
    },
    {
      "label": "Current trail",
      "isCurrentTrail": true,
      "subItems": [
        {
          "subitemLabel": "Subitem 1 current page",
          "isCurrentPage": true
        }
      ]
    },
    {
      "label": "Item 2",
      "subItems": [
        {
          "subitemLabel": "Subitem 2"
        }
      ]
    },
    {
      "label": "Item 4"
    },
    {
      "label": "Item 5 current page",
      "isCurrentPage": true
    }
  ]
}
  • Content:
    /*
     *   This content is licensed according to the W3C Software License at
     *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     *
     *   Supplemental JS for the disclosure menu keyboard behavior
     */
    
    'use strict';
    // Helper function to find the closest ancestor with a specific tag name
    function findAncestor(element, tagName) {
      while (element) {
        if (element.tagName.toLowerCase() === tagName) {
          return element;
        }
        element = element.parentElement;
      }
      return null;
    }
    
    class submenuDisclosure {
      constructor(domNode) {
        this.rootNode = domNode;
        this.controlledNodes = [];
        this.openIndex = null;
        this.useArrowKeys = true;
        this.topLevelNodes = [
          ...this.rootNode.querySelectorAll(
            '.n7-nav-link, .n7-nav-toggle'
          ),
        ];
    
        this.topLevelNodes.forEach((node) => {
          // handle button + menu
          if (
            node.tagName.toLowerCase() === 'button' &&
            node.hasAttribute('aria-controls')
          ) {
            const menuLiParent = node.closest('li');
            const menu = menuLiParent.querySelector('ul');
    
            if (menu) {
              // save ref controlled menu
              this.controlledNodes.push(menu);
    
              // collapse menus
              // if(menuLiParent.classList.contains('is-current-trail','has-current-child')) {
              if (menu.querySelector('.is-current')) {
                node.setAttribute('aria-expanded', 'true');
                this.toggleMenu(menu, true);
      
              } else {
                node.setAttribute('aria-expanded', 'false');
                this.toggleMenu(menu, false);
              }
    
              // attach event listeners
              menu.addEventListener('keydown', this.onMenuKeyDown.bind(this));
              node.addEventListener('click', this.onButtonClick.bind(this));
              node.addEventListener('keydown', this.onButtonKeyDown.bind(this));
            }
          }
          // handle links
          else {
            this.controlledNodes.push(null);
            node.addEventListener('keydown', this.onLinkKeyDown.bind(this));
          }
        });
    
        this.rootNode.addEventListener('focusout', this.onBlur.bind(this));
      }
    
      controlFocusByKey(keyboardEvent, nodeList, currentIndex) {
        switch (keyboardEvent.key) {
          case 'ArrowUp':
          case 'ArrowLeft':
            keyboardEvent.preventDefault();
            if (currentIndex > -1) {
              var prevIndex = Math.max(0, currentIndex - 1);
              nodeList[prevIndex].focus();
            }
            break;
          case 'ArrowDown':
          case 'ArrowRight':
            keyboardEvent.preventDefault();
            if (currentIndex > -1) {
              var nextIndex = Math.min(nodeList.length - 1, currentIndex + 1);
              nodeList[nextIndex].focus();
            }
            break;
          case 'Home':
            keyboardEvent.preventDefault();
            nodeList[0].focus();
            break;
          case 'End':
            keyboardEvent.preventDefault();
            nodeList[nodeList.length - 1].focus();
            break;
        }
      }
    
      // public function to close open menu
      close() {
        this.toggleExpand(this.openIndex, false);
      }
    
      onBlur(event) {
        var menuContainsFocus = this.rootNode.contains(event.relatedTarget);
        if (!menuContainsFocus && this.openIndex !== null) {
          this.toggleExpand(this.openIndex, false);
        }
      }
    
      onButtonClick(event) {
        var target = event.target;
    
        // Check if the target is not a button but is a descendant of a button
        if (target.tagName.toLowerCase() !== 'button') {
          var buttonAncestor = findAncestor(target, 'button');
    
          // If an ancestor button is found, trigger the click on that button
          if (buttonAncestor) {
            buttonAncestor.click();
            return; // Stop further processing since the click is handled
          }
        }
    
        var button = event.target;
        var buttonIndex = this.topLevelNodes.indexOf(button);
        var buttonExpanded = button.getAttribute('aria-expanded') === 'true';
        this.toggleExpand(buttonIndex, !buttonExpanded);
    
        console.log(event.target);
      }
    
      onButtonKeyDown(event) {
        var targetButtonIndex = this.topLevelNodes.indexOf(document.activeElement);
    
        // close on escape
        if (event.key === 'Escape') {
          this.toggleExpand(this.openIndex, false);
        }
    
        // move focus into the open menu if the current menu is open
        else if (
          this.useArrowKeys &&
          this.openIndex === targetButtonIndex &&
          event.key === 'ArrowDown'
        ) {
          event.preventDefault();
          this.controlledNodes[this.openIndex].querySelector('a').focus();
        }
    
        // handle arrow key navigation between top-level buttons, if set
        else if (this.useArrowKeys) {
          this.controlFocusByKey(event, this.topLevelNodes, targetButtonIndex);
        }
      }
    
      onLinkKeyDown(event) {
        var targetLinkIndex = this.topLevelNodes.indexOf(document.activeElement);
    
        // handle arrow key navigation between top-level buttons, if set
        if (this.useArrowKeys) {
          this.controlFocusByKey(event, this.topLevelNodes, targetLinkIndex);
        }
      }
    
      onMenuKeyDown(event) {
        if (this.openIndex === null) {
          return;
        }
    
        var menuLinks = Array.prototype.slice.call(
          this.controlledNodes[this.openIndex].querySelectorAll('a')
        );
        var currentIndex = menuLinks.indexOf(document.activeElement);
    
        // close on escape
        if (event.key === 'Escape') {
          this.topLevelNodes[this.openIndex].focus();
          this.toggleExpand(this.openIndex, false);
        }
    
        // handle arrow key navigation within menu links, if set
        else if (this.useArrowKeys) {
          this.controlFocusByKey(event, menuLinks, currentIndex);
        }
      }
    
      toggleExpand(index, expanded) {
        // close open menu, if applicable
        if (this.openIndex !== index) {
          this.toggleExpand(this.openIndex, false);
        }
    
        // handle menu at called index
        if (this.topLevelNodes[index]) {
          this.openIndex = expanded ? index : null;
          this.topLevelNodes[index].setAttribute('aria-expanded', expanded);
          this.toggleMenu(this.controlledNodes[index], expanded);
        }
      }
    
      toggleMenu(domNode, show) {
        if (domNode) {
          domNode.style.display = show ? 'block' : 'none';
        }
      }
    
      updateKeyControls(useArrowKeys) {
        this.useArrowKeys = useArrowKeys;
      }
    }
    
    /* Initialize Disclosure Menus */
    
    window.addEventListener(
      'load',
      function () {
        var menus = document.querySelectorAll('.has-nav-dropdown');
        var disclosureMenus = [];
    
        for (var i = 0; i < menus.length; i++) {
          disclosureMenus[i] = new submenuDisclosure(menus[i]);
        }
    
        // listen to arrow key checkbox
        var arrowKeySwitch = document.getElementById('arrow-behavior-switch');
        if (arrowKeySwitch) {
          arrowKeySwitch.addEventListener('change', function () {
            var checked = arrowKeySwitch.checked;
            for (var i = 0; i < disclosureMenus.length; i++) {
              disclosureMenus[i].updateKeyControls(checked);
            }
          });
        }
    
        // fake link behavior
        disclosureMenus.forEach((submenuDisclosure, i) => {
          var links = menus[i].querySelectorAll('[href="#mythical-page-content"]');
          var examplePageHeading = document.getElementById('mythical-page-heading');
          for (var k = 0; k < links.length; k++) {
            // The codepen export script updates the internal link href with a full URL
            // we're just manually fixing that behavior here
            links[k].href = '#mythical-page-content';
    
            links[k].addEventListener('click', (event) => {
              // change the heading text to fake a page change
              var pageTitle = event.target.innerText;
              examplePageHeading.innerText = pageTitle;
    
              // handle aria-current
              for (var n = 0; n < links.length; n++) {
                links[n].removeAttribute('aria-current');
              }
              event.target.setAttribute('aria-current', 'page');
            });
          }
        });
      },
      false
    );
    
  • URL: /components/raw/side-navigation/side-navigation.js
  • Filesystem Path: components/04-organisms/side-navigation/side-navigation.js
  • Size: 7.8 KB

Sidebar navigation item. In the example, there are two current page, just for showing how configurations work, actually there will be just one is-current/current-page item.

Configurations:

  • label: ‘’ - Navigation title - used also for accessibility
  • labelId: ‘’ - used for accessibility - in arialabelledby attribute on nav
  • hiddenLabel: true - hide visually navigation title, but maintains it for screen readers
  • id: ‘’ - nav id if needed
  • itemClasses: ‘’ - class for first level item
  • firstLevelLinkClasses: ‘’ - classes for first level link text
  • subnavClasses: ‘’ - classes for submnenu list
  • subnavLinkClasses: ‘’ - classes for submenu links
  • firstLevels: set navigation first levels:
    • label: ‘’ - item label
    • iconLeft: id - id for eventual left icon
    • iconRight: id - id for eventual right icon - default is mini–chevron-down
    • isCurrentTrail: true - if trail is current (first level or child item is current section)
    • isCurrentPage: true - if item is current page, set class and aria-current on link
    • subItems: settings for child items
      • subitemLabel: ‘’ - label for item
      • isCurrentPage: true - if item is current page, set class and aria-current on link