TLDR

Există două categorii de probleme în checkout-urile RO. Prima: ajustările pe care le faci pentru că realitatea din RO nu se mapează curat cu ce spune manualul (sectoarele din București, codurile poștale nefiabile, ramburs cât cardul). A doua: regulile universale Baymard pe care, deși se aplică identic peste tot, multe site-uri RO mari încă nu le-au adoptat. Articolul de săptămâna asta merge în categoria a doua. Nouă pattern-uri standard, documentate și ieftine de implementat. Persistent labels, inline validation, mesaje de eroare adaptive, marcare required și optional, tastatură corectă pe mobile, single name plus single phone, single-column, păstrarea inputului la submit eșuat, microcopy precis pe CTA. Niciuna nu cere reinventare. Toate apar încă greșit în checkout-urile pe care le-am văzut săptămâna trecută.

Înainte să te abați de la manual, fă ce zice manualul

Articolul precedent a tratat 8 locuri în care manualul internațional nu se mapează corect în România. Pentru un context rapid, dacă n-ai citit articolul precedent, Baymard Institute e o echipă care filmează, de peste un deceniu, oameni reali care încearcă să cumpere online și publică ce găsește, ca un fel de manual de UX pentru checkout. 92 dintre regulile lor le-am rulat pe prototipul Otter. Sectoarele Bucureștiului care nu sunt orașe, dar nici cartiere. Codurile poștale din RO nu sunt fiabile, așa că nu le poți folosi ca trigger pentru autofill. Plată ramburs cu greutate, egală cu plata cu cardul, în locul "alternative payment". Voucher discoverable fără să fie zgomotos. În toate cele 8 cazuri, recomandarea internațională cerea ajustări. Nu pentru că Baymard ar fi greșit. Pentru că Baymard e calibrat pe SUA și pe Europa de Vest, unde codurile poștale sunt unice per stradă, plata cu cardul domină, iar diviziunea administrativă nu produce "Sector 1 al unui oraș care e și el județ".

Articolul ăsta merge în direcția opusă. Nu "unde se rupe manualul", ci "unde manualul se aplică direct și totuși RO nu l-a adoptat". Sunt 9 patternuri standard, toate documentate și cu exemple. Niciunul controversat. Și fiecare apare încă greșit pe cel puțin un checkout RO pe care l-am deschis săptămâna trecută, de la magazine medii pe Magento 1.x până la nume pe care le știi. Același site nu le greșește pe toate. Dar nu trebuie să cauți mult ca să le găsești pe câte una undeva.

Premisa centrală e simplă. Înainte să te abați de la manual, fă ce spune manualul. Are sens să discutăm dacă sectoarele Bucureștiului ar trebui afișate ca județ sau ca oraș doar după ce ai pus eticheta, ca să rămână vizibilă la focus. Are sens să discutăm cum afișezi rambursul în paritate cu cardul doar după ce mesajele de eroare îți ghidează utilizatorul la fix; nu îl pui să ghicească ce a greșit. Pattern-urile universale Baymard sunt fundația. Ajustările RO sunt etajul. Nu poți construi etajul fără fundație.

O notă de claritate, ca să nu se piardă pe parcurs: tot ce arăt mai jos ca "la Otter" e din prototipul nou (Otter Checkout v3), nu e încă live. Cifrele de comportament, în schimb, provin din checkout-ul actual de producție. Adică, problemele le-am citit pe cele care rulează acum; fixurile le-am construit în prototip.

Date colaterale ca evidence. Pe checkout-ul de producție am măsurat în Microsoft Clarity 7.102 dead clicks în 30 de zile. Dead click înseamnă că utilizatorul apasă pe ceva ce credea că e interactiv, dar nu se întâmplă nimic. O parte din ele vine din zone proprii (input-uri cu hit-target prea mic, butoane dezactivate fără semnalizare clară). Dar partea covârșitoare vine din fricțiunea universală pe care o abordează articolul ăsta. Eticheta care a dispărut: utilizatorul a atins câmpul greșit. Mesajul de eroare care nu spune unde; utilizatorul dă tap pe input ca să vadă ce s-a întâmplat. Submit eșuat fără păstrarea inputului; utilizatorul atinge câmpurile goale. Frictionul ăsta e măsurabil și e reproductibil pe orice checkout RO cu aceleași probleme.

De ce universalele sunt mai ușor de fix-uit

Cele 9 universale au trei proprietăți care le fac mai ieftine decât ajustările locale.

Prima, sursa unică. Fiecare are un guideline Baymard în spate, cu descriere și cu exemple bune și rele. Nu trebuie să descoperi tu patternul, nu trebuie să-i testezi pe userii tăi ca să-l calibrezi pe RO. E deja calibrat pe mii de utilizatori în mii de testări. Te uiți la guideline, implementezi și treci la următorul.

A doua, edge case-uri puține. Persistent labels nu se ceartă cu sectoarele din București. Inline validation funcționează identic pe orice piață. Single phone field nu cere ajustare pentru numerotarea din RO. Universalele sunt universale pentru că le-au testat extensiv și nu au găsit excepții care să justifice un ajustaj per piață.

A treia: Bariera de implementare este scăzută. Toate cele 9 sunt fixuri de o oră până la o zi de muncă. Persistent labels = o componentă + 30 de linii de CSS. Inline validation = un state per câmp plus un derive. Adaptive errors = un switch-case. Markeri required + optional = un prop pe primitivă. Single name + single phone = ștergi un câmp și un dropdown. Tastatura corectă = inputMode derivat automat. Single-column = un CSS grid cu excepție explicită. Preserve input = nu reset-ezi state-ul în catch block. Microcopy = înlocuiești "Continuă" cu textul potrivit. Niciunul nu cere arhitectură nouă. Niciunul nu necesită o refactorizare majoră. Niciunul nu cere săptămâni de QA pentru edge-case-uri RO.

În articolul precedent ai văzut ajustările la care Baymard nu se aplică în RO. Acum vezi unde se aplică direct Baymard. Costul total al celor 9 e o săptămână de design plus o săptămână de dev pe un proiect serios, mult mai puțin pentru cineva care vine cu o primitivă reutilizabilă. Beneficiul este reducerea fricțiunii universale, care lovește indiferent de piață.

1. Persistent labels: eticheta care nu dispare la focus

Baymard cere ca eticheta să rămână vizibilă pe tot parcursul interacțiunii. Cumpărătorul vede numele câmpului la focus, la tastare și în revizuirea finală înainte de submit. Sună evident. Și totuși placeholder-as-label e încă răspândit, mai ales pe site-uri cu template-uri vechi sau pe care nimeni nu le-a redesenat de ani de zile.

O surpriză plăcută înainte să arăt unde se greșește: marile site-uri locale o fac corect. Am verificat eMAG acum câteva zile. Câmpul de nume afișează eticheta "Nume" deasupra și folosește placeholder-ul doar ca exemplu ("ex.: Popescu Alexandru"). Tastezi; eticheta rămâne. Exact pattern-ul bun. Deci nu e o boală a tuturor checkout-urilor RO; e o boală a celor neîntreținute.

Soluția canonică este floating label din Material Design. Eticheta stă inițial peste câmp ca un placeholder. La focus se ridică și se comprimă în zona de sus a chenarului. Rămâne acolo cât timp câmpul are conținut. Cumpărătorul vede mereu ce câmp completează și ce a tastat. Niciun trade-off cu spațiul, pentru că ridicarea etichetei refolosește pixelii eliberați de placeholder.

Cine în RO mai folosește placeholder-as-label? Cărturești, de exemplu. La adăugarea unei adrese în checkout, câmpurile "Nume", "Număr de telefon", "Adresă" și "Cod poștal" sunt toate gri în interiorul câmpurilor de tip input. Niciun label deasupra. Tastezi prenumele și placeholder-ul dispare, dar restul rămâne o fantomă gri până când le atingi. Cumpărătorul completează formularul, ajunge la al patrulea câmp, vrea să verifice ce a scris în al doilea câmp, scrolează înapoi și vede o casetă cu un text fără titlu. Trebuie să-și amintească din context sau să șteargă conținutul ca să reapară placeholder-ul. (Și, în treacăt, același formular cere cod poștal, fix câmpul pe care l-am scos în articolul precedent pentru că nimeni nu și-l știe.)

Stânga, Cărturești: "Nume", "Adresă", "Cod poștal”. Toate cu textul gri în câmp. Scrii o literă; dispar. Dreapta, eMAG: eticheta "Nume" stă deasupra, iar "ex: Popescu Alexandru" e doar un exemplu. Aceeași piață, două decizii.

Pe mobile efectul se amplifică. Tastatura virtuală ascunde jumătate din formular. Utilizatorul tastează câmp după câmp, fără perspectivă globală. Când eticheta dispare, încrederea în propriile răspunsuri scade. Apar greșeli triviale: "telefon" populat cu email, "oraș" cu numele străzii. Pe Otter măsurăm 91,5% trafic mobil la checkout. Ăsta e cazul de bază, nu excepția.

Fixul: floating label cu 3 stări. Idle (eticheta peste input ca placeholder), Focus (se ridică în top-border și se comprimă la 11-12px), Filled (rămâne sus permanent cât timp câmpul are conținut). CSS-ul folosește :has() modern sau o clasă tracker pe parent. O componentă reutilizabilă plus circa 30 de linii CSS. La Otter, fiecare input render-uit prin primitiva Field aplică automat clasa is-always-up. Pattern-ul stă în primitivă; nu poate fi uitat per câmp. Ăsta e disciplina arhitecturală care contează: nu pui regula în documentație, o pui în cod, unde nu poate fi încălcată.

Cost real: o oră de implementare pentru cineva care nu lucrează pentru prima dată cu floating labels. Cinci minute de copy-paste pentru cineva care preia o primitivă deja făcută. Niciun motiv să mai existe placeholder-as-label în 2026.

2. Inline validation: eroarea sub câmp, nu listă mare la submit

Baymard cere validare per câmp, declanșată la blur sau live-typing, cu mesajul afișat imediat sub câmp. Cumpărătorul corectează când mintea lui e încă în câmpul respectiv, nu zece minute mai târziu, când a uitat ce a scris.

Nuanță importantă. Validarea live-typing îl irită dacă apare la prima tastare. Pattern-ul corect e touched-on-blur gating: validezi prima oară la blur, apoi reevaluezi live ca să surprinzi corectările. Combini reactivitatea cu politețea. Nu afișa "email invalid" când utilizatorul abia a tastat litera "a".

Anti-pattern dominant: validare exclusiv la submit. Cumpărătorul completează formularul, atinge butonul, abia atunci află ce e greșit. evoMag duce asta la extrem. Apeși "plasează comanda" și primești un alert() nativ de browser, caseta aia gri cu "www.evomag.ro says" în titlu, care îți listează generic: "Te rugăm să te asiguri că ai ales modalitatea de plată dorită, ai completat numărul de telefon de contact, ai ales modalitatea de livrare dorită." Un alert de browser, în 2026, la finalul unui checkout. Îl închizi cu OK, apoi cauți singur care dintre cele trei e de fapt problema, pentru că mesajul nu-ți spune. Nu marchează câmpul. Nu te duce la el. Doar îți recită lista și te lasă să ghicești.

evoMag, la finalul comenzii. Un alert() nativ de browser cu listă generică, în loc de o eroare la câmpul greșit. Caseta "www.evomag.ro says" e un pattern de prin 2005.

Varianta ceva mai blândă, tot greșită, e block-ul roșu sus, cu lista: "Email invalid. Telefon invalid. Cod poștal invalid." Trebuie să scrollezi în jos, să găsești fiecare câmp marcat, să-l corectezi, să cauți submit din nou. Pe mobile e mai dur: lista de erori ocupă tot ecranul, iar utilizatorul nu vede simultan eroarea și câmpul afectat.

Anti-pattern paralel, mai subtil: validare live fără gating. Utilizatorul tastează "n" pentru "nume" și primește instantaneu mesajul "Câmp prea scurt". Tastează "nu", "Câmp invalid". Trei keystrokes și simte că nu e nimic în regulă. Abandonează sau completează cu frică, ezitând la fiecare literă. Pe cele 7.102 dead clicks pe care le-am măsurat la checkout în 30 de zile, o parte vine exact din momentul ăsta: utilizatorul atinge un câmp deja marcat "invalid" și nu mai înțelege ce vrea de la el sistemul.

Fix-ul: validate-on-blur cu touched-tracking. Per câmp, ții un state xTouched, setat la true la primul blur. Eroarea afișată e xTouched ? xErr : null. La submit forțezi tot, ca utilizatorul să nu scape de eroare prin faptul că nu a fost atins niciodată câmpul. La Otter, emailErr și phoneErr sunt derivate live, iar emailErrShown și phoneErrShown sunt gate-ate pe (emailTouched || submitAttempted). Primitiva Field randeaza eroarea inline, după input, cu un styling distinct. Niciun toast, niciun modal. La submit, dacă există mai multe erori, regula cere și autoscroll la primul câmp invalid sau la un sumar al erorilor.

Cost de implementare: o oră de logică plus disciplina de a nu valida în timp ce utilizatorul încă tastează. Disciplina este gratuită dacă o pui în primitivă o singură dată. E costisitoare dacă o lași la latitudinea fiecărui dezvoltator care adaugă un câmp nou.

3. Adaptive error messages: identifică subproblema, nu doar problema

Baymard cere ca mesajul să identifice subproblema, nu doar faptul că ceva e invalid. Pentru email, subproblemele pot fi: "lipsește @", "domeniul e tăiat", "TLD-ul e necunoscut", fiecare cu un mesaj specific. Pentru telefon: "prea scurt", "format necunoscut", "incomplet". Fiecare variantă cere un fix diferit. Mesajul corect ghidează la fix, nu pune utilizatorul să descopere singur.

Mesajele generice sunt mai ieftine de scris și mai scumpe de procesat. "Verifică datele introduse" îl întoarce la formular, fără hartă. Trebuie să citească câmp cu câmp ca să identifice ce a tastat greșit. Pe formulare lungi, două runde de scroll și frustrare.

Trei anti-pattern-uri pe checkout-urile RO:

Primul, cel mai frecvent: un singur mesaj per câmp, indiferent de eroare. Elefant face exact ăsta. Scrii "erdf" în câmpul de email și primești "Adresa de e-mail nu este corectă", urmat de încă un rând care zice același lucru cu alte cuvinte: "Vă rugăm să introduceți o adresă de e-mail corectă". Două mesaje, zero informații despre ce e în neregulă. Lipsește @? E doar un cuvânt fără domeniu? Mesajul nu spune. Și e interesant, pentru că Elefant face partea grea bine: validarea apare inline, sub câmp, exact unde trebuie (spre deosebire de alerta evoMag de mai devreme). Dar se oprește fix înainte de pasul care contează, pentru a-ți spune ce anume trebuie să corectezi. Inline corect, conținut generic. Cele două probleme sunt separate, iar Elefant le arată pe ambele într-un singur câmp.

Elefant, după ce tastezi "erdf" în câmpul de email. Două rânduri care spun același lucru și niciunul nu spune ce e greșit. Lipsește @? Mesajul tace.

Al doilea: jargon tehnic. "Format invalid: regex /[a-z0-9._%+-]+@.../ nu se potrivește". Utilizatorul nu citește regex. Vede caractere, panică, închide tab.

Al treilea, ușor de văzut pe checkout-urile RO mari: mesaje în engleză pe site-ul românesc. "Invalid email format" lângă un câmp etichetat "Email". Semnalează că eroarea este autogenerată de o librărie, nu de o decizie de design. Site-ul pare neîngrijit; încrederea scade.

Fixul: pentru fiecare câmp, derivă eroarea live folosind un switch-case pe baza subproblemelor. Pentru email: dacă valoarea conține @, dar nu match-uiește pattern-ul complet, afișează "Domeniul pare incomplet. Exemplu: [email protected]". Dacă lipsește @ complet, afișează "Lipsește "@". Exemplu: [email protected]". Pentru telefon, trei subprobleme. Prea scurt (sub 4 cifre), incomplet (4-9 cifre), format incorect (10+ cifre, dar nu începe cu 07/+40/0040). Fiecare cu mesajul său specific. Mesaje în română, cu exemple concrete unde este util, fără jargon.

Câștigul este diferența dintre utilizatorul care abandonează și utilizatorul care fixează în 5 secunde. Cost de implementare: 30 de minute per câmp validat, plus 10 minute de copywriting pentru fiecare variantă.

Contraargument cinstit, pentru că trebuie să-l auzi. Switch-case-urile astea sunt fragile. Adăugarea unei subprobleme noi necesită refactorizare. Pe un checkout cu 6 câmpuri validate adaptiv, păstrarea consistenței între versiuni necesită un review periodic. Costul de mentenanță e mic, dar nu zero. Cine implementează o dată și uită ajunge, în 6 luni, cu mesaje mixte (parțial adaptive, parțial generice). Ținuta disciplinată face parte din pachet.

4. Marchează atât required cât și optional, niciodată zero markeri

Baymard cere ca ambele tipuri de câmpuri să fie marcate explicit. Convenția dominantă: asterisc roșu pentru required, "(opțional)" în paranteze după label pentru optional. Niciun câmp fără marker. Ambiguitatea costă mai mult decât o etichetă suplimentară.

Utilizatorul citește formularul cu trei întrebări per câmp. Ce e? Trebuie? Ce să pun? Dacă la a doua nu există un răspuns vizibil, completează implicit ca să nu rateze. Markerul de optional e permisiunea explicită de a sări. Fără el, presupune că trebuie.

Trei variante de anti-pattern RO:

Prima, dominantă: doar required marcat cu asterisc, restul implicit optional. Utilizatorul vede 4 câmpuri cu asterisc și 6 fără. Cele fără pot fi optional, sau pot fi câmpuri unde s-a uitat markerul. Niciun semnal clar. Defensivitate completă, plus 40-60 de secunde per formular, mai multe greșeli pe câmpuri inutile pe care le-ar fi evitat dacă ar fi știut că poate.

Cea mai radicală variantă e zero markeri, și revenim la Elefant. Formularul lui de livrare n-are nici asterisc, nici "(opțional)", nicăieri. Și nu e o scăpare izolată, e o consecință a deciziei de la punctul 1. Când eticheta trăiește ca placeholder în interiorul câmpului, n-ai unde să pui markerul. Nu poți scrie "Telefon *" deasupra unui câmp care nu are nimic deasupra. Așa că placeholder-ul, ca etichetă, nu costă doar claritatea etichetei, ci omoară și marcajul de obligatoriu în același timp. O decizie proastă, două reguli încălcate.

Tot Elefant. Niciun asterisc, niciun "(opțional)", nicăieri. Nu știi care câmp e obligatoriu până nu-l lași gol și te oprește la submit.

A doua: marker care variază ca formă. Asterisc pe unele câmpuri, cuvântul "obligatoriu" pe altele, iar la al treilea, labelul în roșu. Utilizatorul nu construiește un model mental coerent. Citește câmp cu câmp ca să nu rateze. Pattern-ul ăsta apare des pe checkout-uri unde echipa de design s-a schimbat de două ori și nimeni n-a făcut un audit de consistență.

A treia: optional marcat sub câmp ca hint mic gri "Acest câmp este opțional". Plasare greșită. Utilizatorul scanează labelul, nu hint-ul. Nu vede markerul decât după ce a început să completeze, și de obicei nu se mai întoarce să șteargă.

Fixul: convenție aplicată la nivel de primitivă, nu per câmp. La Otter, primitiva Field render-ează automat <span class="req"> *</span> când prop-ul required e true. Dezvoltatorii nu pot uita să marcheze. Câmpurile opționale primesc explicit "(opțional)" în label, vizibil în formularul live. Inventarul Otter de câmpuri optional e mic și clar: addr2 (bloc, scară, etaj, ap.), pickup person (altcineva ridică), label adresă (Acasa/Birou), newsletter consent. Restul sunt toate required. Niciun câmp fără marker.

Excepția cinstită: un câmp single optional într-o secțiune full required, etichetat cu "(opțional)", arată ciudat vizual. Markerul stă vizibil ca o anomalie. Doi designeri din trei vor cere să-l ascundă "ca să nu strice ritmul". Recomandarea Baymard rămâne: marker explicit; anomalia vizuală e mai ieftină decât ambiguitatea. Dacă utilizatorul ezită 30 de secunde, hotărând dacă să completeze sau nu, ăsta e mai scump decât o pată vizuală.

5. Mobile keyboard layout: tastatura corectă per câmp, nu mereu aceeași

Baymard cere ca tastatura virtuală pe mobile să se potrivească cu tipul de input așteptat. Atributul HTML inputMode controlează ăsta independent de type. tel pentru telefon, email pentru email, numeric pentru cod poștal, CUI sau cod card, search pentru câmpuri de căutare. Browserul mapează la layout-ul corect, utilizatorul tastează direct, fără să comute manual.

Diferența cumulativă e dramatică. Un checkout cu 3 câmpuri numerice fără inputMode corect înseamnă 6 comutări manuale: focus pe câmp, tap pe butonul "123" pentru a introduce cifrele, tastare, focus pe următorul câmp, default revine la "abc", tap "123" iar. Cu inputMode corect, zero comutări.

Anti-pattern dominant: niciun inputMode, default text peste tot. Și aici Elefant revine, pentru că trebuie să fiu cinstit: e al patrulea punct în care apare. Am deschis checkout-ul lui pe telefon, am atins câmpul de telefon și tastatura care a apărut a fost QWERTY, plină de litere. Pentru un câmp în care urmează să tastez exclusiv cifre. Apeși "123", tastezi numărul, treci la următorul, tastatura revine la litere. Frecare microcontinuă. Niciun tap suplimentar nu pare mare, dar adunați devin ostilitate. Fricțiunea crește, greșelile cresc, abandonul crește.

Elefant pe telefon, cu câmpul telefonic în prim-plan. Tastatura este pe litere, nu pe cifre. Apeși "123" la fiecare câmp numeric, de fiecare dată.

Și nu cred că Elefant apare de 4 ori din ghinion. Ăsta e fix observația care leagă întregul articol. Un site care n-a pus regulile într-o primitivă comună le încalcă în cascadă, pentru că fiecare câmp e construit de mână, separat, fără un standard care să le impună. Placeholder-as-label, fără markeri de obligatoriu, nume split, tastatură greșită pe mobile. Patru simptome, o singură cauză: niciun design system care să decidă o dată corectă pentru toate câmpurile.

Anti-pattern legat: type="number" pe câmpuri care nu sunt strict numerice. Aduce spinner-ul pe desktop și validare ciudată pentru overflow. Patternul corect e type="text" plus inputMode="numeric". Layout potrivit pe mobil, fără efecte secundare pe desktop.

Fixul: o linie de cod per câmp, sau zero dacă derivezi automat. La Otter, primitiva Field derivă inputMode din type și autoComplete. Dacă declari type="email", primești layout email. Dacă declari autoComplete="postal-code" (atribut pe care îl pui oricum pentru autofill), primești numeric. autoComplete="cc-number" și autoComplete="cc-csc" primesc tot numeric. Câmpurile speciale se suprascriu manual. "Strada Nr." la Otter folosește ="text" intenționat, pentru că acceptă "FN", "5 bis", "12C/3". CUI primește inputMode="numeric". Locker-search primește inputMode="search".

Testul e simplu. Deschizi checkout-ul pe telefon, atingi câmp după câmp, verifici că tastatura nu cere niciodată comutare manuală. Pe 91,5% mobile la Otter, ăsta e singurul test care contează. Pe orice alt checkout RO cu pondere mobilă similară (adică 80-95% pe e-commerce), e același test.

6. Un câmp pentru nume, un câmp pentru telefon

Două reguli perechi care cer același lucru: simplifică. Un singur câmp pentru numele complet. Un singur câmp pentru telefon, fără subcategorisiri pe mobil, fix sau serviciu, și fără selector de prefix separat.

Argumentul Baymard e empiric. În testele lor, fiecare câmp suplimentar adaugă o sarcină cognitivă fără valoare reală pentru e-commerce. Numele complet e numele complet, indiferent dacă, în spate, aplicația face un split intern. Telefonul e un șir de cifre cu un prefix opțional, utilizatorul îl știe pe dinafară ca o secvență, nu ca două câmpuri.

Anti-pattern RO pentru nume: split prenume + nume în coloane laterale. Altex face exact ăsta, și e un caz interesant pentru că Altex nu e un site neglijent. Etichetele lui stau frumos deasupra câmpurilor, cu asterisc roșu pe cele obligatorii, exact cum cere manualul. Și totuși, împarte numele în "Prenume" și într-un al doilea câmp separat. Confuzia de ordine e ascunsă, dar reală. În formulare administrative RO (CI, contracte, ANAF), numele de familie este primul, prenumele al doilea. În adresarea conversațională prenumele e primul. Utilizatorul tastează "Popescu" în primul câmp, vede că eticheta zice "Prenume", șterge și retastează. Niciun câștig pentru business, două secunde de frustrare per checkout, plus un pas în plus de mutare cu Tab sau Tap între câmpuri. Un site care face etichetele corect, dar tot splituiește numele, arată că regula asta nu e despre neglijență, este despre un reflex moștenit din formularele de hârtie.

Altex face etichetele corect, cu asterisc pe cele obligatorii. Și totuși, taie numele în "Prenume" și într-un câmp separat. Reflex de formular de hârtie, nu de checkout.

Altex face etichetele corect, cu asterisc pe cele obligatorii. Și totuși, taie numele în "Prenume" și într-un câmp separat. Reflex de formular de hârtie, nu de checkout.

Anti-pattern RO pentru telefon: selector de prefix separat, cu 200 de țări, care default-ează la Afganistan alfabetic. Utilizatorul cu un număr +40 standard caută România în listă, apoi tastează restul. Efecte secundare: dropdown-ul se închide pe mobile la primul tap în câmpul următor; autofill-ul browserului nu populează corect cele două câmpuri separate. Pierzi exact economia pe care o pretindeai că o câștigi prin structura formală.

Fixul: un câmp pentru fiecare. Pentru nume: label "Nume complet" cu placeholder "Ion Popescu" sau similar. Pentru telefon: label "Telefon" cu hint "07… sau +40 …" și mask live care formatează cifrele pe măsură ce utilizatorul tastează (Baymard leagă masca de regula câmpului unic de telefon). La Otter, contactul se colectează în Section 1 cu trei câmpuri: nume complet, email, telefon. Single state pentru fiecare: fullName, phone. UI-ul se rendează prin Field (nume și telefon). Aceleași câmpuri, fără split, apar și în address modal și în billingul fizic. Niciun loc unde să se rupă consistența.

Pentru telefon, mask-ul e implementat în formatPhone și aplicat live prin onPhoneChange. Utilizatorul tastează 0721234567, vede automat 0721 234 567. Validation-ul rulează pe varianta normalizată, mask-ul e doar prezentare. inputMode="tel" derivat automat (vezi punctul cu tastatura mai sus) aduce tastatura numerică pe mobile.

7. Single-column, cu excepții logice doar

Baymard cere single-column atât pe mobil, cât și pe desktop. Argumentul: formularul se citește ca un text, de sus în jos. Coloanele laterale forțează utilizatorul să navigheze pe Z, scanând stânga-dreapta, apoi înapoi. Pe formulare lungi (10+ câmpuri) efectul cumulativ e perceptibil. Completare mai lentă, cu o rată de eroare mai mare.

Excepțiile sunt câmpurile logic-conexe. Două câmpuri care reprezintă conceptual același lucru. Pentru adresele RO: județ + oraș (împreună descriu localizarea geografică), stradă + număr (împreună formează o adresă). Pot sta pe același rând pentru că utilizatorul le procesează ca o unitate, nu ca două câmpuri separate.

Pattern dominant pe checkout-uri RO vechi: 2-3 coloane laterale. Cărturești, pe care l-am văzut deja pe etichetă, este și aici un exemplu. Formularul de adresă e pe două coloane: prenume lângă nume, telefon lângă selectorul de țară, județ lângă localitate, adresă lângă codul poștal. Pe desktop, ochiul sare în zigzag, stânga-dreapta, în loc să curgă de sus în jos. Pe mobile coloanele se colapsează, deci acolo nu se vede problema, dar pe desktop completarea e mai lentă și rata de eroare e mai mare, exact ce arată testele. Niciun câștig real de spațiu, doar senzația de formular "compact" care, de fapt, e mai greu de parcurs.

Cărturești, formularul de adresă pe desktop. Două coloane, deci, ochiul sare în zigzag, în loc să curgă de sus în jos. Pe mobile colapsează, dar pe desktop costă.

Efecte secundare concrete. Autofill-ul Chrome și Safari se rupe când câmpurile autoComplete="given-name" și family-name apar în coloane (mai ales dacă sunt și splituite din nume complet, vezi punctul 6). Pe mobil, layout-ul se sparge inconsistent: uneori cad pe rânduri separate, alteori se înghesuie la 50% și placeholder-ul se taie. Plus efectul psihologic. Un formular "compact" arată și complicat. Utilizatorul îl percepe ca fiind mult de completat, chiar dacă numărul total de câmpuri e identic cu un layout vertical. Densitate vizuală mare = fricțiune percepută.

Fixul: single-column pentru tot, override la grid-2 doar pentru excepții logice. La Otter, layout-ul Section 2 folosește .form-grid (un CSS grid cu 2 coloane) în care fiecare câmp primește implicit .span-2 (full width). Județul și orașul sunt singurele care folosesc implicit o coloană fiecare, ca rând conceptual unic. Stradă + număr stau pe .span-2 cu flex inline (nu grid), pentru că proporția e diferită: strada lungă, număr scurt. Vizual citește ca un singur câmp de adresă, nu ca două câmpuri. Toate celelalte câmpuri (nume, email, telefon, addr2 dacă e deschis) sunt .span-2 standard, full-width.

Pe mobile form-grid colapsează automat la single column sub 640 px. Județ + oraș, la lățime mică, ar fi prea înghesuite. Excepția devine excepție doar pe desktop, single-column rămâne regula universală.

Două minute de CSS pentru un grid. Cinci minute pentru regulile de span. Cel mai mare cost e disciplina de a nu băga câmpuri în coloane "ca să arate compact". Argumentul vizual e mereu prezent. Răspunsul e mereu același: utilizabilitatea bate estetica când utilizatorul are deja cardul în mână și e gata să plătească.

8. Păstrează ce-a tastat utilizatorul când submit-ul eșuează

Baymard cere ca după un submit eșuat, valorile completate să rămână în câmpuri. Doar câmpurile cu probleme primesc stilizare de eroare. Restul rămâne ca și cum nimic nu s-ar fi întâmplat. Regula sună evidentă. E încălcată constant pe site-uri vechi.

Logica e simplă. Utilizatorul a făcut deja efortul de a tasta. Dacă a greșit un câmp, a greșit un câmp; nu trebuie pedepsit cu retestarea celorlalte 8. Form wipe distruge încrederea în formular și este cea mai mare cauză a abandonului la finalul checkoutului. Utilizatorul care vede formularul gol după submit nu îl completează din nou. Închide tabul.

Trei variante de anti-pattern RO:

Prima, dominantă pe Magento 1.x sau pe stack-uri custom care nu returnează state-ul. Server-side validation cu redirect post-submit. Noriel are ăsta. Completezi formularul, apeși submit, validarea pică pe un câmp, iar pagina se reîncarcă cu formularul golit. Pierzi nume, telefon, adresă, tot ce-ai tastat. O iei de la zero pentru un singur câmp greșit. Pe un checkout care ia 3-4 minute de completat, cu un copil care plânge în brațe și un cadou de cumpărat, ăsta nu e friction, e knockout. Utilizatorul care vede formularul gol a doua oară nu mai încearcă a treia oară. Închide tabul.

A doua, mai subtilă: formularul păstrează majoritatea câmpurilor, dar șterge câmpurile sensibile (parolă, CUI, telefon) "din motive de securitate". Utilizatorul nu vede semnalul că a fost șters. Pare că a uitat să le completeze. Încercare nouă, aceeași greșeală. Rationalizarea de "securitate" e de obicei falsă; câmpurile astea nu sunt mai sensibile decât cardul, care oricum e gestionat separat.

A treia variantă, specifică formelor cu autocomplete. Utilizatorul alege "București" din dropdown, validation-ul respinge altceva, la re-render dropdown-ul se resetează și trebuie să refacă selecția. Câmpul nu era greșit, dar a fost șters accidental. Frustrare maximă pe câmp care nici măcar nu a fost cauza problemei.

Fixul: implementare standard în React sau Vue: state-ul controlat al câmpurilor nu se resetează la submit eșuat, ci doar se setează un flag de eroare per câmp. CSS-ul pune border roșu. Mesajul de eroare apare lângă câmp sau în rândul global al chip-ului. Valorile rămân exact așa cum au fost tastate. La Otter, regula stă în primitive. Field doar adaugă clasa is-error la label când prop-ul error e true. Valoarea trece neschimbată. AutocompleteField face la fel, cu o regulă explicită: nu șterge niciodată inputul tastat la mismatch, ci doar marchează invalid. Dacă utilizatorul tastează "Bucuresti" fără diacritice, câmpul rămâne cu textul tastat plus marker de invalid, nu se șterge.

Pentru address modal: state-ul newAddr se păstrează prin submit-uri invalide, modalul rămâne deschis și focus-ează primul câmp invalid. Utilizatorul corectează doar ce e greșit, niciodată nu re-tastează ce era deja corect.

Costul de implementare este zero suplimentar dacă state-ul e deja controlat. E disciplină arhitecturală. Niciodată nu resetează state-ul în handler-ul de submit fail. Doar setează flaguri. Dacă cineva din echipă scrie setFullName('') într-un catch block, code review trebuie să-l prindă. Regula merge în CONTRIBUTING.md sau direct ca custom lint rule, dacă te omoară edge case-urile.

9. Microcopy CTA care spune exact ce face butonul

Baymard cere ca butonul principal să comunice intenția de acțiune. Dacă clickul deschide un redirect către un procesator extern, butonul trebuie să anunțe clar: "Plătește cu cardul", "Continuă la plată". Dacă plasează comanda direct (ramburs sau pickup), trebuie să fie clar că ăsta e finalul: "Plasează comanda", "Trimite comanda".

Argumentul e psihologic. Utilizatorul, la finalul checkoutului, are o ezitare reflexă. "Ce se întâmplă dacă apăs?" Butonul este ultima informație vizuală înainte de click. Dacă spune doar "Continuă", nu rezolvă ezitarea. Amână decizia. Microcopy-ul precis transformă ezitarea în confirmare.

Două variante de anti-pattern RO:

Prima: "Continuă" sau "Mai departe" generic pe toate butoanele, indiferent dacă duce la redirect, la următorul pas, sau la confirmare finală. La click pe ultimul "Continuă" utilizatorul se trezește pe Netopia, PayU sau Stripe într-un context vizual nou. Mulți întorc reflex pentru că pare că s-au înșelat. Click înapoi, abandonment. Și aici e punctul în care nu mai pot arăta cu degetul spre site-urile neîntreținute. La microcopy de CTA cade aproape toată lumea, inclusiv eMAG și Altex, adică fix site-urile care, la celelalte puncte, erau exemple bune. "Continuă" generic la pasul de plată e atât de răspândit, încât a devenit invizibil. Nimeni nu-l mai pune sub semnul întrebării, deși e o linie de cod pentru a-l face specific.

A doua: același text pentru toate metodele de plată. "Plasează comanda" pe ramburs (corect; comanda chiar se plasează). "Plasează comanda" pe card (greșit; urmează redirect și autorizare la procesator). Utilizatorul așteaptă confirmare instantă, primește pagina procesatorului și se confuzează.

Fix-ul: microcopy adaptiv per state. Butonul citește metoda de plată curentă și rendează textul potrivit:

  • Card → "Plătește cu cardul" sau "Continuă la plată" (semnalează redirect explicit)

  • Mokka sau alt BNPL → "Continuă cu Mokka" (semnal de redirect spre BNPL)

  • Ramburs → "Plasează comanda" (action completă, nu mai urmează nimic)

  • Pickup → "Rezervă comanda" (semantic distinct, comanda e rezervată în magazin)

La Otter, implementarea desktop a CTA-ului adaptiv emite: "Continua la plata" pentru card, "Continua cu Mokka" pentru Mokka, "Plaseaza comanda" pentru ramburs, "Rezerva comanda" pentru pickup. Logica e un switch pe selectedPaymentMethod direct în render-ul butonului. Cinci linii de cod, plus inventarul de texte. Și totuși, la mulți competitori RO găsești același "Continuă" pe toate variantele.

Recunosc o gură de fricțiune la mine în prototip: mobile sticky CTA folosește variantele scurte, din lipsă de spațiu. Card → "Continua", Mokka → "Mokka", ramburs → "Plaseaza", pickup → "Rezerva". Forma scurtă pentru card, "Continua", e fix anti-pattern-ul descris mai sus. Auditul propriu îl marchează PARȚIAL. Recomandarea este "Card →" sau "Plătește cu cardul" pe mobil, în funcție de lățimea măsurată. Nu l-am rezolvat încă. Nu pretind că prototipul este perfect. Pretind că standardul e clar, iar restul e doar un finisaj.

Outro

Niciuna dintre astea nouă nu e specifică RO. Toate sunt pași standard într-un checkout internațional din 2025-2026, documentate de Baymard cu exemple și testare în spate. Toate sunt mai ieftine de implementat decât ajustările RO din articolul precedent (sectoare, addr2, ramburs, voucher). Și totuși, paradoxal, sunt și cele mai des ignorate.

De ce. Cred că e pentru că au un aer de "detalii de finisaj". PM-ul aude "marcăm câmpurile optional cu paranteze" și pune ticket-ul în backlog la coadă, pentru că nu sună ca o feature. CEO-ul aude "schimbăm CTA-ul de la Continuă la Plătește cu cardul" și nu vede return on investment, pentru că nimeni nu măsoară microcopy în KPI-urile tradiționale. Echipa tehnică aude "validare inline pe blur" și o pune după sprintul de migrare. Și apoi, doi ani mai târziu, încă e acolo.

Fricțiunea universală e invizibilă pentru cine construiește, vizibilă pentru cine completează. Cele 7.102 dead clicks în 30 de zile pe care le-am măsurat pe checkout-ul de producție sunt urma exactă a friction-ului ăsta. Câmpuri în care utilizatorul atinge cu intenție și nimic nu răspunde așa cum credea că va răspunde. Multe vin din zone proprii, dar partea cea mai mare e fricțiunea universală pe care articolul ăsta tocmai l-a inventariat.

Recap practic. Dacă faci doar pe astea nouă într-o săptămână de design plus o săptămână de dev, recuperezi fricțiune pe care nu trebuia să o ai, în primul rând. Persistent labels prima; e cea mai vizibilă. Inline validation a doua, repară cel mai mare bloc de abandonment. Adaptive errors a treia, pentru că rezolvă cu copywriting ce nu poți repara cu UX. Apoi markeri required/optional. Apoi tastatura mobile. Apoi single name/single phone. Apoi single-column. Apoi preserve input. Microcopy CTA la final, cel mai ieftin dintre toate, dar cu cel mai mare impact asupra confirmării la submit-ul final.

Săptămâna viitoare: studii de caz din Otter. Cum am decis ce să facem când Baymard nu ne mai spune. Două situații concrete în care manualul tace, iar realitatea din RO impune o decizie. Sectoarele Bucureștiului în contextul cross-checkout-ului. Voucher in line vs voucher în drawer. Decizii de design unde am tatonat zile întregi, am intrat în Microsoft Clarity să citim session recordings, am ieșit cu un compromis care n-are precedent în niciun guideline.

Ești deja pe listă, așa că articolele 3 și 4 îți pică în inbox când apar. N-ai de făcut nimic. Dacă vrei contextul complet, articolul precedent, cele 8 ajustări RO, este aici. Articolul ăsta se citește și standalone, dar perechea funcționează mai bine: întâi vezi unde manualul se rupe în RO, apoi unde nu se rupe și totuși RO nu l-a adoptat.

Ne vedem la articolul 3.