Toast · sonner

Feedback tranzitoriu, sus-centrat — ceva s-a întâmplat și dispare pe cronometru. Pentru ceva ce trebuie să persiste, Alert. Ghidul: docs/feedback.md.

Ierarhia notificărilor

Un singur principiu: cine ține informația după ce mesajul dispare.

TreaptăCine ține informațiaComponenta
0Tăcereconsecința din interfață — un sold mai mic, un document nou evident în listă, un card care lipsește. Utilizatorul vede rezultatul, deci mesajul n-ar adăuga nimicnimic
1Chitanțăinterfața ține informația, dar operațiunea a avut miză mai mare — ștergerea unui item se vede în UI și se confirmă și prin toast. Toast temporal, care poartă sens și prin culoare.toast
2Dovadămesajul, și numai mesajul — nimic din interfață nu o arată. Ex: sesiunea SPV a expirat, deci documentele nu se mai sincronizează; sau CUI-ul clientului e invalid, deci factura nu poate fi trimisă în e-Factura.alert (inline sau .page)
3Obligațiemesajul, și cere muncă.alert.page + .alert-actions, fără dismiss
4Deciziemesajul, și nimic nu merge înainte.modal

Treptele urcă, nu coboară — un eșec nu devine chitanță pentru că e mic.

Treapta 0 e o decizie de produs, nu de design system — DS-ul nu poate ști la ce se uită utilizatorul. Dar întrebarea merită pusă pe fiecare ecran: mesajul ăsta adaugă ceva ce interfața nu arată deja? O coloană de status care se schimbă, un sold care se actualizează, un badge care trece pe success — acolo feedbackul e starea vizibilă, nu un mesaj.

Work in progress — maparea aplicațiilor reale. Ierarhia e definită, dar nu e încă verificată pe inventarul complet de notificări din aplicațiile noastre. Urmează un task de inventariere: fiecare tip de notificare existent, mapat pe una din cele cinci trepte. Ce se schimbă după inventar se schimbă aici întâi.

Markup canonic · components/toast.json

sursa = JSON · generat de scripts/build-catalog.py
Factura a fost trimisă. Clientul primește un email cu link de plată.

Cinci stări

.toast .neutral / .success / .warning / .error / .brand · lățimea vine din CSS: min(560px, 100vw − 32px)
Se sincronizează 24 de documente…
Documentul a fost semnat. Copia semnată e în Documente.
Două facturi nu au putut fi încadrate automat. Le găsești în „De verificat".
Vox a găsit 3 deduceri noi. Le vezi în raportul lunii.
previzualizare la nivel de pagină · .toast-stack fixed, sus-centrat

Butoanele trimit toastul în stiva reală a paginii — poziția, umbra, intrarea, cronometrul și maximul de 3 simultan vin din bono-ds.css, nu din pagina asta. error nu se auto-închide: rămâne până îl închizi.

Aceleași cinci stări, cu același sens, ca pe .alert. .warn și .info rămân ca aliasuri deprecate pentru .warning și .neutral.

Acțiunea — una, și numai opțională

.toast-actions — undo e cazul canonic
Factura a fost ștearsă.
previzualizare la nivel de pagină · 6s, fiindcă are acțiune
O acțiune, maximum, și numai OPȚIONALĂ — adică „nu fac nimic" trebuie
să fie un rezultat corect și sigur.

undo               ✅  cronometrul E fereastra de undo
„Vezi documentul"  ✅  navigare opțională
„Copiază link"     ✅  comoditate
„Reautorizează"    ❌  obligatoriu → .alert
„Confirmă suma"    ❌  cere un răspuns → .modal

Regula e a lui Carbon: acțiunea dintr-o notificare trebuie să fie
opțională și să nu blocheze sarcina curentă.

Două corolare

1 · Un toast nu poate fi singurul loc în care există o informație.

„D112 depusă · referință 2026-04-22-7841" — dacă referința stă pe înregistrarea depunerii, toastul e o chitanță și e corect. Dacă stă doar în toast, ai pus o dovadă într-o suprafață de chitanță, iar utilizatorul are patru secunde să noteze un număr de care poate avea nevoie într-o contestație la ANAF.

Dacă utilizatorul ar putea vrea să-l noteze, nu e toast.

Remediul: pune datele în interfață, și atunci toastul devine corect — sau
nu folosi toast, iar un .alert.success inline pe depunere ține referința
cât e nevoie.

2 · Acțiunea opțională e o punte spre locul unde interfața ține informația.

De asta acțiunea din toast e permisă doar opțională: „Vezi documentul" e o scurtătură spre un loc care va exista și peste zece minute. Undo e excepția care confirmă regula — singurul caz în care toastul ține ceva ce interfața încă nu ține, și exact de asta e singura acțiune cu termen.

„Interfața o ține" nu e destul dacă utilizatorul nu o poate găsi. „37 din 40 importate, 3 au eșuat" e toast doar dacă există o vedere a eșecurilor spre care să ducă acțiunea opțională. Dacă nu există, mesajul trebuie să persiste — deci e .alert.

Poziționare și durată

.toast-stack   SUS-CENTRAT, top 24px, stivuit în jos, gap 10px
lățime         min(560px, 100vw - 32px)
sub 1024px     se întinde la left/right 16px, top 16px
umbră          --sh-toast — nu --sh-popover, nu hardcodată
durată         4s implicit · 6s dacă are acțiune · error NU se auto-închide
maxim          3 simultan — peste 3 e un log, iar logul stă în pagină

Sus-centrat pentru că acolo se uită deja utilizatorul după ce a apăsat
ceva, și pentru că nu acoperă CTA-ul de jos pe mobil.
.toast-stack.top-center e alias no-op DEPRECAT (v8) — poziția e implicită.

Anunțare — stiva e live region, nu toastul

.toast-stack   aria-live="polite" aria-atomic="false"
.toast.error   + role="alert"

Stiva există în DOM la încărcare, deci orice se adaugă în ea se anunță. E
mai robust decât să te bazezi pe suportul pentru un nod injectat cu role.
aria-atomic="false" ca stiva să anunțe doar toastul nou, nu întreaga listă.

error e singura excepție: merită să întrerupă — și nici nu se auto-închide,
deci nu dispare de sub cititorul de ecran.

butonul de închidere   aria-label — e icon-only
.ic              aria-hidden="true" — decorativ, 16px din CSS

Anatomie

.toast-stack > .toast > .ic  +  .toast-body  +  .btn.ghost.xs.icon-only

.ic              16px, dat de CSS — nu din atributele SVG
.toast-body      obligatoriu: poartă min-width: 0
.toast-actions   în .toast-body, sub text. Una, opțională
butonul de       NU o clasă proprie — e butonul din DS:
închidere        .btn.ghost.xs.icon-only. .toast-close și butonul de închidere
                 sunt aliasuri DEPRECATE (2026-09-01)

Perechea de culoare

Fundalul e tokenul de tint al familiei, textul e tokenul de text al familiei. Niciodată fill-ul ca text: --c-error pe --c-error-bg dă 4.38:1, sub AA. Exact defectul pe care wrapperul sonner din bono-skills-website îl moștenea, fiindcă își lega variabilele de tokenii de fill.

Bordura e tokenul -text al familiei — aceeași pereche de culoare, plus o margine, pentru că tintul singur e la 1.05–1.12:1 față de pagină. Bordura e la 70% opacitate, aceeași pe toate stările — media opacităților care duc fiecare stare la 3:1, rotunjită în sus. Excepția e .neutral: bordura e --c-fog, nu --c-ink — ink-ul dădea 19:1, o margine de trei ori mai grea decât a celorlalte stări.
StareFundalTextContrastBordură (70%)vs alb / bej-0
.neutral--c-bej-1--c-ink16.23:1--c-fog2.9 / 2.7:1
.success--c-success-bg--c-success-text5.84:1--c-success-text3.6 / 3.4:1
.warning--c-warn-bg--c-warn-text4.86:1--c-warn-text3.2 / 3.1:1
.error--c-error-bg--c-error-text4.91:1--c-error-text3.6 / 3.4:1
.brand--c-white + marker --c-pink--c-fog / --c-pink-deep4.83:1--c-rule + marker—