Modal / Dialog

Markup canonic · components/modal.json

sursa = JSON · generat de scripts/build-catalog.py

Scrim live · deschide/închide

Demo interactivă a tratamentului canonic: background: var(--c-backdrop) (ink @ 25%) + backdrop-filter: blur(var(--scrim-blur)) (6px), cu animația reală (modal-fade 180ms pe backdrop, modal-in 220ms pe card). Închide cu X, click pe backdrop, sau Escape — cele trei căi obligatorii din „Comportament obligatoriu" (docs/components/modal.md).

apasă butonul pentru a vedea scrimul intrând — nu un mockup static

Eyebrow parametric

Eticheta .t-eyebrow de deasupra titlului e opțională — o folosești când modalul are nevoie de context sau categorie (ex. „Factură nouă"). Layoutul cardului funcționează identic cu sau fără ea; când lipsește, titlul .t28 urcă primul și primește +4px spațiu sus.

cu eyebrow · fără eyebrow — același card, aceeași structură

Variantă · acțiuni suprapuse

Dialog de dismiss cu două acțiuni suprapuse vertical, într-un .modal-actions.stacked (= .btn-group.stacked): dismiss ca .btn.outline deasupra, confirm ca .btn.primary dedesubt. Ambele size default (md), întinse de grup; spațiere moderată: gap 8px (--sp-8). Rămâne o singură acțiune primary per modal, și aceeași ordine la orice lățime.

.modal-actions.stacked · .btn.outline + .btn.primary · size default · gap var(--sp-8)

Dialog de ștergere · distructiv

Confirmarea unei acțiuni distructive, în același .modal-actions.stacked. Acțiunea distructivă e negru cu iconiță (.btn.danger), dismiss-ul deasupra ca .btn.outline — dedesubt niciodată, la nicio lățime. Pentru ștergeri cu impact mare adaugă confirmare prin nume: inputul rămâne în stare de eroare până când textul se potrivește, cu .field-help.error aliniat sub input.

.modal-actions.stacked · .btn.outline + .btn.danger (negru + iconiță) · confirmare prin nume cu stare de eroare

Structură HTML

markup minim pentru a porni un modal
<div class="modal-backdrop" onclick="if(event.target===this)closeModal()">
  <div class="modal">
    <button class="btn outline sm icon-only" aria-label="Închide">...</button>
    <div class="t-eyebrow">...</div>          <!-- opțional — fără el, titlul primește margin-top:4px -->
    <h2 class="t28">Titlu <em>modal</em></h2>
    <!-- inputs lg -->
    <button class="btn primary lg full">Acțiune</button>

    <!-- SAU varianta stacked-actions (dismiss dialog) -->
    <div class="modal-actions stacked">
      <button class="btn outline">Renunță</button>
      <button class="btn primary">Confirmă</button>
    </div>
  </div>
</div>
JS: backdrop.classList.toggle('open')

Sloturile

Părțile pe care orice modal le are oricum, formalizate ca să nu fie reinventate per ecran.

.modal-title     20px / 600 · ink · padding-right 40px (lasă loc pentru X)
.modal-body      15px / 22px · fog
.modal-actions   flex end · gap 10px · ordinea [secundar][primar]
                 sub 1024px: column, aceeași ordine, fiecare buton full-width
.modal-actions.stacked
                 coloană la orice lățime · gap --sp-8 · suprapuse

.modal-actions e .btn-group — același selector în CSS, nu o a doua implementare. Un modal nu are alt rând de acțiuni decât restul aplicației, deci nici alt CSS. Ordinea e [secundar][primar] pe mobil și pe desktop; .stacked schimbă doar axa, niciodată ordinea.

Alert dialog · .modal.confirm

Aceeași suprafață, conținut simplificat: un titlu și o propoziție. Fără X — o întrebare blocantă cere un răspuns explicit, nu o ieșire laterală. Exemplul de aici e distructiv, deci poartă regula de ștergere: .modal-actions.stacked, cu .btn.outline deasupra și .btn.danger cu iconiță dedesubt — aceeași ca la Dialog de ștergere. Un .modal.confirm nedistructiv (confirmare de trimitere, de exemplu) folosește același .modal-actions, fără .stacked.

.modal.confirm   max-width 400px · padding 28px 28px 24px
                 aceeași suprafață: bej-1 · --r-xl · outline alb offset 16px
                 butonul de închidere ascuns
acțiuni          dacă e DISTRUCTIV: .modal-actions.stacked — .btn.outline
                 deasupra + .btn.danger (negru + iconiță) dedesubt —
                 aceeași regulă ca „Dialog de ștergere" mai sus
                 dacă NU e distructiv: același .modal-actions fără .stacked —
                 rând aliniat dreapta pe desktop, coloană full-width sub 1024px
                 ordinea [secundar][primar] în ambele cazuri, la orice lățime
role             alertdialog (nu dialog) + aria-modal + aria-labelledby
De ce nu .modal.alert. Ăsta era numele evident, dar .alert e deja o componentă — iar .modal.alert prinde și regula ei, care e display: flex: titlul, textul și butoanele se așează pe un rând. Un modificator nu poate purta numele unei componente existente. Prins la randare, nu la citire.
Butonul distructiv e .btn.danger — fill --c-danger (ink), nu roșu. Și fără accent de brand în modalul distructiv: fără pink, fără <em> în titlu.