<nav aria-label="Sezioni del contenuto">
    <ul class="rtds-tablist rtds-grid md:rtds-flex rtds-border-b rtds-border-gray-01">

        <li>
            <a href="/sezione-1/" class="rtds-tab is-tab rtds-tab--default rtds-w-full md:rtds-w-auto is-active" aria-current="page">

                <span class="rtds-tab__label">Sezione 1</span>

            </a>
        </li>

        <li>
            <a href="/sezione-2/" class="rtds-tab is-tab rtds-tab--default rtds-w-full md:rtds-w-auto">

                <span class="rtds-tab__label">Sezione 2</span>

            </a>
        </li>

        <li>
            <a href="/sezione-3/" class="rtds-tab is-tab rtds-tab--default rtds-w-full md:rtds-w-auto rtds-tab--icon-label rtds-tab--icon-left">

                <svg class="rtds-icon rtds-fill-current rtds-w-4 rtds-h-4 md:rtds-w-5 md:rtds-h-5 lg:rtds-w-6 lg:rtds-h-6" aria-hidden="true" focusable="false" role="img">
                    <use href="../../icons.svg#outline--home-modern" />
                </svg>

                <span class="rtds-tab__label">Sezione 3</span>

            </a>
        </li>

    </ul>
</nav>
<nav aria-label="{{ ariaLabel | default('Sezioni') }}">
  <ul class="rtds-tablist rtds-grid md:rtds-flex rtds-border-b rtds-border-gray-01">
    {% for tab in tabs %}
    {% set tabClasses = 'rtds-w-full md:rtds-w-auto' %}
    {% if tab.iconLeft %}{% set tabClasses = tabClasses ~ ' rtds-tab--icon-label rtds-tab--icon-left' %}{% endif %}
    {% if tab.iconRight %}{% set tabClasses = tabClasses ~ ' rtds-tab--icon-label rtds-tab--icon-right' %}{% endif %}
    <li>
      {% render '@tab', {
        label: tab.label,
        href: tab.href,
        iconLeft: tab.iconLeft,
        iconRight: tab.iconRight,
        isActive: tab.current,
        ariaCurrent: 'page' if tab.current,
        classes: tabClasses
      }, true %}
    </li>
    {% endfor %}
  </ul>
</nav>
{
  "instanceId": "demo",
  "labelledby": "tablist-demo-heading",
  "tabs": [
    {
      "label": "Sezione 1",
      "href": "/sezione-1/",
      "current": true
    },
    {
      "label": "Sezione 2",
      "href": "/sezione-2/"
    },
    {
      "label": "Sezione 3",
      "href": "/sezione-3/",
      "iconLeft": "outline--home-modern"
    }
  ],
  "ariaLabel": "Sezioni del contenuto"
}
  • Content:
    /* TABS */
    /*
     *   This content is licensed according to the W3C Software License at
     *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     *
     *   File:   tabs-manual.js
     *
     *   Desc:   Tablist widget that implements ARIA Authoring Practices
     */
    
    'use strict';
    
    class TabsManualHorizontal {
      constructor(groupNode) {
        this.tablistNode = groupNode;
    
        this.tabs = [];
    
        this.firstTab = null;
        this.lastTab = null;
    
        this.tabs = Array.from(this.tablistNode.querySelectorAll('[role=tab].is-tab'));
        this.tabpanels = [];
    
        for (var i = 0; i < this.tabs.length; i += 1) {
          var tab = this.tabs[i];
          var tabpanel = document.getElementById(tab.getAttribute('aria-controls'));
    
          tab.tabIndex = -1;
          tab.setAttribute('aria-selected', 'false');
          if (tabpanel) this.tabpanels.push(tabpanel);
    
          tab.addEventListener('keydown', this.onKeydown.bind(this));
          tab.addEventListener('click', this.onClick.bind(this));
    
          if (!this.firstTab) {
            this.firstTab = tab;
          }
          this.lastTab = tab;
        }
    
        this.setSelectedTab(this.firstTab);
      }
    
      setSelectedTab(currentTab) {
        for (var i = 0; i < this.tabs.length; i += 1) {
          var tab = this.tabs[i];
          if (currentTab === tab) {
            tab.setAttribute('aria-selected', 'true');
            tab.removeAttribute('tabindex');
            this.tabpanels[i].classList.remove('rtds-hidden');
          } else {
            tab.setAttribute('aria-selected', 'false');
            tab.tabIndex = -1;
            this.tabpanels[i].classList.add('rtds-hidden');
          }
        }
      }
    
      moveFocusToTab(currentTab) {
        currentTab.focus();
      }
    
      moveFocusToPreviousTab(currentTab) {
        var index;
    
        if (currentTab === this.firstTab) {
          this.moveFocusToTab(this.lastTab);
        } else {
          index = this.tabs.indexOf(currentTab);
          this.moveFocusToTab(this.tabs[index - 1]);
        }
      }
    
      moveFocusToNextTab(currentTab) {
        var index;
    
        if (currentTab === this.lastTab) {
          this.moveFocusToTab(this.firstTab);
        } else {
          index = this.tabs.indexOf(currentTab);
          this.moveFocusToTab(this.tabs[index + 1]);
        }
      }
    
      /* EVENT HANDLERS */
    
      onKeydown(event) {
        var tgt = event.currentTarget,
          flag = false;
    
        switch (event.key) {
          case 'ArrowLeft':
            this.moveFocusToPreviousTab(tgt);
            flag = true;
            break;
    
          case 'ArrowUp':
            this.moveFocusToPreviousTab(tgt);
            flag = true;
            break;
    
          case 'ArrowRight':
            this.moveFocusToNextTab(tgt);
            flag = true;
            break;
    
          case 'ArrowDown':
            this.moveFocusToNextTab(tgt);
            flag = true;
            break;
    
          case 'Home':
            this.moveFocusToTab(this.firstTab);
            flag = true;
            break;
    
          case 'End':
            this.moveFocusToTab(this.lastTab);
            flag = true;
            break;
    
          default:
            break;
        }
    
        if (flag) {
          event.stopPropagation();
          event.preventDefault();
        }
      }
    
      // Since this example uses buttons for the tabs, the click onr also is activated
      // with the space and enter keys
      onClick(event) {
        this.setSelectedTab(event.currentTarget);
      }
    }
    
    // Initialize tablist
    window.addEventListener('load', function () {
      var tablistsHorizontal = document.querySelectorAll('[role=tablist].is-manual');
      for (var i = 0; i < tablistsHorizontal.length; i++) {
        new TabsManualHorizontal(tablistsHorizontal[i]);
      }
    });
  • URL: /components/raw/tablist/tablist.js
  • Filesystem Path: components/04-organisms/tablist/tablist.js
  • Size: 3.5 KB
  • Handle: @tablist--navigazione
  • Preview:
  • Filesystem Path: components/04-organisms/tablist/tablist--navigazione.njk
  • References (1): @tab

Tablist

Il componente Tablist renderizza la barra di navigazione tab in due modalità: tabbed interface (button con role="tab", usati dentro @tabbed-interface) e navigazione (link verso pagine o sezioni diverse).


Comportamento

Pattern tabbed interface (default)

Renderizza una lista <ul role="tablist"> con button <button role="tab">. Non include pannelli né JavaScript — il pattern completo con pannelli e JS è in @tabbed-interface.

Il primo tab è attivo di default (aria-selected="true"); i successivi hanno aria-selected="false" e tabindex="-1". Lo stato attivo è comunicato esclusivamente tramite aria-selected, gestito da tablist.js. La classe is-active non viene usata in questo pattern.

Gli ID dei tab seguono il pattern tab-{instanceId}-{n} per supportare più istanze sulla stessa pagina.

Pattern navigazione

Renderizza un landmark <nav> con una lista di link <a>. Nessun pannello, nessun JavaScript. Il link attivo è marcato con aria-current="page" e dalla classe is-active, che applica lo stile visivo del tab selezionato.


Varianti

  • default: Lista di button con role="tab" — da usare dentro @tabbed-interface
  • navigazione: Lista di link in <nav> — standalone, nessun JS

Parametri di configurazione

Contesto di sviluppo Fractal/Nunjucks — Questa sezione riguarda esclusivamente i parametri del context .config.yml usati durante lo sviluppo nella libreria.

Pattern tabbed interface (default)

Parametro Tipo Default Descrizione
tabs array Lista tab. Ogni item: label (stringa, obbligatorio), iconLeft (stringa, opzionale), iconRight (stringa, opzionale)
instanceId stringa 'tablist' Prefisso per gli ID generati (tab-{instanceId}-1, tabpanel-{instanceId}-1). Usare valori univoci se ci sono più tablist sulla stessa pagina
labelledby stringa ID dell’elemento che etichetta il gruppo tab (heading sr-only fornito da @tabbed-interface o da un titolo di sezione esistente)

Pattern navigazione

Parametro Tipo Default Descrizione
ariaLabel stringa 'Sezioni' Etichetta accessibile per il landmark <nav>
tabs array Lista link. Ogni item: label (obbligatorio), href (obbligatorio), current (booleano, opzionale), iconLeft (stringa, opzionale), iconRight (stringa, opzionale)

Accessibilità

  • <ul role="tablist" aria-labelledby> — etichettato dall’elemento referenziato da labelledby
  • <li role="presentation"> — rimuove la semantica lista ridondante nel widget ARIA
  • <button role="tab" aria-selected aria-controls> — ogni tab dichiara stato e pannello collegato
  • Tab non selezionati: tabindex="-1", esclusi dalla navigazione Tab lineare (roving tabindex)
  • La navigazione da tastiera (frecce, Home, End) è gestita da tablist.js, inizializzato su [role=tablist].is-manual

Stili CSS

  • rtds-tablist — contenitore della lista tab (su <ul>)

Il layout è responsivo: rtds-grid su mobile (voci impilate in colonna), md:rtds-flex su desktop (voci affiancate orizzontalmente). Il breakpoint è md (768px).

Ogni bottone / link interno riceve rtds-w-full md:rtds-w-auto: larghezza piena su mobile, auto su desktop.


Note

  • Il componente rende solo la barra; per il pattern completo con pannelli usare @tabbed-interface
  • tablist.js si inizializza su [role=tablist].is-manual — la classe è applicata solo quando il tablist è usato dentro @tabbed-interface; la variante navigazione è HTML statico, nessuna inizializzazione
  • La classe is-manual indica attivazione manuale: le frecce spostano il focus ma non attivano il pannello; serve Enter o Space
  • tablist.js gestisce esclusivamente aria-selected — non aggiunge né rimuove is-active. Nel pattern navigazione non c’è JS: is-active è impostata server-side dal template NJK tramite il flag current di ogni tab.
  • Nel pattern navigazione, i <li> mantengono la semantica lista nativa — non aggiungere role="presentation"
  • Dipende da: @tab (molecola)