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.