Validators
Validators provide contextual error messages on input widgets. Each validator validates a particular shape of data — string, number, boolean, array — and supports a set of rules tailored to that shape. You can also create custom validators for application-specific rules.
A validator is just a validator object on an input widget, with the rules for that input’s value shape plus an optional messages block. The shape depends on how much the widget already knows about the value:
- DX, typed-value inputs —
textInput,numberInput,booleanInput,password,currency,textarea,markdown,checkbox,calendar,dateInput,datePicker,rangeCalendar,rangeDateInput,rangeDatePicker,repeater. The widget pins the value type (atextInputis always a string, anumberInputis always a number, etc.), so the validator’stypeis inferred. Write only the rules:validator: { minLength: 8, ... }. - DX, polymorphic inputs —
dropdown,radiogroup,select,list, and anygui.inputs.custom. The value type depends on the items or component you wire up, so the widget can’t pin it. The validator carries an explicittype:discriminator:validator: { type: 'string', required: true }. - JSON — the validator always carries an explicit
type:discriminator:validator: { type: 'string', minLength: 8, ... }. The JSON path has no type inference; what you write is what the engine validates.
The rest of this page uses DX typed-value inputs in its examples; the JSON tab next to each shows the shape with the discriminator. The rules and message keys are identical in all three cases — only the presence of type: changes.
Adding validators to your inputs
Section titled “Adding validators to your inputs”A validator without a messages block falls back to Zod’s defaults — strings like “Invalid input” or “Required” that rarely tell the user what they should actually do. Try the demo: leave the field empty, type a short value, type a long value, type letters only:
Always pair every rule with a messages entry to give the user contextual, actionable feedback. The example below requires a non-empty password and supplies its own message:
import { gui } from '@golemui/gui-shared';
gui.inputs.textInput('user.password', { validator: { required: true, messages: { required: 'Please enter your password', }, },});{ "kind": "input", "type": "textinput", "path": "user.password", "validator": { "type": "string", "required": true, "messages": { "required": "Please enter your password" } }}Validators stack — combine multiple rules and you’ll see one message per failing rule. The next example checks the password is between 8 and 20 characters long, and that it contains both letters and numbers, with a tailored message for each rule:
gui.inputs.textInput('user.password', { validator: { required: true, minLength: 8, maxLength: 20, pattern: '^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]+$', messages: { required: 'Please enter your password', minLength: 'Password must be at least 8 characters', maxLength: 'Password cannot exceed 20 characters', pattern: 'Password must contain both letters and numbers', invalid: 'Password must be plain text', }, },});{ "kind": "input", "type": "textinput", "path": "user.password", "validator": { "type": "string", "required": true, "minLength": 8, "maxLength": 20, "pattern": "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]+$", "messages": { "required": "Please enter your password", "minLength": "Password must be at least 8 characters", "maxLength": "Password cannot exceed 20 characters", "pattern": "Password must contain both letters and numbers", "invalid": "Password must be plain text" } }}Translating messages
Section titled “Translating messages”A messages entry can be a plain string (as above) or a Localizable shape — { key, default } — that the active translator resolves at render time. Pair this with the i18n feature and your validator messages localize alongside the rest of the form.
gui.inputs.textInput('user.password', { validator: { required: true, messages: { required: { key: 'errors.password.required', default: 'Please enter your password', }, }, },});{ "kind": "input", "type": "textinput", "path": "user.password", "validator": { "type": "string", "required": true, "messages": { "required": { "key": "errors.password.required", "default": "Please enter your password" } } }}A more complete example combining several rule types — switch the language dropdown to see the same messages in English, Spanish, and French:
Validator types reference
Section titled “Validator types reference”Each validator type supports a specific set of rules and message keys.
String validator
Section titled “String validator”| Property | Message key | Description |
|---|---|---|
| (type check) | invalid | Value is not a string |
required | required | Empty string when required: true |
minLength | minLength | String length is below the minimum |
maxLength | maxLength | String length exceeds the maximum |
pattern | pattern | String does not match the regex pattern |
format | format | String does not match the format (email, url, uuid, hostname, ipv4, ipv6, date, time, date-time, duration) |
enum | enum | Value is not one of the allowed values |
const | const | Value does not match the exact value |
Number validator
Section titled “Number validator”| Property | Message key | Description |
|---|---|---|
| (type check) | invalid | Value is not a number |
required | required | Empty string when required: true |
minimum | minimum | Value is below the minimum |
maximum | maximum | Value exceeds the maximum |
exclusiveMinimum | exclusiveMinimum | Value is not greater than the exclusive minimum |
exclusiveMaximum | exclusiveMaximum | Value is not less than the exclusive maximum |
multipleOf | multipleOf | Value is not a multiple of the specified number |
enum | enum | Value is not one of the allowed values |
const | const | Value does not match the exact value |
Boolean validator
Section titled “Boolean validator”| Property | Message key | Description |
|---|---|---|
| (type check) | invalid | Value is not a boolean |
required | required | Empty string when required: true |
const | const | Value does not match the expected boolean value |
Array validator
Section titled “Array validator”| Property | Message key | Description |
|---|---|---|
| (type check) | invalid | Value is not an array |
required | required | Array is empty when required: true |
minItems | minItems | Array has fewer items than the minimum |
maxItems | maxItems | Array has more items than the maximum |
Configuring when validation triggers
Section titled “Configuring when validation triggers”The validateOn setting controls when field validation runs.
type ValidateOn = | 'eager' | 'change' | 'blur' | 'submit' | ('change' | 'blur' | 'submit')[];When not set, the default behaviour is 'eager'.
| Mode | Triggers when |
|---|---|
'change' | The user changes the field value |
'blur' | The user leaves the field |
'submit' | A submit event is emitted (all fields are touched first) |
'eager' | Any of the above happens (default) |
Where validateOn lives on the form component depends on the path:
validateOn goes inside formConfig.
import { gui } from '@golemui/gui-shared';import { GuiForm } from '@golemui/gui-react';
const config = { formDef: [ gui.inputs.textInput('username', { validator: { required: true, minLength: 2, messages: { required: 'Please enter a username', minLength: 'Username must be at least 2 characters', }, }, }), gui.actions.button({ label: 'Create User', actionType: 'submit' }), ], formConfig: { validateOn: 'change' as const },};
export function MyForm() { return <GuiForm config={config} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import type { ValidateOn } from '@golemui/core';import { gui } from '@golemui/gui-shared';import { FormComponent } from '@golemui/gui-angular';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config"></gui-form>`,})export class MyForm { protected config = { formDef: [ gui.inputs.textInput('username', { validator: { required: true, minLength: 2, messages: { required: 'Please enter a username', minLength: 'Username must be at least 2 characters', }, }, }), gui.actions.button({ label: 'Create User', actionType: 'submit' }), ], formConfig: { validateOn: 'change' as ValidateOn }, };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { ValidateOn } from '@golemui/core';import { gui } from '@golemui/gui-shared';import '@golemui/gui-lit';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef: [ gui.inputs.textInput('username', { validator: { required: true, minLength: 2, messages: { required: 'Please enter a username', minLength: 'Username must be at least 2 characters', }, }, }), gui.actions.button({ label: 'Create User', actionType: 'submit' }), ], formConfig: { validateOn: 'change' as ValidateOn }, };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { gui } from '@golemui/gui-shared';import { GuiForm } from '@golemui/gui-vue';
const config = { formDef: [ gui.inputs.textInput('username', { validator: { required: true, minLength: 2, messages: { required: 'Please enter a username', minLength: 'Username must be at least 2 characters', }, }, }), gui.actions.button({ label: 'Create User', actionType: 'submit' }), ], formConfig: { validateOn: 'change' as const },};</script>
<template> <GuiForm :config="config" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import { gui } from '@golemui/gui-shared';
const form = document.getElementById('app-form');form.config = { formDef: [ gui.inputs.textInput('username', { validator: { required: true, minLength: 2, messages: { required: 'Please enter a username', minLength: 'Username must be at least 2 characters', }, }, }), gui.actions.button({ label: 'Create User', actionType: 'submit' }), ], formConfig: { validateOn: 'change' },};validateOn goes in the config object.
import { GuiForm } from '@golemui/gui-react';import formDef from './my-form.json';
const config = { formDef, validateOn: 'change' as const };
export function MyForm() { return <GuiForm config={config} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { FormComponent } from '@golemui/gui-angular';import formDef from './my-form.json';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config"></gui-form>`,})export class MyForm { protected config = { formDef, validateOn: 'change' };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-lit';import formDef from './my-form.json';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef, validateOn: 'change' };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { GuiForm } from '@golemui/gui-vue';import formDef from './my-form.json';
const config = { formDef, validateOn: 'change' as const };</script>
<template> <GuiForm :config="config" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import formDef from './my-form.json';
const form = document.getElementById('app-form');form.config = { formDef, validateOn: 'change' };See also
Section titled “See also”- Extending GolemUI / Validators — define your own validator schemas for application-specific rules.
- Form Definition API / Reference /
gui.inputs— every input widget’s typedvalidatorprop.