Field validation
The vocabulary: one tone per state, never color alone
Shown in the session list. Optional.
Saved. This name is available.
This name is close to another session.
A session name is required.
On the field: required, error, warning, success
A session name is required.
Spaces become hyphens: auth-flow.
Workspace found.
Live: type the email, watch it validate
Enter a valid email address.
Multiple rules: the checklist resolves as you type
- At least 12 characters
- One uppercase letter
- One number
Edited 0 times · email invalid · password 1/3 rules. The island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform; this is the intended API,// not yet exported from @dizyx/nockerl-react (promotion tracked). The message + icon + border are one// bundled status layer on the field: error/warning/success/neutral, never color alone.)
export function SessionForm() { const [email, setEmail] = useState(''); const valid = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email); const status = email === '' ? 'neutral' : valid ? 'success' : 'error';
return ( <> <ValidatedField label="Session name" required value={name} onValueChange={setName} status="error" message="A session name is required." />
<ValidatedField label="Notify email" type="email" leadingIcon="@" value={email} onValueChange={setEmail} status={status} message={status === 'error' ? 'Enter a valid email address.' : 'We send run summaries here.'} />
{/* counter + over-limit error, and a multi-rule checklist */} <ValidatedField label="Title" value={title} onValueChange={setTitle} maxLength={32} /> <ValidatedField label="New password" type="password" value={pwd} onValueChange={setPwd} status={pwdOk ? 'success' : 'error'} issues={[{ label: 'At least 12 characters', ok: pwd.length >= 12 }]} /> </> );}// Android: Jetpack Compose (canonical). The shipped validation path is the M3// OutlinedTextField error state: isError + a supportingText line in colorScheme.error// (see auth.ui.LoginScreen / chat.ui.SessionCreationSheet). Required / warning /// success / counter are NOT yet extracted on Android; see the drift note below.import androidx.compose.material3.OutlinedTextFieldimport androidx.compose.material3.OutlinedTextFieldDefaultsimport androidx.compose.material3.Textimport com.nockerl.app.core.theme.NockerlControlShape
OutlinedTextField( value = email, onValueChange = { email = it; viewModel.clearError() }, label = { Text("Notify email") }, singleLine = true, isError = emailError != null, supportingText = { // bodySmall line under the field; colorScheme.error when isError = true. Text(emailError ?: "We send run summaries here.") }, modifier = Modifier.fillMaxWidth(), shape = NockerlControlShape, // the 12dp control radius token colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = MaterialTheme.colorScheme.primary, // brand cyan errorBorderColor = MaterialTheme.colorScheme.error, // status red ),)// macOS: SwiftUI (canonical). Voice carries the richest validation vocabulary: a// status message is a sibling caption Text in NockerlTheme.error / .warning / .success// with an SF Symbol, never color alone (SettingsView API-key SUCCESS row;// OnboardingView ERROR / WARNING captions). There is no bundled message slot yet.TextField("you@example.com", text: $email) .textFieldStyle(.plain) .foregroundStyle(NockerlTheme.onSurface) .nockerlFieldBackground() // canvasAlt inset well + hairline, rounded rect
// The validation message sits directly under the field as a sibling:if let error { // ERROR Label(error, systemImage: "exclamationmark.triangle.fill") .font(.caption).foregroundStyle(NockerlTheme.error)} else if valid { // SUCCESS Label("We can reach you here.", systemImage: "checkmark.circle.fill") .font(.caption).foregroundStyle(NockerlTheme.success)} else { // HELPER (neutral) Text("We send run summaries here.") .font(.caption).foregroundStyle(NockerlTheme.onSurfaceMuted)}Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
label * | string | Persistent visible label, bound to the input via htmlFor / id. | |
value * | string | Controlled value of the field. | |
onValueChange | (value: string) => void | 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 | 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 }[] | 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 | Shows a right-aligned character counter; turns to the error treatment (counter + border) when over the cap. | |
leadingIcon | string | Optional glyph inside the well, before the text. | |
type | 'text' | 'email' | 'password' | … | 'text' | Underlying input type / keyboard. |
Compose validates through the M3 OutlinedTextField error 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 bodySmall Text. 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 | Current text and the edit callback. Clear the error here (viewModel.clearError()) so the message updates live. | |
label | @Composable (() -> Unit)? | null | Floating label slot; pass { Text("Notify email") }. |
shape | Shape | NockerlControlShape | 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 sibling
Text / 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 | The error tone (#EF5350 dark). Use on the message Text + an exclamationmark.triangle.fill symbol, as in OnboardingView’s denied caption. | |
NockerlTheme.warning | Color | The warning tone (#FFC107). A caution note under the field (OnboardingView uses .foregroundStyle(.orange) for this). | |
NockerlTheme.success | Color | The success tone (#4CAF50 dark). Paired with checkmark.circle.fill, as in the SettingsView “API key saved” confirmation. | |
NockerlTheme.onSurfaceMuted | Color | The neutral helper line (.font(.caption)), the resting supporting text. | |
.nockerlFieldBackground() | ViewModifier | The recessed input well the message attaches to: canvasAlt inset + hairline, rounded rectangle. |