Input

.input e controlul — cutia în care scrii. Nu știe de etichetă, ajutor sau eroare: alea sunt ale câmpului, pe Field. Ce e în interiorul bordurii — iconiță, text, buton, sau eticheta însăși — e Input group.

Trei nume, o singură graniță — bordura controlului.
Input · .input e controlul: cutia în care scrii. Nu știe de etichetă, ajutor sau eroare.
Field · .field e tot ce e în afara bordurii: etichetă, ajutor, eroare, orientare. Agnostic la control.
Input group · .input-group e tot ce e în interiorul bordurii: iconiță, text, buton, scurtătură — sau eticheta însăși. Nu e o pagină, e același control cu ceva în el.
Se cuibăresc, nu concurează: .field > .input-group > .input. Împărțirea e a lui shadcn — Input e controlul, Field „combines labels, controls, and help text”, InputGroup „adds addons, buttons, and helper content to inputs”.

React · react/input.tsx — forma implicită

Aceleași clase ca markup-ul HTML, împachetate. Mărimea și tonul sunt props, nu clase scrise la call-site.

import { Input } from "@/components/ui/input"

<div className="field">
  <label className="field-label" htmlFor="f-name">Nume firmă</label>
  <Input id="f-name" placeholder="SC Exemplu SRL" />
</div>

{/* Sub 1024px mărimea lg e OBLIGATORIE — iOS Safari mărește pagina la focus sub 16px */}
<Input size="lg" placeholder="Nume firmă" />

{/* Tonal: fill bej-0 + bordură tonală. NUMAI pe gazdă albă sau bej-0. */}
<Input tone="tonal" placeholder="Caută" />

{/* Ce stă ÎN interiorul bordurii vine din react/input-group.tsx — contract separat */}
{/* import { InputGroup, InputGroupAddon, InputGroupText } from "@/components/ui/input-group" */}
Clasele DS, nu utilitare. Componenta poartă clasele din bono-ds.css și nu re-declară stil în TypeScript. Un rând scris în utilitare ar fi a doua definiție a aceluiași lucru, iar prima schimbare de token ar ajunge la HTML și nu la React — exact deriva pentru care există scripts/check-stamp-drift.py. scripts/build-catalog.py pică pe utilitare în react/. Detalii în react/README.md.
— fără dependențe în afară de React
La adopție se scot două lucruri. (1) selection:bg-primary / selection:text-primary-foreground de pe <Input>-ul shadcn — selecția de text e declarată o dată, neutru, pe ::selection, iar o clasă utilitară bate o regulă de document: fără asta, un cmd+A într-un câmp focusat iese cu bandă roz plin și text alb. (2) Orice font-size pus la call-site (text-sm, text-base) — mărimea e un prop (size="lg"), nu o clasă de deasupra.

Markup canonic · components/input.json

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

Specificații

Cemd (implicit)lg
înălțime · radius40 · --r-lg (20)44 · --r-lg (20)
padding orizontal = radius1620
textInter 14/20 · 400 · inkInter 16 · 400 · ink
bordură1px --c-input-border (ink @ 30%) · hover --c-input-border-hover
focusborder --c-fog + halo 0 0 0 3px rgba(122,117,112,.14) — neutru, niciodată brand
eroare · succesborder --c-error + fundal --c-error-bg · border --c-success; mesajul e al câmpului (.field-help)
disabledfundal --c-bej-1 · text --c-mist
placeholder--c-mist · exemplu de format („RO12345678”), nu instrucțiune

padding = radius — textul începe unde se termină curba

16
text
16

Eticheta și ajutorul au același padding-left: 16px, ca textul din label, din input și din help să pornească din aceeași coloană. Distanțele în afara controlului (4 în câmp, 18 între câmpuri) sunt pe Field · Spațiere.

Suprafața · regula de context

Inputul nu-și alege fundalul singur — îl dă cardul de sub el. Alb pe bej-1, tonal pe alb, alb pe canvas bej-0. Motivul e măsurabil: conturul de 30% dă 2.00:1 peste alb (sub 3:1, WCAG 1.4.11), deci pe card alb inputul trece pe .tonal, cu contur la 46% (3.16:1). Tonalul nu stă niciodată pe bej-1 — acolo cade la 2.78:1.

A · alb pe card bej-1 — implicit (Dublin, dashboard, modal)
Apare pe facturile emise.
B · tonal pe card alb — .input.tonal (setup, wizard)
Apare pe contractul de servicii.
C · pe canvas bej-0 cu dot grid (flow) — alb, fără fundal sau bordură în plus · câmpurile sunt live
Validat în Registrul Comerțului.
Format email invalid.

Textarea

E .input pe un <textarea>, nu o clasă separată. Fără regula dedicată ar moșteni height: 40px și s-ar strânge la un rând.

label deasupra · .field
label în interior · .input-group + block-start
textarea.input          height auto · min-height 88px (~4 rânduri) · padding 10 16
                        resize vertical · scroll intern
cu etichetă în bordură  min-height 60px (~3 rânduri) · padding 2 16 7 · resize vertical
.input-group >          resize none — marginea ar trece peste toolbar (vezi mai jos)

Ce stă în interiorul bordurii → .input-group

Iconiță, text static (prefix/sufix), buton, spinner, scurtătură, contor — și eticheta de brand din bordură. Toate au contract propriu din v8.2: Input group (components/input-group.json).

Granița, o dată: .field deține ce e în afara bordurii, .input-group ce e în interiorul ei, .input e controlul gol. .field > .input-group > .input.

.input-group · carcasa ia bordura, controlul o pierde
⌘K

OTP

Celula e un .input obișnuit — .otp schimbă mărimea și forma, nimic altceva. Suprafața urmează regula de context de mai sus: alb pe bej-1, tonal pe alb.

pe card bej-1 → celule albe
—
pe card alb → celule tonale
—
.otp .input      48 × 56 · cifra 20px/500 tabular · radius --r-md (nu pastilă)
                 = .input.lg + 1 treaptă pe X (44→48), 3 pe Y (44→56)
                 lățimea e plătită de rând (6 celule pe un telefon), înălțimea nu
.otp             gap 8 · .otp-sep opțional la mijloc · mobil: gap 6, mărimi neschimbate

Un input per cifră, fiecare cu aria-label — nu un input cu letter-spacing, care rupe paste-ul și cititorul de ecran. Eroarea e mesaj sub câmp, nu contur roșu pe fiecare celulă.