<ul role="tablist" aria-labelledby="tablist-demo-heading" class="rtds-tablist is-tablist is-manual rtds-border-b rtds-border-gray-01 rtds-mb-4 md:rtds-mb-10 rtds-grid md:rtds-flex">
<li role="presentation">
<button class="rtds-tab is-tab rtds-tab--default rtds-w-full md:rtds-w-auto" role="tab" id="tab-demo-1" aria-selected="true" aria-controls="tabpanel-demo-1">
<span class="rtds-tab__label">Voce 1</span>
</button>
</li>
<li role="presentation">
<button class="rtds-tab is-tab rtds-tab--default rtds-w-full md:rtds-w-auto" role="tab" id="tab-demo-2" aria-selected="false" tabindex="-1" aria-controls="tabpanel-demo-2">
<span class="rtds-tab__label">Voce 2</span>
</button>
</li>
<li role="presentation">
<button class="rtds-tab is-tab rtds-tab--default rtds-w-full md:rtds-w-auto" role="tab" id="tab-demo-3" aria-selected="false" tabindex="-1" aria-controls="tabpanel-demo-3">
<span class="rtds-tab__label">Voce 3</span>
</button>
</li>
</ul>
{% set _instanceId = instanceId if instanceId else 'tablist' %}
<ul
role="tablist"
{% if ariaLabel %}aria-label="{{ ariaLabel }}"{% elif labelledby %}aria-labelledby="{{ labelledby }}"{% endif %}
class="rtds-tablist is-tablist is-manual rtds-border-b rtds-border-gray-01 rtds-mb-4 md:rtds-mb-10 rtds-grid md:rtds-flex"
>
{% for tab in tabs %}
{% set tabId = 'tab-' ~ _instanceId ~ '-' ~ loop.index %}
{% set panelId = 'tabpanel-' ~ _instanceId ~ '-' ~ loop.index %}
{% 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 role="presentation">
{% render '@tab', {
label: tab.label,
iconLeft: tab.iconLeft,
iconRight: tab.iconRight,
classes: tabClasses,
href: '',
id: tabId,
tabIndex: not loop.first,
ariaSelected: 'true' if loop.first else 'false',
ariaControls: panelId,
roleTab: true
}, true %}
</li>
{% endfor %}
</ul>
{
"instanceId": "demo",
"labelledby": "tablist-demo-heading",
"tabs": [
{
"label": "Voce 1"
},
{
"label": "Voce 2"
},
{
"label": "Voce 3"
}
]
}
/* 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]);
}
});
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).
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.
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.
default: Lista di button con role="tab" — da usare dentro @tabbed-interfacenavigazione: Lista di link in <nav> — standalone, nessun JSContesto di sviluppo Fractal/Nunjucks — Questa sezione riguarda esclusivamente i parametri del context
.config.ymlusati durante lo sviluppo nella libreria.
| 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) |
| 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) |
<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 collegatotabindex="-1", esclusi dalla navigazione Tab lineare (roving tabindex)tablist.js, inizializzato su [role=tablist].is-manualrtds-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.
@tabbed-interfacetablist.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 inizializzazioneis-manual indica attivazione manuale: le frecce spostano il focus ma non attivano il pannello; serve Enter o Spacetablist.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.<li> mantengono la semantica lista nativa — non aggiungere role="presentation"@tab (molecola)