Form

Pagina unei înregistrări pe care o completezi și o salvezi. E un specimen, nu formularul unui produs: etichetele numesc controlul, nu un domeniu, ca să se vadă cum stau controalele unul lângă altul — câmp, rând, listă, grup de opțiuni, bifă, fișier, grup repetat. Copiezi carcasa și pui etichetele tale. Totul vine din grila paginii și din componente: șablonul nu adaugă nicio clasă.

Completată

De la 1024px formularul stă pe .col-8 și fișierul înregistrării pe .col-4; dedesubt, aceeași ordine pe o coloană. Fiecare grup e un card tonal; ajutorul de sub unele câmpuri spune regula pe care o arată câmpul.

Înregistrare nouă

Câmpuri

Ajutorul stă sub câmp, aliniat cu textul din el.
Până la vreo șapte opțiuni.
Listă lungă: scrii, apoi alegi.
Lei
Lei
Lei
Încarcă un document
sau trage-l aici

Opțiuni

O opțiune, cu context
O opțiune din câteva
Da sau nu, cu răspuns obligatoriu
Mai multe opțiuni

Element 1

document-1.pdf
Am primit fișierul. · 284 KB

Element 2

Încarcă un document
sau trage-l aici

Documentul

PDF sau imagine, cel mult 10 MB.

Încarcă un document
sau trage-l aici

Salvare refuzată

Fiecare control își poartă eroarea la el, cu mesajul sub el. Câmpul: contur și fundal roșii. Grupul de opțiuni și bifa: control și etichetă roșii. Fișierul: rândul lui, fără mesaj separat. O eroare care nu ține de un câmp anume e un singur .alert.error în capul coloanei. La salvare focusul merge pe primul câmp cu eroare. Restul paginii rămâne cum era.

Înregistrare nouă

Câmpuri

Completează câmpul.
Lei
Lei
Cel mult două zecimale.
Lei
document-mare.pdf
Are 14 MB. Limita e 10 MB.

Opțiuni

Da sau nu, cu răspuns obligatoriu
Alege Da sau Nu.

Bifează afirmația ca să salvezi.

Ce control, pentru ce

Întrebarea e ce fel de răspuns aștepți, nu cum vrei să arate.

RăspunsulControlulEticheta
text liber.input · textarea.input pentru mai multe rânduri.field-label, deasupra
o valoare din câteva (până la ~7).select-trigger — sau .radio-group când toate trebuie văzute deodată.field-label · la radio, .field-title
o valoare dintr-o listă lungă.typeahead.field-label
un cod scurt dintr-un set mic, citit dintr-o privire (cota TVA, moneda).toggle-group.text.form — 40px, conturul inputului; .tonal / .lg ca inputurile vecinespan.field-label + aria-labelledby
o valoare care schimbă restul formularului.radio-item.card, cu o propoziție pe opțiune.field-title
da / nu, cu răspuns obligatoriu.radio-group.row, nimic ales la început.field-title
oricâte dintr-o listă.check-group.field-title
o afirmație bifată sau nu.check-item singurpropoziția însăși, .check-label
o setare care se aplică pe loc.switch — nu într-un formular cu „Salvează”.label, lângă control
o dată sau un interval.select-trigger + .calendar în popover · intervalul pe un .field-row.cols-2.field-label
o sumă.input-group + moneda în addon.field-label
un fișier printre câmpuri.uploader.is-compact.field-label
fișierul înregistrării.uploader în .card alb, în coloana laterală.card-title

Compoziția și distanțele

.layout-12                          grila paginii — nicio grilă locală
  .col-12   h1.t-h1                 numele înregistrării
  .col-8    .stack-lg               coloana formularului · 24px între carduri
    .alert.error                    doar când salvarea e refuzată fără un câmp anume
    .card.tonal                     UN grup = UN card · inputurile rămân albe pe bej-1
      h2.card-title                 → 16px până la grup
      fieldset.fieldset             aria-labelledby → titlul cardului · 18px între câmpuri
        .field                      4px între etichetă, control și ajutor
        .field-row.cols-2 / .cols-3 12px între câmpurile unui rând — o singură valoare
        .field > .field-title       titlul unui grup de opțiuni
          .radio-group / .check-group     10px între opțiuni · .row → 16px
        .field > .uploader.is-compact     fișier printre câmpuri
        hr.separator.soft           înaintea bifei singure care încheie grupul
        .check-item                 bifa singură — fără .field-title
      .card-ft                      doar pe grupul repetat: „Elimină elementul”
    .btn.secondary                  „Adaugă un element”, sub ultimul grup repetat
  .col-4                            coloana laterală, opțională
    .card                           alb — ține DOAR .uploader
  .col-8    .btn-group.end          [Renunță] [Salvează] — rând propriu, după fișier

Reguli

  1. Grila e a paginii. Formularul stă pe .col-8, coloana laterală pe .col-4 a lui .layout-12 — nu o coloană de lățime fixă. Cu railul de 240, .col-4 are 363px la 1440 și 309px la 1280; cel mai îngust e la 1024 — 224px lângă un .col-8 de 472. Acolo încap încă trei câmpuri pe un rând, rândul unui fișier și zona goală a uploaderului, care își ascunde iconița ca să-i rămână butonul întreg. Fără fișierul înregistrării, coloana laterală lipsește și formularul rămâne pe .col-8. Sub 1024px coloanele se așază una sub alta, fără nicio clasă.
  2. Un grup, un card tonal. .card.tonal > h2.card-title + fieldset.fieldset legat de titlu cu aria-labelledby, ca cititorul de ecran să spună grupul la fiecare câmp. Titlul cardului e numele grupului: fără legend în plus.
  3. Câmpurile stau unul sub altul. Pe un rând stau doar câmpurile care se citesc ca o singură valoare: un interval (.field-row.cols-2), o sumă cu părțile ei (.cols-3, două coloane sub 1024px). Nu există o grilă generică de câmpuri: câmpuri fără legătură puse alături se citesc în zigzag și se sar.
  4. Eticheta urmează controlul. Deasupra unui control cu conținut (text, listă, dată, sumă, fișier): .field-label. Deasupra unui grup de opțiuni: .field-title — nu poate fi <label>, fiindcă nu descrie un singur control; grupul ia role="radiogroup" / role="group" și aria-labelledby. O bifă singură n-are titlu: eticheta ei e propoziția.
  5. Una sau oricâte. Radio pentru exact una (minim două opțiuni, același name), checkbox pentru oricâte. Da / Nu cu răspuns obligatoriu e radio pe rând, nimic ales la început: un checkbox debifat nu deosebește „nu” de „n-am răspuns”. .radio-item.card când fiecare opțiune are nevoie de o propoziție.
  6. Fără switch în formularul care se salvează. Switch-ul se aplică pe loc (docs/components/switch.md); aici totul așteaptă „Salvează”, deci o setare da / nu e un .check-item.
  7. Două feluri de fișier. Fișierul unui câmp stă printre câmpuri, .uploader.is-compact, cu .field-label. Fișierul înregistrării — documentul din care completezi — are cardul lui alb în coloana laterală, la mărimea implicită, cu limitele spuse o dată în .card-desc.
  8. Grupul repetat e un șir de carduri la fel construite: „Element 1”, „Element 2”. „Elimină elementul” (.btn.danger.outline.sm + trash-2) stă în .card-ft și apare doar cât sunt cel puțin două. „Adaugă un element” e un .btn.secondary sub ultimul.
  9. Acțiunile vin la sfârșit. .btn-group.end pe un rând propriu de .col-8, după coloana laterală în DOM: sub 1024px ajunge după fișier. Ordinea e [secundar][primar], un singur .btn.primary.
  10. Erorile stau unde e problema. Pe control (.error, aria-invalid) și sub el (.field-help.error); la fișier, pe rândul lui. Fără câmp: un singur .alert.error, role="alert", în capul coloanei formularului. Focusul merge pe primul câmp cu eroare.
  11. Sub 1024px conținutul e același, pe o coloană. Ecranul stă în .flow-chrome (un formular care se termină într-o acțiune), salvarea e CTA-ul din footer, iar fiecare control trece la .lg (input-group.json → sizes).

Ce nu decide încă

Deschise în bono-ro/edge-33#124, deci șablonul nu le desenează: legătura înapoi de deasupra titlului (1), semnul „(opțional)” — aici, text simplu în etichetă (3), lista de fișiere a unei înregistrări care nu se mai poate schimba (8), eroarea pe un .select-trigger, care n-are încă regulă în foaie (17), și vizualizatorul documentului lângă formular (21).