Skip to content

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 validationMode in 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 ​

ModeValidates on changeValidates on blurDescription
onChangeYes-Errors appear immediately as you type.
onBlur-YesErrors appear only when you tab out of a field.
onTouchedAfter first blurYesBest UX: errors appear on blur, then update in real-time.
onSubmit--No errors until you click submit.
allYesYesValidates 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>

Released under the MIT License.