Validation Modes
Control when validation runs using the validationMode config option. Alpine Forms supports five modes that determine when error messages appear — on every keystroke, on blur, after first touch, only on submit, or all at once.
What this example covers:
- Setting
validationModein config - Switching modes at runtime via
config.validationMode - Reading form and field state (
isValid,isDirty,isTouched) - Resetting the form after mode change
Live Demo
How Each Mode Works
| Mode | Validates on change | Validates on blur | Description |
|---|---|---|---|
onChange | Yes | - | Errors appear immediately as you type. |
onBlur | - | Yes | Errors appear only when you tab out of a field. |
onTouched | After first blur | Yes | Best UX: errors appear on blur, then update in real-time. |
onSubmit | - | - | No errors until you click submit. |
all | Yes | Yes | Validates on both events. |
JavaScript
The mode is set via config.validationMode. You can change it at runtime by updating form.config.validationMode and calling form.reset().
js
Alpine.data('validationModesForm', () => ({
result: '',
showResult: false,
currentMode: 'onTouched',
form: Alpine.Form(
{ email: '', password: '' },
{
config: {
validationMode: 'onTouched',
validations: {
email(value) {
if (!value) return { message: 'Email is required' };
if (!/\S+@\S+\.\S+/.test(value)) return { message: 'Invalid email' };
},
password(value) {
if (!value) return { message: 'Password is required' };
if (value.length < 8) return { message: 'At least 8 characters' };
},
},
},
},
),
setMode(mode) {
this.currentMode = mode;
this.form.config.validationMode = mode;
this.form.reset();
this.showResult = false;
},
handleSubmit(data) {
this.showResult = true;
this.result = JSON.stringify(Alpine.raw(data), null, 2);
},
}));HTML
The state bar at the bottom shows live form and field state values so you can observe how each mode behaves.
html
<p style="color: #64748b; font-size: 14px; margin-top: 0">
Switch modes, then try typing, tabbing away, and submitting to see the difference.
</p>
<div x-data="validationModesForm">
<div class="mode-switcher">
<button @click="setMode('onChange')" :class="currentMode === 'onChange' && 'active'">
onChange
</button>
<button @click="setMode('onBlur')" :class="currentMode === 'onBlur' && 'active'">
onBlur
</button>
<button @click="setMode('onTouched')" :class="currentMode === 'onTouched' && 'active'">
onTouched
</button>
<button @click="setMode('onSubmit')" :class="currentMode === 'onSubmit' && 'active'">
onSubmit
</button>
<button @click="setMode('all')" :class="currentMode === 'all' && 'active'">all</button>
</div>
<form @submit.prevent="form.submit(handleSubmit)">
<div class="field-group">
<label>Email</label>
<input x-register:form="form.field('email')" type="email" placeholder="Enter email" />
<span
class="field-error"
x-show="!form.getFieldState('email').isValid"
x-text="form.getFieldState('email').error"
></span>
</div>
<div class="field-group">
<label>Password</label>
<input
x-register:form="form.field('password')"
type="password"
placeholder="Min 8 characters"
/>
<span
class="field-error"
x-show="!form.getFieldState('password').isValid"
x-text="form.getFieldState('password').error"
></span>
</div>
<div class="btn-group">
<button type="submit">Submit</button>
<button type="button" @click="form.reset()">Reset</button>
</div>
</form>
<pre x-show="showResult" x-text="result"></pre>
<div class="state-bar">
<span>Mode: <strong x-text="currentMode"></strong></span>
<span>Valid: <strong x-text="form.getFormState().isValid"></strong></span>
<span>Dirty: <strong x-text="form.getFormState().isDirty"></strong></span>
<span>Email touched: <strong x-text="form.getFieldState('email').isTouched"></strong></span>
<span
>Password touched: <strong x-text="form.getFieldState('password').isTouched"></strong
></span>
</div>
</div>