Skip to content

Field validation

In review
Live · web

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

Spaces become hyphens: auth-flow.

Workspace found.

Live: type the email, watch it validate

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 }]} />
</>
);
}
PropTypeDefaultDescription
label *stringPersistent visible label, bound to the input via htmlFor / id.
value *stringControlled value of the field.
onValueChange(value: string) => voidChange 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.
messagestringThe 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.
requiredbooleanfalseAdds a status-colored * after the label and sets the input required.
maxLengthnumberShows a right-aligned character counter; turns to the error treatment (counter + border) when over the cap.
leadingIconstringOptional glyph inside the well, before the text.
type'text' | 'email' | 'password' | …'text'Underlying input type / keyboard.
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.