<div class="rtds-toast-container rtds-toast-container--bottom-right" role="region" aria-label="Notifiche di sistema">
<div class="rtds-toast rtds-toast--success" role="status" aria-labelledby="toast-c1-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--check-circle" />
</svg>
</div>
<div class="rtds-toast__content">
<h2 id="toast-c1-heading" class="rtds-toast__heading">File caricato</h2>
<p class="rtds-toast__body">Il documento è stato caricato con successo.</p>
</div>
</div>
<div class="rtds-toast rtds-toast--info" role="status" aria-labelledby="toast-c2-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--information-circle" />
</svg>
</div>
<div class="rtds-toast__content">
<h2 id="toast-c2-heading" class="rtds-toast__heading">Aggiornamento disponibile</h2>
<p class="rtds-toast__body">Una nuova versione è disponibile.</p>
</div>
<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">
<svg class="rtds-icon rtds-fill-current rtds-w-4 rtds-h-4" aria-hidden="true" focusable="false" role="img">
<use href="../../icons.svg#mini--x-mark" />
</svg>
<span class="rtds-sr-only">Chiudi notifica</span>
</button>
</div>
<div class="rtds-toast rtds-toast--error" role="alert" aria-labelledby="toast-c3-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-c3-heading" class="rtds-toast__heading">Connessione interrotta</h2>
<p class="rtds-toast__body">Impossibile connettersi al server. Riprova.</p>
</div>
</div>
</div>
<div
class="rtds-toast-container rtds-toast-container--{{ position | default('bottom-right') }}{% if classes %} {{ classes }}{% endif %}"
role="region"
aria-label="{{ ariaLabel if ariaLabel else 'Notifiche' }}"
>
{% for toast in toasts %}
{% render '@toast', toast, true %}
{% endfor %}
</div>
{
"type": "success",
"heading": "Operazione completata",
"body": "I dati sono stati salvati correttamente.",
"position": "bottom-right",
"ariaLabel": "Notifiche di sistema",
"toasts": [
{
"id": "toast-c1",
"type": "success",
"heading": "File caricato",
"body": "Il documento è stato caricato con successo."
},
{
"id": "toast-c2",
"type": "info",
"heading": "Aggiornamento disponibile",
"body": "Una nuova versione è disponibile.",
"dismissible": true
},
{
"id": "toast-c3",
"type": "error",
"role": "alert",
"heading": "Connessione interrotta",
"body": "Impossibile connettersi al server. Riprova."
}
]
}
/* ─── 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%;
}
}
@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%;
}
}
}
⚠️ 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) oModal.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.
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.
success: Conferma operazione completatainfo: Messaggio informativowarning: Avviso che richiede attenzioneerror: Errore — usa role="alert" per annuncio assertivoneutro: Toast senza tipo semantico (nessun bordo colorato)container: Demo di stacking (3 toast in colonna)| 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 |
| 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 |
role="status" e role="alert" comunicano i messaggi agli ATrole="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.| 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 |
.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.rtds-toast-container: Base fixed, flex column, gap, z-index 9000.rtds-toast-container--bottom-right / --bottom-left / --top-right / --top-left| 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 |
| 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 |
<!-- 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>document.querySelectorAll('.rtds-toast__close').forEach(btn => {
btn.addEventListener('click', () => {
btn.closest('.rtds-toast').remove();
});
});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);/icons.svg) caricato in pagina.--toast-container-zindex su .rtds-toast-container.