<div class="rtds-toast rtds-toast--error" role="alert" aria-labelledby="toast-error-heading">
    <div class="rtds-toast__icon" aria-hidden="true">
        <svg class="rtds-icon rtds-fill-current rtds-w-7 rtds-h-7" aria-hidden="true" focusable="false" role="img">
            <use href="../../icons.svg#outline--x-circle" />
        </svg>

    </div>
    <div class="rtds-toast__content">

        <h2 id="toast-error-heading" class="rtds-toast__heading">Errore</h2>

        <p class="rtds-toast__body">Si è verificato un errore durante l'elaborazione. Riprova o contatta l'assistenza.</p>

    </div>

</div>
{% if type == 'success' %}{% set iconId = 'outline--check-circle' %}
{% elif type == 'warning' %}{% set iconId = 'outline--exclamation-triangle' %}
{% elif type == 'error' %}{% set iconId = 'outline--x-circle' %}
{% else %}{% set iconId = 'outline--information-circle' %}
{% endif %}{% set toastId = id if id else 'rtds-toast' %}<div
    class="rtds-toast{% if type %} rtds-toast--{{ type }}{% endif %}{% if classes %} {{ classes }}{% endif %}"
    role="{{ role if role else 'status' }}"
    {% if heading %}aria-labelledby="{{ toastId }}-heading"{% else %}aria-label="{{ ariaLabel | default('Notifica') }}"{% endif %}
>
    <div class="rtds-toast__icon" aria-hidden="true">
        {% render '@icon', {id: iconId, size: 'rtds-w-7 rtds-h-7'}, true %}
    </div>
    <div class="rtds-toast__content">
        {% if heading %}
        <h2 id="{{ toastId }}-heading" class="rtds-toast__heading">{{ heading }}</h2>
        {% endif %}
        {% if body %}
        <p class="rtds-toast__body">{{ body | safe }}</p>
        {% endif %}
    </div>
    {% if dismissible %}
    <button type="button" class="rtds-btn rtds-btn--icon rtds-btn--outlined rtds-border-none rtds-content-01 hover:rtds-bg-black focus:rtds-content-03 rtds-w-8 rtds-h-8 rtds-toast__close">
        {% render '@icon', {id: 'mini--x-mark', size: 'rtds-w-4 rtds-h-4'}, true %}
        <span class="rtds-sr-only">{{ dismissLabel | default('Chiudi notifica') }}</span>
    </button>
    {% endif %}
</div>
{
  "type": "error",
  "heading": "Errore",
  "body": "Si è verificato un errore durante l'elaborazione. Riprova o contatta l'assistenza.",
  "id": "toast-error",
  "role": "alert"
}
  • Content:
    /* ─── Single Toast ─────────────────────────────────────────────── */
    .rtds-toast {
            --_toast-bg:          var(--toast-bg,          var(--color-background-01, var(--color-neutral-50)));
            --_toast-border-color: var(--toast-border-color, var(--color-neutral-200));
            --_toast-shadow:      var(--toast-shadow,       var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)));
            --_toast-min-width:   var(--toast-min-width,   20rem);
            --_toast-max-width:   var(--toast-max-width,   28rem);
    
            display: flex;
            align-items: flex-start;
            gap: var(--spacing-3);
            padding: var(--spacing-4);
            min-width: var(--_toast-min-width);
            max-width: var(--_toast-max-width);
            border-width: 1px;
            border-left-width: var(--spacing-1);
            border-style: solid;
            border-color: var(--_toast-border-color);
            background-color: var(--_toast-bg);
            box-shadow: var(--_toast-shadow);
            color: var(--color-content-01, var(--color-neutral-900));
        }
    .rtds-toast__icon {
            flex-shrink: 0;
        }
    .rtds-toast__content {
            flex: 1;
        }
    .rtds-toast__heading {
            display: block;
            font-size: inherit;
            font-weight: 600;
            margin: 0 0 var(--spacing-1);
        }
    .rtds-toast__body {
            margin: 0;
        }
    .rtds-toast__close {
            flex-shrink: 0;
            align-self: flex-start;
        }
    /* ─── Semantic type: border color + icon ────────────────────────── */
    /* Info */
    .rtds-toast--info {
            --_toast-info-border: var(--toast-info-border, var(--color-content-info, var(--color-info-dark)));
            --_toast-info-icon:   var(--toast-info-icon,   var(--color-content-info, var(--color-info-dark)));
            border-color: var(--_toast-info-border);
        }
    .rtds-toast--info .rtds-toast__icon {
            color: var(--_toast-info-icon);
        }
    /* Success */
    .rtds-toast--success {
            --_toast-success-border: var(--toast-success-border, var(--color-content-success, var(--color-success-dark)));
            --_toast-success-icon:   var(--toast-success-icon,   var(--color-content-success, var(--color-success-dark)));
            border-color: var(--_toast-success-border);
        }
    .rtds-toast--success .rtds-toast__icon {
            color: var(--_toast-success-icon);
        }
    /* Warning */
    .rtds-toast--warning {
            --_toast-warning-border: var(--toast-warning-border, var(--color-content-warn, var(--color-warn-dark)));
            --_toast-warning-icon:   var(--toast-warning-icon,   var(--color-content-warn, var(--color-warn-dark)));
            border-color: var(--_toast-warning-border);
        }
    .rtds-toast--warning .rtds-toast__icon {
            color: var(--_toast-warning-icon);
        }
    /* Error */
    .rtds-toast--error {
            --_toast-error-border: var(--toast-error-border, var(--color-content-error, var(--color-error-dark)));
            --_toast-error-icon:   var(--toast-error-icon,   var(--color-content-error, var(--color-error-dark)));
            border-color: var(--_toast-error-border);
        }
    .rtds-toast--error .rtds-toast__icon {
            color: var(--_toast-error-icon);
        }
    /* ─── Toast Container ───────────────────────────────────────────── */
    .rtds-toast-container {
            --_toast-container-offset: var(--toast-container-offset, var(--spacing-4, 1rem));
            --_toast-container-gap:    var(--toast-container-gap,    var(--spacing-2, 0.5rem));
            --_toast-container-zindex: var(--toast-container-zindex, 9000);
    
            position: fixed;
            z-index: var(--_toast-container-zindex);
            display: flex;
            flex-direction: column;
            gap: var(--_toast-container-gap);
        }
    .rtds-toast-container--bottom-right {
            bottom: var(--_toast-container-offset);
            right: var(--_toast-container-offset);
        }
    .rtds-toast-container--bottom-left {
            bottom: var(--_toast-container-offset);
            left: var(--_toast-container-offset);
        }
    .rtds-toast-container--top-right {
            top: var(--_toast-container-offset);
            right: var(--_toast-container-offset);
        }
    .rtds-toast-container--top-left {
            top: var(--_toast-container-offset);
            left: var(--_toast-container-offset);
        }
    /* ─── Mobile: full-width for bottom positions ───────────────────── */
    @media (max-width: 640px) {
            .rtds-toast-container--bottom-right,
            .rtds-toast-container--bottom-left {
                left: 0;
                right: 0;
                bottom: 0;
            }
    
            .rtds-toast-container--bottom-right .rtds-toast,
            .rtds-toast-container--bottom-left .rtds-toast {
                min-width: 0;
                max-width: none;
                width: 100%;
            }
        }
    
  • URL: /components/raw/toast/toast.built.css
  • Filesystem Path: components/08-servizi/toast/toast.built.css
  • Size: 5 KB
  • Content:
    @layer components {
    
        /* ─── Single Toast ─────────────────────────────────────────────── */
    
        .rtds-toast {
            --_toast-bg:          var(--toast-bg,          var(--color-background-01, var(--color-neutral-50)));
            --_toast-border-color: var(--toast-border-color, var(--color-neutral-200));
            --_toast-shadow:      var(--toast-shadow,       var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)));
            --_toast-min-width:   var(--toast-min-width,   20rem);
            --_toast-max-width:   var(--toast-max-width,   28rem);
    
            display: flex;
            align-items: flex-start;
            gap: var(--spacing-3);
            padding: var(--spacing-4);
            min-width: var(--_toast-min-width);
            max-width: var(--_toast-max-width);
            border-width: 1px;
            border-left-width: var(--spacing-1);
            border-style: solid;
            border-color: var(--_toast-border-color);
            background-color: var(--_toast-bg);
            box-shadow: var(--_toast-shadow);
            color: var(--color-content-01, var(--color-neutral-900));
        }
    
        .rtds-toast__icon {
            flex-shrink: 0;
        }
    
        .rtds-toast__content {
            flex: 1;
        }
    
        .rtds-toast__heading {
            display: block;
            font-size: inherit;
            font-weight: 600;
            margin: 0 0 var(--spacing-1);
        }
    
        .rtds-toast__body {
            margin: 0;
        }
    
        .rtds-toast__close {
            flex-shrink: 0;
            align-self: flex-start;
        }
    
        /* ─── Semantic type: border color + icon ────────────────────────── */
    
        /* Info */
        .rtds-toast--info {
            --_toast-info-border: var(--toast-info-border, var(--color-content-info, var(--color-info-dark)));
            --_toast-info-icon:   var(--toast-info-icon,   var(--color-content-info, var(--color-info-dark)));
            border-color: var(--_toast-info-border);
        }
        .rtds-toast--info .rtds-toast__icon {
            color: var(--_toast-info-icon);
        }
    
        /* Success */
        .rtds-toast--success {
            --_toast-success-border: var(--toast-success-border, var(--color-content-success, var(--color-success-dark)));
            --_toast-success-icon:   var(--toast-success-icon,   var(--color-content-success, var(--color-success-dark)));
            border-color: var(--_toast-success-border);
        }
        .rtds-toast--success .rtds-toast__icon {
            color: var(--_toast-success-icon);
        }
    
        /* Warning */
        .rtds-toast--warning {
            --_toast-warning-border: var(--toast-warning-border, var(--color-content-warn, var(--color-warn-dark)));
            --_toast-warning-icon:   var(--toast-warning-icon,   var(--color-content-warn, var(--color-warn-dark)));
            border-color: var(--_toast-warning-border);
        }
        .rtds-toast--warning .rtds-toast__icon {
            color: var(--_toast-warning-icon);
        }
    
        /* Error */
        .rtds-toast--error {
            --_toast-error-border: var(--toast-error-border, var(--color-content-error, var(--color-error-dark)));
            --_toast-error-icon:   var(--toast-error-icon,   var(--color-content-error, var(--color-error-dark)));
            border-color: var(--_toast-error-border);
        }
        .rtds-toast--error .rtds-toast__icon {
            color: var(--_toast-error-icon);
        }
    
        /* ─── Toast Container ───────────────────────────────────────────── */
    
        .rtds-toast-container {
            --_toast-container-offset: var(--toast-container-offset, var(--spacing-4, 1rem));
            --_toast-container-gap:    var(--toast-container-gap,    var(--spacing-2, 0.5rem));
            --_toast-container-zindex: var(--toast-container-zindex, 9000);
    
            position: fixed;
            z-index: var(--_toast-container-zindex);
            display: flex;
            flex-direction: column;
            gap: var(--_toast-container-gap);
        }
    
        .rtds-toast-container--bottom-right {
            bottom: var(--_toast-container-offset);
            right: var(--_toast-container-offset);
        }
    
        .rtds-toast-container--bottom-left {
            bottom: var(--_toast-container-offset);
            left: var(--_toast-container-offset);
        }
    
        .rtds-toast-container--top-right {
            top: var(--_toast-container-offset);
            right: var(--_toast-container-offset);
        }
    
        .rtds-toast-container--top-left {
            top: var(--_toast-container-offset);
            left: var(--_toast-container-offset);
        }
    
        /* ─── Mobile: full-width for bottom positions ───────────────────── */
    
        @media (max-width: 640px) {
            .rtds-toast-container--bottom-right,
            .rtds-toast-container--bottom-left {
                left: 0;
                right: 0;
                bottom: 0;
            }
    
            .rtds-toast-container--bottom-right .rtds-toast,
            .rtds-toast-container--bottom-left .rtds-toast {
                min-width: 0;
                max-width: none;
                width: 100%;
            }
        }
    }
    
  • URL: /components/raw/toast/toast.css
  • Filesystem Path: components/08-servizi/toast/toast.css
  • Size: 5.1 KB

Toast

⚠️ Limitazioni di accessibilità — leggere prima dell’uso

Il pattern toast ha vincoli intrinseci che non possono essere eliminati per design:

  • Area ridotta: il toast occupa poco spazio visivo e può essere ignorato da utenti con bassa visione.
  • Auto-dismiss: il messaggio può sparire prima che l’utente lo legga o che lo screen reader lo annunci completamente. Preferire tempi di dismiss ≥ 5 secondi e offrire sempre un’alternativa persistente (es. sezione “Notifiche” nella UI).
  • Sovrasta il contenuto: il posizionamento fixed può coprire elementi interattivi della pagina, in particolare su schermi piccoli.
  • Non sostituisce Alert o Modal: per messaggi critici che richiedono azione, usare il componente Alert (inline) o Modal.

Il componente è conforme WCAG 2.2 AA solo se usato correttamente: live region vuota nel DOM al caricamento, toast iniettato via JS, timing adeguato.

Componente di notifica temporanea sovrapposta al contenuto per comunicare esiti di operazioni. Display-only — nessun JavaScript integrato.


Comportamento

Il singolo toast usa role="status" (polite) per i tipi info/success/warning e role="alert" (assertive) per il tipo error. Il parametro role consente di sovrascrivere il valore.

Il container (role="region") deve essere presente nel DOM al caricamento della pagina — non può essere creato dinamicamente. I singoli toast vengono iniettati nel container via JavaScript dal consumer.


Varianti

  • success: Conferma operazione completata
  • info: Messaggio informativo
  • warning: Avviso che richiede attenzione
  • error: Errore — usa role="alert" per annuncio assertivo
  • neutro: Toast senza tipo semantico (nessun bordo colorato)
  • container: Demo di stacking (3 toast in colonna)

Parametri — Toast singolo

Parametro Tipo Obbligatorio Default Descrizione
type stringa no Tipo semantico: info | success | warning | error
id stringa no rtds-toast ID base per aria-labelledby (necessario se più toast in pagina)
heading stringa no Titolo del toast; attiva aria-labelledby
body stringa no Testo del messaggio; accetta HTML (safe)
ariaLabel stringa no Notifica aria-label alternativo quando heading è assente
role stringa no status Sovrascrive il role ARIA (es. alert per error)
dismissible booleano no false Mostra il pulsante di chiusura
dismissLabel stringa no Chiudi notifica Label accessibile del pulsante di chiusura
classes stringa no Classi CSS aggiuntive sull’elemento radice

Parametri — Container

Parametro Tipo Obbligatorio Default Descrizione
toasts array no [] Array di context oggetto per ogni toast figlio
position stringa no bottom-right Posizione: bottom-right | bottom-left | top-right | top-left
ariaLabel stringa no Notifiche aria-label del container (role="region")
classes stringa no Classi CSS aggiuntive sul container

Accessibilità

WCAG 2.2 — criteri pertinenti

  • 1.4.1 Uso del colore — il bordo colorato è affiancato dall’icona per distinguere il tipo
  • 1.4.3 Contrasto minimo — testo su sfondo con contrasto ≥ 4.5:1
  • 2.2.1 Tempo regolabile — fornire sempre alternativa persistente; non affidarsi solo al toast
  • 4.1.3 Messaggi di statorole="status" e role="alert" comunicano i messaggi agli AT

Struttura live region

  • role="status" (polite): lo screen reader annuncia al termine della lettura corrente. Usare per info, success.
  • role="alert" (assertive): lo screen reader interrompe e annuncia immediatamente. Usare solo per error.
  • role="region" sul container: nessun aria-live aggiuntivo — causerebbe doppio annuncio.

Attributi ARIA

Attributo Valori Condizione
role status | alert sul singolo toast — sempre
aria-labelledby ID dell’heading se heading è presente
aria-label testo descrittivo se heading è assente
aria-hidden true sull’icona SVG — decorativa
role="region" sul container
aria-label Notifiche sul container — sempre

Stili CSS

Classi — Toast singolo

  • .rtds-toast: Stile base — flex, padding, bordo 1px + bordo sinistro spesso, sfondo, ombra
  • .rtds-toast--info: Bordo e icona blu (--color-content-info)
  • .rtds-toast--success: Bordo e icona verde (--color-content-success)
  • .rtds-toast--warning: Bordo e icona arancione (--color-content-warn)
  • .rtds-toast--error: Bordo e icona rosso (--color-content-error)
  • .rtds-toast__icon: Contenitore icona decorativa
  • .rtds-toast__content: Area testo (heading + body)
  • .rtds-toast__heading: Titolo in grassetto — elemento <h2> con reset font-size/margin
  • .rtds-toast__body: Testo del messaggio
  • .rtds-toast__close: Pulsante dismiss — classi: rtds-btn rtds-btn--icon rtds-btn--outlined rtds-border-none rtds-content-01 hover:rtds-bg-black focus:rtds-content-03

Classi — Container

  • .rtds-toast-container: Base fixed, flex column, gap, z-index 9000
  • .rtds-toast-container--bottom-right / --bottom-left / --top-right / --top-left

Tematizzazione — Custom Properties

Toast singolo (Tier 1 — API pubblica)

Proprietà CSS Default DS Descrizione
--toast-bg var(--color-background-01) Sfondo del toast
--toast-border-color var(--color-neutral-200) Colore bordo (lati non colorati)
--toast-shadow var(--shadow-md) Ombra esterna
--toast-min-width 20rem Larghezza minima
--toast-max-width 28rem Larghezza massima
--toast-info-border var(--color-content-info) Colore bordo variante info
--toast-info-icon var(--color-content-info) Colore icona variante info
--toast-success-border var(--color-content-success) Colore bordo variante success
--toast-success-icon var(--color-content-success) Colore icona variante success
--toast-warning-border var(--color-content-warn) Colore bordo variante warning
--toast-warning-icon var(--color-content-warn) Colore icona variante warning
--toast-error-border var(--color-content-error) Colore bordo variante error
--toast-error-icon var(--color-content-error) Colore icona variante error

Container (Tier 1 — API pubblica)

Proprietà CSS Default DS Descrizione
--toast-container-offset var(--spacing-4) Distanza dal bordo della viewport
--toast-container-gap var(--spacing-2) Spazio verticale tra toast
--toast-container-zindex 9000 z-index del container

Esempi di utilizzo

Markup HTML base

<!-- Toast success -->
<div class="rtds-toast rtds-toast--success" role="status" aria-labelledby="toast-1-heading">
  <div class="rtds-toast__icon" aria-hidden="true"><!-- SVG outline--check-circle --></div>
  <div class="rtds-toast__content">
    <h2 id="toast-1-heading" class="rtds-toast__heading">Operazione completata</h2>
    <p class="rtds-toast__body">I dati sono stati salvati.</p>
  </div>
</div>

<!-- Toast error (assertive) -->
<div class="rtds-toast rtds-toast--error" role="alert" aria-labelledby="toast-2-heading">
  <div class="rtds-toast__icon" aria-hidden="true"><!-- SVG outline--x-circle --></div>
  <div class="rtds-toast__content">
    <h2 id="toast-2-heading" class="rtds-toast__heading">Errore</h2>
    <p class="rtds-toast__body">Impossibile salvare. Riprova.</p>
  </div>
</div>

<!-- Container (da includere nel layout base della pagina) -->
<div class="rtds-toast-container rtds-toast-container--bottom-right"
     role="region" aria-label="Notifiche">
</div>

Gestione dismiss via JavaScript

document.querySelectorAll('.rtds-toast__close').forEach(btn => {
  btn.addEventListener('click', () => {
    btn.closest('.rtds-toast').remove();
  });
});

Auto-dismiss via JavaScript

function showToast(container, toastEl, delay = 5000) {
  container.appendChild(toastEl);
  setTimeout(() => toastEl.remove(), delay);
}

// Uso:
const container = document.querySelector('.rtds-toast-container');
const toast = document.createElement('div');
toast.className = 'rtds-toast rtds-toast--success';
toast.setAttribute('role', 'status');
toast.setAttribute('aria-label', 'Operazione completata');
toast.innerHTML = `
  <div class="rtds-toast__content">
    <p class="rtds-toast__body">Salvato con successo.</p>
  </div>
`;
showToast(container, toast, 6000);

Note

  • Richiede lo sprite SVG (/icons.svg) caricato in pagina.
  • Il container DEVE essere nel DOM al caricamento della pagina — non crearlo dinamicamente: le live region create dopo il caricamento non vengono riconosciute dagli screen reader.
  • Per z-index in conflitto con modal/overlay: sovrascrivere --toast-container-zindex su .rtds-toast-container.
  • Il dismiss è responsabilità del consumer — nessun JavaScript integrato.