Text fields
components specs/components/text-fields.kmd
Text input control — 2 variants (filled, outlined), single-line and multi-line, with label, helper text, error state, leading/trailing icons, character counter. Material parity (`/components/text-fields`).
Quando esta spec se aplica
Triggers primários
- Implement text input anywhere
Todos os triggers
- Build any text input in a Koder UI
- Pick between filled and outlined variant
- Add validation, helper text, error state to an input
Corpo da especificação
Spec — Text fields
Facet Visual do Koder Design. Material parity: https://m3.material.io/components/text-fields.
2 variants
| Variant | Visual | Use |
|---|---|---|
| Filled | Surface-variant bg + bottom border (accent on focus) | Default — denser, more visual weight |
| Outlined | Border on all sides + transparent bg | Forms with grouped fields; cleaner on light bg |
Pick one per surface — don't mix in the same form.
Anatomy
[leading icon?] Label (floating) [trailing icon?]
[counter?]
┌──────────────────────────────────────────────────────┐
│ [icon?] Input value here [icon?] │
└──────────────────────────────────────────────────────┘
Helper text or error message [chars/max]
- Container: 56 px height (default), 48 px (compact)
- Label:
body-largefloating (above when focused/filled) - Input text:
body-large - Helper / error:
body-smallbelow - Counter:
body-smallright-aligned below (whenmaxLength) - Icons: 24 px, leading + trailing optional
R1 — Label behavior
Two label modes:
- Floating (default) — label sits inside container at rest; floats up when focused or has value
- Always-above — label permanently above the container
Floating is Material standard; always-above is gnome/carbon_ibm
preset convention.
R2 — States
Per interaction/states.kmd:
| State | Filled | Outlined |
|---|---|---|
| Empty (rest) | Label inside container | Label inside container |
| Focused | Label floats up + bottom border accent (2 px) | Label floats up + border accent (2 px) |
| Filled (has value) | Label stays floated | Label stays floated |
| Hovered | Bottom border darker | Border text-muted darker |
| Error | Border error + helper text becomes error text | Same |
| Disabled | Opacity 0.38; no interaction | Same |
R3 — Single-line vs multi-line
- Single-line: 56 px height default, scrolls horizontally
- Multi-line: starts at 56 px, grows to a max (configurable; default max 5 lines), then scrolls vertically inside
<input type="text">vs<textarea>semantics in HTML
R4 — Specialized input types
| Type | Modifier |
|---|---|
inputmode="email", autocomplete=email, validation regex | |
| Number | inputmode="numeric", no spinners (CSS hide), pattern validation |
| Phone | inputmode="tel", mask per locale |
| Password | type="password" + show/hide eye icon (trailing) |
| Search | type="search", ⌘K shortcut hint, clear button (trailing X) |
| Date | Pair with date picker (per future date-pickers.kmd) |
R5 — Helper text + error
Below the input container, single line:
Filled state: "Used for sign-in and password reset" (text-muted)
Error state: "Email must include @" (error color)
Filled + counter:"123/200" (text-muted, right)
When error: helper text replaced by error message. Both styled with
body-small role. Error must be associated via aria-describedby
linking input → error element.
Error format per errors/user-facing-messages.kmd: humanized,
specific, no "Invalid" / "Wrong".
R6 — Validation
| Validation type | When |
|---|---|
required | On submit, not while typing |
| Format regex | On blur (after the user finishes typing) |
| Server-side | On submit; debounced check on blur for sensitive fields (email exists) |
| Real-time (length, char restrictions) | While typing — give immediate feedback |
NEVER validate on first keystroke for format-based rules (annoying; "a" → "Invalid email" before user can type more).
R7 — Leading + trailing icons
- Leading icon: signals input type (mail icon for email, search icon for search) — decorative, no interaction
- Trailing icon: actionable (clear X, show/hide password, voice
input mic, calendar picker open) —
<button>element witharia-label
Tap target on trailing icon: 24 px visible, 40 px hit zone.
R8 — Accessibility
<label for="id">connection MANDATORY (oraria-label/aria-labelledby)- Helper text linked via
aria-describedby - Error linked via
aria-describedby+aria-invalid="true"on the input - Required fields:
aria-required="true"(don't rely on*alone) - Autocomplete attribute set for personal data (email, name, address)
- Screen readers announce label → value → state on focus
R9 — Forbidden patterns
- ❌ Input without visible label (placeholder is NOT a label)
- ❌ Placeholder containing format example ("user@example.com") shown only until user types — use helper text instead (or both)
- ❌ Custom validation messages that contradict spec
(
errors/user-facing-messages.kmd) - ❌ Inputs that look like text but aren't focusable (use a real
<button>if action) - ❌ Disabling submit until valid (use clear error reveal on submit instead)
R10 — Per-preset variation
| Preset | Variant default |
|---|---|
material3 | Filled |
gnome | Outlined |
windows_11 | Filled |
windows_95 | 3D bevel border (Outlined visually) |
ios_cupertino | Outlined |
carbon_ibm | Underline only (lighter than Filled bottom-border) |
shadcn | Outlined |
brutalist | Outlined + 3 px thick border |
Cross-link
interaction/states.kmd— hover/focus/error visualsthemes/color-roles.kmd— accent / error / text-muted bindingserrors/user-facing-messages.kmd— error message formatfoundations/ux-writing.kmd— label + helper text stylefoundations/elements.kmd— Control family
Requirements (testable)
Requirement: Floating label floats on focus or value {#req-text-fields-label-float}
A text field using the floating label mode SHALL keep the label inside the container at rest and SHALL float the label above the container whenever the field is focused or holds a non-empty value, so the label remains visible while the user types.
Scenario: Label floats when the empty field receives focus
- GIVEN a floating-label text field that is empty and unfocused with its label rendered inside the container
- WHEN the user focuses the field
- THEN the label moves to the floated position above the container
- AND the label remains visible while the input is focused
Scenario: Label stays floated when the field has a value and loses focus
- GIVEN a floating-label text field that contains the value "hello"
- WHEN the field loses focus
- THEN the label stays in the floated position above the container
- AND the label does not return inside the container
Requirement: Error state marks the input invalid and is announced {#req-text-fields-error-aria}
When a text field is in the error state, it SHALL apply the error border color, SHALL replace the helper text with the error message styled as body-small, SHALL set aria-invalid="true" on the input, and SHALL associate the error element to the input via aria-describedby.
Scenario: Entering the error state exposes the error to assistive tech
- GIVEN a text field with helper text "Used for sign-in and password reset"
- WHEN validation fails and the field enters the error state with message "Email must include @"
- THEN the input carries
aria-invalid="true" - AND the input's
aria-describedbyreferences the element that renders "Email must include @" - AND the helper text "Used for sign-in and password reset" is replaced by the error message
- AND the container border uses the
errorcolor role
Requirement: Format validation is deferred, never on first keystroke {#req-text-fields-validation-timing}
A text field SHALL NOT run format-based validation on the first keystroke; format regex validation SHALL run on blur (after the user finishes typing) and required validation SHALL run on submit rather than while typing.
Scenario: Typing one character does not trigger a format error
- GIVEN an empty email text field with a format regex rule
- WHEN the user types the single character "a"
- THEN no error message is shown
- AND the field is not marked invalid
Scenario: Format is validated on blur
- GIVEN an email text field containing "a" that has focus
- WHEN the field loses focus (blur)
- THEN the format regex validation runs
- AND the error state is applied because "a" is not a valid email
Requirement: Trailing icons are real actionable controls {#req-text-fields-trailing-icon}
A trailing icon that performs an action (clear, show/hide password, voice input, open picker) SHALL be a <button> element carrying an aria-label, and SHALL expose a hit zone of at least 40 px even though the visible icon is 24 px.
Scenario: Password show/hide trailing icon is a labeled button
- GIVEN a password text field rendered with a show/hide trailing icon
- WHEN the trailing icon is inspected in the DOM
- THEN it is a
<button>element - AND it has a non-empty
aria-label - AND its interactive hit zone measures at least 40 px while the visible glyph measures 24 px
Scenario: Activating the trailing button toggles password visibility
- GIVEN a password text field with
type="password"and a show/hide trailing button - WHEN the user activates the trailing button
- THEN the input's type toggles to reveal the entered characters
- AND activating it again returns the input to
type="password"
Requirement: Every field has a programmatic label, never placeholder-as-label {#req-text-fields-accessible-label}
Every text field SHALL be associated with a programmatic label via <label for="id">, aria-label, or aria-labelledby, and SHALL NOT rely on placeholder text as the field's label.
Scenario: Input is connected to a label element
- GIVEN a text field rendered with id "email" and a visible label "Email"
- WHEN the field markup is inspected
- THEN a
<label for="email">(or anaria-label/aria-labelledbyon the input) associates the label to the input - AND a screen reader focusing the input announces the label text
Scenario: Placeholder alone does not satisfy the label requirement
- GIVEN a text field that has a
placeholderattribute but no<label>,aria-label, oraria-labelledby - WHEN the field is validated for accessibility conformance
- THEN the field is reported as non-conformant
- AND the placeholder is not treated as the accessible name
Referências
specs/foundations/elements.kmdspecs/interaction/states.kmdspecs/themes/color-roles.kmdspecs/errors/user-facing-messages.kmdspecs/foundations/ux-writing.kmd