<a href="#" class="rtds-tab is-tab rtds-tab--default  is-disabled" tabindex="-1" aria-disabled="true">

    <span class="rtds-tab__label">Tab disabilitato</span>

</a>
<{% if href %}a href="{{ href }}"{% else %}button{% endif %} class="rtds-tab is-tab{% block classes %} rtds-tab--default {{ classes }}{% endblock %}{% if isActive %} is-active{% endif %}{% if isDisabled %} is-disabled{% endif %}"
{% if roleTab %}role="tab"{% endif %}
{% if id %}id="{{ id }}"{% endif %}
{% if ariaSelected %}aria-selected="{{ ariaSelected }}"{% endif %}
{% if tabIndex or (isDisabled and href) %}tabindex="-1"{% endif %}
{% if ariaControls %}aria-controls="{{ ariaControls }}"{% endif %}
{% if ariaCurrent %}aria-current="{{ ariaCurrent }}"{% endif %}
{% if isDisabled %}{% if not href %}disabled{% else %}aria-disabled="true"{% endif %}{% endif %}
>
    {% if iconLeft %}
        {% render '@icon--small', { id: iconLeft, size: 'rtds-w-4 rtds-h-4 md:rtds-w-5 md:rtds-h-5 lg:rtds-w-6 lg:rtds-h-6' }, true %}
    {% endif %}
    <span class="rtds-tab__label{% if iconOnly %} rtds-sr-only{% endif %}">{{ label }}</span>

    {% if iconRight %}
        {% render '@icon--small', { id: iconRight, size: 'rtds-w-4 rtds-h-4 md:rtds-w-5 md:rtds-h-5 lg:rtds-w-6 lg:rtds-h-6' }, true %}
    {% endif %}
</{% if href %}a{% else %}button{% endif %}>
{
  "label": "Tab disabilitato",
  "href": "#",
  "isDisabled": true
}
  • Content:
    /**
     * TAB
     * 
     *
    */
    /* BASE */
    .rtds-tab {
        display: flex;
        justify-content: center;
        font-weight: 700;
        transition-property: all;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
        transition-duration: 150ms;
    }
    .rtds-tab:hover {
            background-color: var(--color-background-02);
        }
    .rtds-tab--default {
        border-bottom-width: 2px;
        border-color: transparent;
        padding: var(--spacing-2);
        font-size: var(--fontsize-text-sm);
            color: var(--color-content-02);
    }
    .rtds-tab--default:hover {
            background-color: var(--color-background-02);
        }
    @media (min-width: 768px) {
        .rtds-tab--default {
            padding: var(--spacing-3);
            font-size: var(--fontsize-text-base);
        }
    }
    .rtds-tab--default.is-active,
        .rtds-tab--default[aria-selected="true"] {
        border-color: currentColor;
            color: var(--color-content-primary);
    }
    /* BORDERLESS */
    .rtds-tab--borderless {
        padding: var(--spacing-4);
            background-color: var(--color-background-03);
    }
    @media (min-width: 1024px) {
        .rtds-tab--borderless {
            padding-top: var(--spacing-6);
            padding-bottom: var(--spacing-6);
            padding-left: var(--spacing-12);
            padding-right: var(--spacing-12);
        }
    }
    @media (min-width: 1280px) {
        .rtds-tab--borderless {
            padding-top: var(--spacing-6);
            padding-bottom: var(--spacing-6);
            padding-left: var(--spacing-16);
            padding-right: var(--spacing-16);
        }
    }
    .rtds-tab--borderless.is-active,
        .rtds-tab--borderless[aria-selected="true"] {
        --tw-bg-opacity: 1;
        background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
            color: var(--color-content-primary);
    }
    .rtds-tab--icon-label {
        gap: var(--spacing-2);
    }
    /* DISABLED */
    .rtds-tab.is-disabled,
        .rtds-tab.is-disabled:hover {
            color: var(--color-content-disabled);
            background-color: transparent;
            border-color: transparent;
            cursor: not-allowed;
        }
    
  • URL: /components/raw/tab/tab.built.css
  • Filesystem Path: components/03-molecules/tab/tab.built.css
  • Size: 2 KB
  • Content:
    /**
     * TAB
     * 
     *
    */
    /* BASE */
    @layer components {
        .rtds-tab {
            @apply rtds-flex rtds-justify-center rtds-font-bold hover:rtds-background-02 rtds-transition-all;
        }
    
        .rtds-tab--default {
            @apply rtds-content-02 rtds-p-2 md:rtds-p-3 rtds-border-b-2 rtds-border-transparent rtds-text-sm md:rtds-text-base hover:rtds-background-02;
        }
    
        .rtds-tab--default.is-active,
        .rtds-tab--default[aria-selected="true"] {
            @apply rtds-content-primary rtds-border-current;
        }
    
        /* BORDERLESS */
        .rtds-tab--borderless {
            @apply rtds-p-4 lg:rtds-py-6 lg:rtds-px-12 xl:rtds-py-6 xl:rtds-px-16 rtds-background-03;
        }
    
        .rtds-tab--borderless.is-active,
        .rtds-tab--borderless[aria-selected="true"] {
            @apply rtds-bg-white rtds-content-primary;
        }
        
        .rtds-tab--icon-label {
            @apply rtds-gap-2;
        }
    
        /* DISABLED */
        .rtds-tab.is-disabled,
        .rtds-tab.is-disabled:hover {
            color: var(--color-content-disabled);
            background-color: transparent;
            border-color: transparent;
            cursor: not-allowed;
        }
    }
  • URL: /components/raw/tab/tab.css
  • Filesystem Path: components/03-molecules/tab/tab.css
  • Size: 1.1 KB

Tab

Il componente Tab gestisce la visualizzazione di tab singoli, utilizzabile sia come link che come pulsante. È progettato per essere accessibile e responsive, supportando diverse configurazioni di layout e stile.

Per il pattern completo con gruppo di tab, pannelli di contenuto o navigazione a sezioni, vedi il componente @tablist (04-organisms/tablist), che usa questo componente come building block.


Comportamento

Il componente può essere renderizzato come:

  • <a href="..."> — quando viene specificato href nel contesto Fractal/Nunjucks
  • <button> — quando href non è specificato nel contesto Fractal/Nunjucks

Il tag HTML non determina da solo il comportamento semantico: il contesto d’uso definisce quali attributi ARIA applicare. Esistono tre scenari distinti.

Scenario A — Tab in tablist custom (con <button>)

Il <button> è usato per attivare/disattivare un pannello tramite JavaScript. In questo caso il tab fa parte di una struttura role="tablist" e richiede la semantica ARIA completa del pattern tab:

  • role="tab" sull’elemento
  • aria-selected="true|false" per comunicare lo stato
  • aria-controls="[id-del-tabpanel]" per collegare il tab al pannello
  • tabindex="-1" sui tab non selezionati (navigazione con frecce, non con Tab)

Scenario B — Tab in tablist custom (con <a>)

Un <a> può essere usato come tab all’interno di una struttura tablist gestita via JavaScript, esattamente come il <button>. In questo caso si applicano gli stessi attributi ARIA dello Scenario A. L’href dovrebbe puntare all’id del tabpanel (es. href="#panel-1"), così il collegamento semantico è preservato anche senza JS.

Questo scenario richiede che il componente tablist parent sovrascriva il comportamento nativo del link (prevenendo la navigazione e gestendo l’attivazione via JS).

Un <a> può essere usato come elemento di navigazione tradizionale — link diretto a un’altra pagina o link ad ancora a una sezione della stessa pagina. In questo caso il tab non fa parte di un tablist e non deve avere role="tab", aria-selected, aria-controls o tabindex="-1". L’elemento mantiene il suo ruolo implicito di link.

Se il link corrisponde alla pagina o sezione corrente, si può usare aria-current="page" o aria-current="true" al posto di aria-selected.


Varianti

Varianti di Stile

  • default: Variante base del tab
  • borderless: Variante senza bordo

Varianti di Stato

  • active: Variante con stato attivo (isActive: true) — usare nel pattern navigazione (Scenario C). Nel pattern tablist, lo stato attivo deriva da aria-selected="true" via CSS, non da questa classe.
  • disabled: Variante con stato disabilitato su <a>aria-disabled="true", tabindex="-1", classe is-disabled (isDisabled: true)
  • disabled-button: Variante con stato disabilitato su <button> — attributo disabled, classe is-disabled (isDisabled: true, href: '')

Varianti per pattern d’uso

Le seguenti varianti mostrano il markup corretto per i due scenari principali di integrazione:

  • tablist-attivo: Pattern tablist ARIA — <button> selezionato con role="tab", aria-selected="true", aria-controls
  • tablist-inattivo: Pattern tablist ARIA — <button> non selezionato con tabindex="-1", aria-selected="false"
  • navigazione-attivo: Pattern navigazione — <a> con aria-current="page" per il link attivo
  • navigazione-inattivo: Pattern navigazione — <a> senza attributi ARIA aggiuntivi

Le varianti visive presentate in Fractal mostrano la resa grafica del componente ma non riflettono necessariamente i diversi pattern di utilizzo documentati sopra. Fare riferimento agli Esempi di utilizzo per i markup corretti nei tre scenari.


Parametri di configurazione

Contesto di sviluppo Fractal/Nunjucks — Questa sezione riguarda esclusivamente i parametri del context .config.yml usati durante lo sviluppo nella libreria. Non descrive attributi HTML o proprietà da usare nei temi, CMS o ambienti di implementazione: per quello, fare riferimento alla sezione Esempi di utilizzo.

Parametri Base

  • label (stringa, obbligatorio): Etichetta del tab
  • href (stringa, opzionale): URL o hash — se specificato, il componente è un <a>, altrimenti un <button>
  • id (stringa, opzionale): ID del tab

Parametri di Accessibilità

  • roleTab (booleano, opzionale): Se true, aggiunge role="tab" (uso in tablist — Scenari A e B)
  • ariaSelected (booleano, opzionale): Valore per aria-selected (uso in tablist — Scenari A e B)
  • ariaControls (stringa, opzionale): ID del pannello tab correlato (uso in tablist — Scenari A e B)
  • ariaCurrent (stringa, opzionale): Valore per aria-current (page, step, location, date, time, true) — uso come link di navigazione (Scenario C)
  • tabIndex (booleano, opzionale): Se true, aggiunge tabindex="-1" (tab non selezionato in tablist — Scenari A e B)

Parametri Icone

  • iconLeft (booleano, opzionale): Se true, mostra un’icona a sinistra
  • iconRight (booleano, opzionale): Se true, mostra un’icona a destra

Parametri di Stile

  • borderColor (stringa, opzionale): Colore del bordo per lo stato attivo (default: rtds-border-gray-04)
  • bgColor (stringa, opzionale): Colore di sfondo
  • bgActiveColor (stringa, opzionale): Colore di sfondo per lo stato attivo
  • isActive (booleano, opzionale): Se true, aggiunge la classe is-active — usare solo per il pattern navigazione (Scenario C). Nel pattern tablist (Scenari A e B) lo stato attivo è gestito da JavaScript via aria-selected; la classe is-active non deve essere usata.
  • isDisabled (booleano, opzionale): Se true, aggiunge la classe is-disabled; su <button> aggiunge disabled; su <a> aggiunge aria-disabled="true" e tabindex="-1"
  • classes (stringa, opzionale): Classi CSS aggiuntive

Accessibilità

WCAG 2.1

Livello AA — criteri pertinenti:

  • 1.3.1 Informazioni e correlazioni — quando usato in una tablist, role="tab" e aria-selected comunicano la struttura e lo stato semantico; quando usato come link, il ruolo implicito link è sufficiente
  • 2.1.1 Tastiera — il tab è accessibile tramite <a> (link) o <button>, entrambi focalizzabili da tastiera
  • 4.1.2 Nome, ruolo, valore — aria-selected riflette lo stato di selezione nel pattern tablist; aria-controls collega il tab al pannello; aria-current indica la posizione corrente quando usato come link di navigazione

Struttura e semantica

  • <a> — ruolo implicito link; attivabile con Enter
    • In tablist (Scenario B): role="tab" sovrascrive il ruolo implicito; aria-selected comunica lo stato; href punta all’id del tabpanel
    • Come link (Scenario C): mantiene il ruolo implicito link; non aggiungere attributi del pattern tablist
  • <button> — ruolo implicito button; attivabile con Enter e Space; usare sempre in tablist (Scenario A) con role="tab" e aria-selected
  • Il testo della label è in <span class="rtds-tab__label">; nella versione icona-only, la label ha classe rtds-sr-only — visivamente nascosta ma disponibile agli screen reader
  • Stato disabilitato: su <button> usare disabled (il browser lo esclude automaticamente dalla navigazione Tab); su <a> usare aria-disabled="true" + tabindex="-1" (il link non è nativo-disabilitabile, va escluso manualmente dal focus). Usare sempre isDisabled: true per ottenere entrambi l’attributo e la classe is-disabled.

Attributi ARIA per scenario

Attributo Scenari A e B (tablist) Scenario C (link navigazione)
role="tab" Obbligatorio Non usare
aria-selected Obbligatorio Non usare
aria-controls Raccomandato Non usare
tabindex="-1" Sui tab non selezionati Non usare
aria-current Non usare Opzionale ("page", ecc.)
Tasto Azione
Tab / Shift+Tab Sposta il focus verso/dal tab (Scenario C — link); raggiunge solo il tab selezionato in tablist (Scenari A e B)
Enter Attiva il tab
Space Attiva il tab (solo <button>)
Frecce direzionali Navigazione tra i tab in tablist (gestita dal componente tablist parent, non da questo componente)

Tab disabilitati (.is-disabled): i <button disabled> sono esclusi automaticamente dal browser; i <a aria-disabled="true" tabindex="-1"> sono esclusi via tabindex. In entrambi i casi il tab non è raggiungibile con la tastiera.

Gestione del focus

  • Focus visibile: lo stile :focus-visible è sempre visibile e non viene rimosso via CSS
  • In tablist (Scenari A e B): i tab non selezionati hanno tabindex="-1" per escluderli dall’ordine di focus Tab; la navigazione avviene con le frecce direzionali
  • Come link (Scenario C): il focus segue l’ordine naturale della pagina

Screen reader

  • Scenario A / B (tablist): lo screen reader annuncia la label, il ruolo (“scheda”) e lo stato da aria-selected (selezionato/deselezionato)
  • Scenario C (link): viene annunciato come link con il testo della label; se aria-current="page" è presente, viene annunciata anche la posizione corrente
  • In versione icona-only, <span class="rtds-tab__label rtds-sr-only"> contiene la label — visivamente nascosta ma disponibile agli screen reader

Esempi di utilizzo

Scenario A — <button> in tablist

Lo stato attivo è comunicato da aria-selected="true" — gestito via JavaScript. Non aggiungere is-active ai button in questo pattern: il CSS applica lo stile attivo tramite [aria-selected="true"] e il JS non rimuove mai la classe, causando due tab visivamente attivi contemporaneamente.

<!-- Tab selezionato — aria-selected="true" dà lo stile attivo -->
<button class="rtds-tab rtds-tab--default is-tab" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
  <span class="rtds-tab__label">Prima scheda</span>
</button>

<!-- Tab non selezionato -->
<button class="rtds-tab rtds-tab--default is-tab" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
  <span class="rtds-tab__label">Seconda scheda</span>
</button>

Scenario B — <a> in tablist custom

Come nello Scenario A, lo stato attivo è gestito da JavaScript via aria-selected. Non aggiungere is-active.

<!-- Tab selezionato — href punta all'id del tabpanel -->
<a href="#panel-1" class="rtds-tab rtds-tab--default is-tab" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
  <span class="rtds-tab__label">Prima scheda</span>
</a>

<!-- Tab non selezionato -->
<a href="#panel-2" class="rtds-tab rtds-tab--default is-tab" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
  <span class="rtds-tab__label">Seconda scheda</span>
</a>

Lo stato attivo è determinato server-side (nessun JS). La classe is-active è il meccanismo corretto in questo contesto.

<!-- Link attivo nella navigazione corrente -->
<a href="/sezione-corrente/" class="rtds-tab rtds-tab--default is-tab is-active" aria-current="page">
  <span class="rtds-tab__label">Sezione corrente</span>
</a>

<!-- Link ad ancora nella stessa pagina -->
<a href="#sezione-2" class="rtds-tab rtds-tab--default is-tab">
  <span class="rtds-tab__label">Sezione 2</span>
</a>

Note

Due meccanismi per lo stato attivo

Il CSS definisce due selettori equivalenti per lo stile attivo:

.rtds-tab--default.is-active,
.rtds-tab--default[aria-selected="true"] { … }

I due meccanismi sono intenzionalmente distinti e non intercambiabili:

Meccanismo Contesto Gestito da
[aria-selected="true"] Scenari A e B — tablist ARIA con JS JavaScript (tablist.js)
.is-active Scenario C — navigazione link Server-side / statico (NJK)

Nel pattern tablist non aggiungere is-active ai tab: tablist.js gestisce solo aria-selected e non rimuove mai la classe. Se is-active è presente sul primo tab, al cambio scheda quel tab mantiene lo stile attivo anche dopo che JS imposta aria-selected="false".


  • Il componente gestisce il singolo tab: la navigazione con frecce tra tab e la gestione di aria-selected sono responsabilità del componente tablist parent
  • Negli Scenari A e B, il componente tablist parent deve gestire l’aggiornamento di aria-selected e tabindex via JavaScript
  • Nello Scenario B, il tablist parent deve anche intercettare il click sul link per prevenire la navigazione nativa e attivare il pannello via JS
  • Supporta la transizione fluida tra stati
  • Le icone sono gestite attraverso il componente icon esistente
  • Per il componente che raggruppa tab con pannelli di contenuto o navigazione, vedi @tablist (04-organisms/tablist)