Persistent visible label, bound to the input via htmlFor / id.
value *
string
n/a
Controlled value of the field.
onValueChange
(value: string) => void
n/a
Change handler. Re-run validation here to update status / message live.
status
'neutral' | 'error' | 'warning' | 'success'
'neutral'
Validation state. Drives the message tone, the status glyph, and the field border. neutral = the resting helper line, no border recolor. Warm status tokens only, never the brand cyan.
message
string
n/a
The single line under the field (helper / error / warning / success copy). Pairs with a glyph so it is never color alone. The error message renders role="alert" and is wired via aria-describedby.
issues
{ label: string; ok: boolean }[]
n/a
Multiple rules rendered as a live checklist (e.g. password requirements). When set, replaces message.
required
boolean
false
Adds a status-colored * after the label and sets the input required.
maxLength
number
n/a
Shows a right-aligned character counter; turns to the error treatment (counter + border) when over the cap.
leadingIcon
string
n/a
Optional glyph inside the well, before the text.
type
'text' | 'email' | 'password' | …
'text'
Underlying input type / keyboard.
Compose validates through the M3 OutlinedTextFielderror state. Only the
error path ships today: isError flips the border + supportingText to
colorScheme.error. Required indicators, the warning / success tones, the counter,
and a multi-rule checklist are not yet extracted on Android (drift below).
Parameter
Type
Default
Description
isError
Boolean
false
Renders the error treatment: error-colored border, label, and supportingText.
supportingText
@Composable (() -> Unit)?
null
The helper / error line under the field, a bodySmallText. Tints to colorScheme.error while isError.
colors
TextFieldColors
OutlinedTextFieldDefaults.colors(…)
Set errorBorderColor = colorScheme.error (status red) and focusedBorderColor = colorScheme.primary (brand cyan).
value / onValueChange *
String / (String) -> Unit
n/a
Current text and the edit callback. Clear the error here (viewModel.clearError()) so the message updates live.
The 12dp control radius token, shared with every field.
SwiftUI has no bundled message slot. The field chrome is the
.nockerlFieldBackground() modifier; the validation message is a siblingText / Label below it, tinted by a NockerlTheme status color and paired with
an SF Symbol so it is never color alone.
Style
Type
Default
Description
NockerlTheme.error
Color
n/a
The error tone (#EF5350 dark). Use on the message Text + an exclamationmark.triangle.fill symbol, as in OnboardingView’s denied caption.
NockerlTheme.warning
Color
n/a
The warning tone (#FFC107). A caution note under the field (OnboardingView uses .foregroundStyle(.orange) for this).
NockerlTheme.success
Color
n/a
The success tone (#4CAF50 dark). Paired with checkmark.circle.fill, as in the SettingsView “API key saved” confirmation.
NockerlTheme.onSurfaceMuted
Color
n/a
The neutral helper line (.font(.caption)), the resting supporting text.
.nockerlFieldBackground()
ViewModifier
n/a
The recessed input well the message attaches to: canvasAlt inset + hairline, rounded rectangle.
Cross-platform drift (tracked for reconciliation)
The bundled status layer (one status + message + glyph, four tones) is the
target; these are the real divergences between the shipped platforms:
Vocabulary coverage: Voice ships all four tones (helper / error / warning /
success, each a caption + SF Symbol). Android ships error only (M3 isError +
supportingText). Web bundles all four as status. Warning + success + the
required indicator are owed on Android.
No shared component: the validation message is hand-assembled per screen:
Android repeats supportingText inline; Voice repeats a sibling Text. An
extracted ValidatedField is owed on Android + web (it follows the text-field
drift, since there is no NockerlTextField yet either).
Counter + multi-rule list: designed here (and shown live in the demo); neither
app ships a character counter or a rule checklist yet.
Required indicator: the * mark is web-only so far; Android marks required in
copy (“required” in the label string), Voice not at all.