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.
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" */}
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
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
Specificații
| Ce | md (implicit) | lg |
|---|---|---|
| înălțime · radius | 40 · --r-lg (20) | 44 · --r-lg (20) |
| padding orizontal = radius | 16 | 20 |
| text | Inter 14/20 · 400 · ink | Inter 16 · 400 · ink |
| bordură | 1px --c-input-border (ink @ 30%) · hover --c-input-border-hover | |
| focus | border --c-fog + halo 0 0 0 3px rgba(122,117,112,.14) — neutru, niciodată brand | |
| eroare · succes | border --c-error + fundal --c-error-bg · border --c-success; mesajul e al câmpului (.field-help) | |
| disabled | fundal --c-bej-1 · text --c-mist | |
| placeholder | --c-mist · exemplu de format („RO12345678”), nu instrucțiune | |
padding = radius — textul începe unde se termină curba
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.
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.
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.
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.
.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ă.
În modal · mărimea lg
Modal-ul e un context dedicat unei singure acțiuni, deci folosește exclusiv lg (44h / 16px) — input și buton la aceeași înălțime. Restul e pe Modal.