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.
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.
Ce control, pentru ce
Întrebarea e ce fel de răspuns aștepți, nu cum vrei să arate.
| Răspunsul | Controlul | Eticheta |
|---|---|---|
| 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 vecine | span.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 singur | propoziț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
- Grila e a paginii. Formularul stă pe
.col-8, coloana laterală pe.col-4a lui.layout-12— nu o coloană de lățime fixă. Cu railul de 240,.col-4are 363px la 1440 și 309px la 1280; cel mai îngust e la 1024 — 224px lângă un.col-8de 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ă. - Un grup, un card tonal.
.card.tonal>h2.card-title+fieldset.fieldsetlegat de titlu cuaria-labelledby, ca cititorul de ecran să spună grupul la fiecare câmp. Titlul cardului e numele grupului: fărălegendîn plus. - 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. - 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 iarole="radiogroup"/role="group"șiaria-labelledby. O bifă singură n-are titlu: eticheta ei e propoziția. - 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.cardcând fiecare opțiune are nevoie de o propoziție. - 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. - 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. - 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.secondarysub ultimul. - Acțiunile vin la sfârșit.
.btn-group.endpe 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. - 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. - 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).