<div class="rtds-toggle">
    <label class="rtds-toggle__label" for="toggle-form-disabled-checked">
        <span class="rtds-toggle__text">
            Notifiche email

        </span>
        <span class="rtds-toggle__control">
            <input type="checkbox" role="switch" class="rtds-toggle__input" id="toggle-form-disabled-checked" name="notifications" checked disabled>
            <span class="rtds-toggle__lever" aria-hidden="true"></span>

        </span>
    </label>

</div>
{% set describedIds = '' %}
{% if hasErrorControl %}{% set describedIds = id ~ 'Error' %}{% endif %}
{% if ariaDescribedBy %}{% set describedIds = (describedIds ~ ' ' ~ ariaDescribedBy) | trim %}{% endif %}
<div class="rtds-toggle{% if hasError %} has-error{% endif %}{% if classes %} {{ classes }}{% endif %}">
  <label class="rtds-toggle__label" for="{{ id }}">
    <span class="rtds-toggle__text">
      {{ label | safe }}
      {% if fieldRequired %}<abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr>{% endif %}
    </span>
    <span class="rtds-toggle__control">
      <input
        type="checkbox"
        role="switch"
        class="rtds-toggle__input"
        id="{{ id }}"
        name="{{ name if name else id }}"
        {% if fieldRequired %}aria-required="true"{% endif %}
        {% if ariaInvalid %}aria-invalid="true"{% endif %}
        {% if checked %}checked{% endif %}
        {% if disabled %}disabled{% endif %}
        {% if describedIds %}aria-describedby="{{ describedIds }}"{% endif %}
      >
      <span class="rtds-toggle__lever" aria-hidden="true"></span>
      {% if showStateLabels %}
      <span class="rtds-toggle__state-on" aria-hidden="true">{{ onLabel if onLabel else 'On' }}</span>
      <span class="rtds-toggle__state-off" aria-hidden="true">{{ offLabel if offLabel else 'Off' }}</span>
      {% endif %}
    </span>
  </label>
  {% if hasErrorControl %}
  <span class="rtds-toggle__error" id="{{ id }}Error" aria-live="assertive">{{ errorDescription }}</span>
  {% endif %}
</div>
{
  "id": "toggle-form-disabled-checked",
  "label": "Notifiche email",
  "name": "notifications",
  "disabled": true,
  "checked": true
}
  • Content:
    /* Wrapper */
    .rtds-toggle {
            display: block;
        }
    /* =====================
           Form variant
           ===================== */
    .rtds-toggle__label {
            display: inline-flex;
            align-items: center;
            gap: var(--spacing-3);
            cursor: pointer;
        }
    .rtds-toggle__text {
            font-size: var(--fontsize-text-base);
            color: var(--color-content-01, var(--color-neutral-900));
            line-height: 1.5;
        }
    .rtds-toggle__text abbr {
            text-decoration: none;
        }
    .rtds-toggle__control {
            position: relative;
            display: inline-flex;
            align-items: center;
            flex-shrink: 0;
        }
    /* Visually hidden but accessible and focusable — label wraps the control so clicks on __lever activate the input */
    .rtds-toggle__input {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border-width: 0;
        }
    /* Visual track */
    .rtds-toggle__lever {
            display: inline-block;
            position: relative;
            flex-shrink: 0;
            width: var(--spacing-11);
            height: var(--spacing-6);
            border-radius: 9999px;
            background-color: var(--color-neutral-700);
            pointer-events: none;
            transition: background-color 150ms ease;
        }
    /* Knob */
    .rtds-toggle__lever::before {
            content: '';
            position: absolute;
            top: 50%;
            left: var(--spacing-0-5);
            transform: translateY(-50%);
            width: var(--spacing-5);
            height: var(--spacing-5);
            border-radius: 9999px;
            background-color: var(--color-neutral-50);
            transition: left 150ms ease;
            z-index: 1;
        }
    /* Icon inside knob via mask — x-mark (off) default */
    .rtds-toggle__lever::after {
            content: '';
            position: absolute;
            top: 50%;
            /* Center of knob in off state: edge-offset + half-knob-width */
            left: calc(var(--spacing-0-5) + var(--spacing-5) / 2);
            transform: translate(-50%, -50%);
            width: var(--spacing-3-5);
            height: var(--spacing-3-5);
            background-color: var(--color-content-01, var(--color-neutral-900));
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M5 5L15 15M15 5L5 15'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M5 5L15 15M15 5L5 15'/%3E%3C/svg%3E");
            -webkit-mask-size: contain;
            mask-size: contain;
            -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
            -webkit-mask-position: center;
            mask-position: center;
            pointer-events: none;
            transition: left 150ms ease, background-color 150ms ease;
            z-index: 2;
        }
    /* Form: checked */
    .rtds-toggle__input:checked ~ .rtds-toggle__lever {
            background-color: var(--color-button-primary, var(--color-primary-500));
        }
    .rtds-toggle__input:checked ~ .rtds-toggle__lever::before {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5));
        }
    /* Icon switches to check, moves with knob to right position */
    .rtds-toggle__input:checked ~ .rtds-toggle__lever::after {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5) + var(--spacing-5) / 2);
            background-color: var(--color-button-primary, var(--color-primary-500));
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
        }
    /* Form: focus ring on lever */
    .rtds-toggle__input:focus-visible ~ .rtds-toggle__lever {
            outline: 2px solid var(--color-focusring);
            outline-offset: 2px;
        }
    /* Form: disabled */
    .rtds-toggle__input:disabled ~ .rtds-toggle__lever {
            opacity: 0.4;
        }
    .rtds-toggle__label:has(.rtds-toggle__input:disabled) {
            cursor: not-allowed;
        }
    /* Form: state labels */
    .rtds-toggle__state-on,
        .rtds-toggle__state-off {
            font-size: var(--fontsize-text-sm);
            color: var(--color-content-02, var(--color-neutral-700));
            margin-left: var(--spacing-2);
        }
    .rtds-toggle__input:checked ~ .rtds-toggle__state-off { display: none; }
    .rtds-toggle__input:not(:checked) ~ .rtds-toggle__state-on { display: none; }
    /* Form: error */
    .rtds-toggle__error {
            display: block;
            color: var(--color-content-warn, var(--color-warn-dark));
            font-size: var(--fontsize-text-sm);
            margin-top: var(--spacing-1);
        }
    /* =====================
           Button variant
           ===================== */
    .rtds-toggle__switch {
            display: inline-flex;
            align-items: center;
            gap: var(--spacing-3);
            background: none;
            border: none;
            cursor: pointer;
            padding: 0;
            color: var(--color-content-01, var(--color-neutral-900));
            font-size: var(--fontsize-text-base);
            line-height: 1.5;
        }
    /* Button: checked */
    .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever {
            background-color: var(--color-button-primary, var(--color-primary-500));
        }
    .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever::before {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5));
        }
    .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever::after {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5) + var(--spacing-5) / 2);
            background-color: var(--color-button-primary, var(--color-primary-500));
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
        }
    /* Button: focus ring on lever */
    .rtds-toggle__switch:focus-visible .rtds-toggle__lever {
            outline: 2px solid var(--color-focusring);
            outline-offset: 2px;
        }
    /* Button: disabled */
    .rtds-toggle__switch:disabled {
            cursor: not-allowed;
            opacity: 0.4;
        }
    /* Button: state labels */
    .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__state-off { display: none; }
    .rtds-toggle__switch[aria-checked="false"] .rtds-toggle__state-on { display: none; }
    
  • URL: /components/raw/toggle/toggle.built.css
  • Filesystem Path: components/02-atoms/toggle/toggle.built.css
  • Size: 7.1 KB
  • Content:
    @layer components {
        /* Wrapper */
        .rtds-toggle {
            display: block;
        }
    
        /* =====================
           Form variant
           ===================== */
    
        .rtds-toggle__label {
            display: inline-flex;
            align-items: center;
            gap: var(--spacing-3);
            cursor: pointer;
        }
    
        .rtds-toggle__text {
            font-size: var(--fontsize-text-base);
            color: var(--color-content-01, var(--color-neutral-900));
            line-height: 1.5;
        }
    
        .rtds-toggle__text abbr {
            text-decoration: none;
        }
    
        .rtds-toggle__control {
            position: relative;
            display: inline-flex;
            align-items: center;
            flex-shrink: 0;
        }
    
        /* Visually hidden but accessible and focusable — label wraps the control so clicks on __lever activate the input */
        .rtds-toggle__input {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border-width: 0;
        }
    
        /* Visual track */
        .rtds-toggle__lever {
            display: inline-block;
            position: relative;
            flex-shrink: 0;
            width: var(--spacing-11);
            height: var(--spacing-6);
            border-radius: 9999px;
            background-color: var(--color-neutral-700);
            pointer-events: none;
            transition: background-color 150ms ease;
        }
    
        /* Knob */
        .rtds-toggle__lever::before {
            content: '';
            position: absolute;
            top: 50%;
            left: var(--spacing-0-5);
            transform: translateY(-50%);
            width: var(--spacing-5);
            height: var(--spacing-5);
            border-radius: 9999px;
            background-color: var(--color-neutral-50);
            transition: left 150ms ease;
            z-index: 1;
        }
    
        /* Icon inside knob via mask — x-mark (off) default */
        .rtds-toggle__lever::after {
            content: '';
            position: absolute;
            top: 50%;
            /* Center of knob in off state: edge-offset + half-knob-width */
            left: calc(var(--spacing-0-5) + var(--spacing-5) / 2);
            transform: translate(-50%, -50%);
            width: var(--spacing-3-5);
            height: var(--spacing-3-5);
            background-color: var(--color-content-01, var(--color-neutral-900));
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M5 5L15 15M15 5L5 15'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M5 5L15 15M15 5L5 15'/%3E%3C/svg%3E");
            -webkit-mask-size: contain;
            mask-size: contain;
            -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
            -webkit-mask-position: center;
            mask-position: center;
            pointer-events: none;
            transition: left 150ms ease, background-color 150ms ease;
            z-index: 2;
        }
    
        /* Form: checked */
        .rtds-toggle__input:checked ~ .rtds-toggle__lever {
            background-color: var(--color-button-primary, var(--color-primary-500));
        }
    
        .rtds-toggle__input:checked ~ .rtds-toggle__lever::before {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5));
        }
    
        /* Icon switches to check, moves with knob to right position */
        .rtds-toggle__input:checked ~ .rtds-toggle__lever::after {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5) + var(--spacing-5) / 2);
            background-color: var(--color-button-primary, var(--color-primary-500));
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
        }
    
        /* Form: focus ring on lever */
        .rtds-toggle__input:focus-visible ~ .rtds-toggle__lever {
            outline: 2px solid var(--color-focusring);
            outline-offset: 2px;
        }
    
        /* Form: disabled */
        .rtds-toggle__input:disabled ~ .rtds-toggle__lever {
            opacity: 0.4;
        }
    
        .rtds-toggle__label:has(.rtds-toggle__input:disabled) {
            cursor: not-allowed;
        }
    
        /* Form: state labels */
        .rtds-toggle__state-on,
        .rtds-toggle__state-off {
            font-size: var(--fontsize-text-sm);
            color: var(--color-content-02, var(--color-neutral-700));
            margin-left: var(--spacing-2);
        }
    
        .rtds-toggle__input:checked ~ .rtds-toggle__state-off { display: none; }
        .rtds-toggle__input:not(:checked) ~ .rtds-toggle__state-on { display: none; }
    
        /* Form: error */
        .rtds-toggle__error {
            display: block;
            color: var(--color-content-warn, var(--color-warn-dark));
            font-size: var(--fontsize-text-sm);
            margin-top: var(--spacing-1);
        }
    
        /* =====================
           Button variant
           ===================== */
    
        .rtds-toggle__switch {
            display: inline-flex;
            align-items: center;
            gap: var(--spacing-3);
            background: none;
            border: none;
            cursor: pointer;
            padding: 0;
            color: var(--color-content-01, var(--color-neutral-900));
            font-size: var(--fontsize-text-base);
            line-height: 1.5;
        }
    
        /* Button: checked */
        .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever {
            background-color: var(--color-button-primary, var(--color-primary-500));
        }
    
        .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever::before {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5));
        }
    
        .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever::after {
            left: calc(var(--spacing-11) - var(--spacing-5) - var(--spacing-0-5) + var(--spacing-5) / 2);
            background-color: var(--color-button-primary, var(--color-primary-500));
            -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 10L8 14L16 6'/%3E%3C/svg%3E");
        }
    
        /* Button: focus ring on lever */
        .rtds-toggle__switch:focus-visible .rtds-toggle__lever {
            outline: 2px solid var(--color-focusring);
            outline-offset: 2px;
        }
    
        /* Button: disabled */
        .rtds-toggle__switch:disabled {
            cursor: not-allowed;
            opacity: 0.4;
        }
    
        /* Button: state labels */
        .rtds-toggle__switch[aria-checked="true"] .rtds-toggle__state-off { display: none; }
        .rtds-toggle__switch[aria-checked="false"] .rtds-toggle__state-on { display: none; }
    }
    
  • URL: /components/raw/toggle/toggle.css
  • Filesystem Path: components/02-atoms/toggle/toggle.css
  • Size: 7.4 KB

Toggle

Interruttore binario (on/off) con due varianti semanticamente distinte: form per contesti form, button per azioni dirette.


Comportamento

Variante form (toggle.njk)

Renderizza <div.rtds-toggle> > <label> > <span.__text> + <span.__control>. All’interno di __control: <input type="checkbox" role="switch"> (elemento interattivo) + <span.__lever> (rappresentazione visiva). Lo stato checked è gestito nativamente dal browser e viene inviato con il form.

Variante button (toggle--button.njk)

Renderizza <div.rtds-toggle> > <button role="switch" aria-checked> contenente <span.__text> + <span.__lever>. Il nome accessibile è fornito dal testo interno __text. Il consumer JS è responsabile di aggiornare aria-checked ad ogni click.

Nota per il consumer: senza JavaScript, la variante button non comunica il cambio di stato alle AT. Aggiornare aria-checked ad ogni click: btn.setAttribute('aria-checked', btn.getAttribute('aria-checked') === 'true' ? 'false' : 'true').


Varianti

Variante Template Stato
form toggle.njk off (default)
form-checked toggle.njk on
form-disabled toggle.njk disabilitato
form-disabled-checked toggle.njk disabilitato + on
form-required toggle.njk obbligatorio
form-has-error toggle.njk errore validazione
form-with-state-labels toggle.njk label On/Off visibili
button toggle--button.njk off (default)
button-checked toggle--button-checked.njk on
button-disabled toggle--button.njk disabilitato
button-with-state-labels toggle--button.njk label On/Off visibili

Parametri di configurazione

Comuni a entrambe le varianti

Parametro Tipo Obbligatorio Default Descrizione
id stringa ID univoco del controllo; deve essere unico in pagina
label stringa Testo etichetta visibile. Supporta HTML inline (| safe)
checked booleano no false Stato iniziale on
disabled booleano no false Disabilita il toggle
classes stringa no Classi CSS aggiuntive sul wrapper .rtds-toggle
showStateLabels booleano no false Mostra testo “On”/“Off” accanto al lever
onLabel stringa no 'On' Testo stato attivo — usato solo se showStateLabels: true
offLabel stringa no 'Off' Testo stato inattivo — usato solo se showStateLabels: true
ariaDescribedBy stringa no ID di un elemento esterno di descrizione aggiuntiva

Solo form variant

Parametro Tipo Obbligatorio Default Descrizione
name stringa no valore di id Attributo name dell’input per la submission del form
fieldRequired booleano no false Aggiunge aria-required="true" e asterisco * visibile nella label
hasError booleano no false Aggiunge .has-error al wrapper
ariaInvalid booleano no false Aggiunge aria-invalid="true" sull’input
hasErrorControl booleano no false Mostra .rtds-toggle__error; collega automaticamente via aria-describedby
errorDescription stringa no Testo del messaggio di errore inline

Accessibilità

WCAG 2.1 — Criteri pertinenti

Criterio Come soddisfatto
1.3.1 Informazioni e correlazioni Form: <label for> associa testo e input. Button: nome accessibile da text content interno (__text)
2.1.1 Tastiera Form: <input> nativo attivabile con Space. Button: <button> nativo attivabile con Space ed Enter
4.1.2 Nome, Ruolo, Valore role="switch" + stato comunicato da checked (form) / aria-checked aggiornato dal consumer (button)
1.4.11 Contrasto in contenuti non testuali Track unchecked (--color-neutral-700, ~4.3:1 su bianco), track checked (--color-button-primary, >3:1), focus ring >3:1. Stato disabled (opacity 0.4) escluso dal criterio
Tasto Azione
Tab / Shift+Tab Sposta il focus sul/dal toggle
Space Attiva/disattiva (entrambe le varianti)
Enter Attiva/disattiva (solo button variant)

Screen reader

  • Form: annuncia nome + ruolo + stato, es. “Notifiche email, switch, spento”
  • Button: annuncia nome + ruolo + stato, es. “Modalità scura, switch, spento”
  • Testi “On”/“Off” (showStateLabels) hanno aria-hidden="true" — lo stato è già comunicato dal controllo
  • Errore: .rtds-toggle__error con aria-live="assertive" annuncia il messaggio appena compare

Stili CSS

Classi BEM

Classe Elemento Variante
.rtds-toggle wrapper <div> entrambe
.rtds-toggle.has-error modificatore errore solo form
.rtds-toggle__label <label> solo form
.rtds-toggle__text <span> testo etichetta entrambe
.rtds-toggle__control <span> contenitore input+lever solo form
.rtds-toggle__input <input type="checkbox"> solo form
.rtds-toggle__switch <button role="switch"> solo button
.rtds-toggle__lever <span> rappresentazione visiva (track+knob) entrambe
.rtds-toggle__state-on <span> testo “On” entrambe (se showStateLabels)
.rtds-toggle__state-off <span> testo “Off” entrambe (se showStateLabels)
.rtds-toggle__error <span> messaggio errore solo form

Pseudo-elementi di __lever

Pseudo-elemento Ruolo
::before Knob (cerchio bianco mobile)
::after Icona dentro il knob — × (off, --color-content-01) o ✓ (on, --color-button-primary) via mask-image. Path stroke stroke-width="2.5", dimensione --spacing-3-5

L’__input è nascosto con pattern sr-only (position: absolute; clip; 1px×1px) ma rimane focusable. Il clic sul __lever attiva l’input perché il <label> avvolge tutta la struttura.

Selettori di stato principali

/* Form — checked: track, knob, icona */
.rtds-toggle__input:checked ~ .rtds-toggle__lever { background-color: ...; }
.rtds-toggle__input:checked ~ .rtds-toggle__lever::before { left: ...; }
.rtds-toggle__input:checked ~ .rtds-toggle__lever::after { /* icona check, colore primario */ }

/* Form — focus ring sul lever */
.rtds-toggle__input:focus-visible ~ .rtds-toggle__lever { outline: ...; }

/* Form — disabled */
.rtds-toggle__input:disabled ~ .rtds-toggle__lever { opacity: 0.4; }

/* Button — checked */
.rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever { ... }
.rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever::before { left: ...; }
.rtds-toggle__switch[aria-checked="true"] .rtds-toggle__lever::after { /* icona check */ }

/* Button — focus */
.rtds-toggle__switch:focus-visible .rtds-toggle__lever { outline: ...; }

Note

  • Scegliere la variante in base al contesto: form se il valore deve essere inviato con un <form>, button se l’azione è diretta (es. tema, preferenza UI non legata a un form)
  • La variante button richiede JavaScript per funzionare accessibilmente — senza aggiornamento di aria-checked le AT non percepiscono il cambio di stato
  • Il parametro id deve essere univoco in pagina per garantire la corretta associazione label/controllo
  • Il componente non include JavaScript: la logica di stato è responsabilità del consumer