Typeahead

Câmp cu sugestii ancorate. Refolosește .input pentru câmp și .popover pentru listă — nu are chrome propriu, deliberat.

Markup canonic · components/typeahead.json

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

Câmpul E .input. Neschimbat.

Regula care ține componenta onestă: typeahead-ul nu are câmp propriu. Nicio înălțime proprie, nicio bordură proprie, niciun radius propriu — folosește .input exact așa cum e. Tot ce adaugă e ancorarea listei și starea „niciun rezultat". Un al doilea input, oricât de bine copiat, derivă de primul la prima schimbare de token.
.typeahead            position: relative + gap 4px (ca .field)
.typeahead > .popover top: calc(100% + 6px), left/right 0, max-height 280px
.typeahead-match      substringul potrivit — --c-accent-deep, weight 600
.typeahead-meta       rândul secundar — 12px/16px, --c-fog
.typeahead-empty      starea fără rezultate — 14px, --c-fog, centrat

Combobox · .input.has-chevron

typeahead = text liber care filtrează o listă deschisă. combobox = alegere dintr-o listă închisă, deci arată ca .select — dar deschide un .popover, nu meniul nativ. Chevronul e SVG-ul copiat literal din .select: dacă se schimbă o săgeată, se schimbă amândouă în același commit.

Fără rezultate

Niciun client pentru „qwerty"

Accesibilitate

input       role="combobox" aria-expanded aria-controls aria-activedescendant
            autocomplete="off"
lista       role="listbox"
rândul      role="option"
ales        .is-selected + aria-selected="true" + bifa .popover-check
sub săgeată .is-active   ← aria-activedescendant arată aici, NU aria-selected
tastatură   ↑ ↓ navighează · Enter alege · Esc închide și întoarce focusul în câmp

Focusul rămâne în .input tot timpul; rândul de sub săgeată se marchează cu .is-active plus aria-activedescendant, nu prin mutarea focusului — altfel utilizatorul nu mai poate scrie. Cele două atribute nu stau pe același rând: aria-selected spune care e valoarea, aria-activedescendant spune unde a ajuns săgeata.