Skip to content

Custom Validation (No Joi)

A login form using only custom validation functions — no external schema library required. Each field has its own validator function that returns an error object when validation fails.

What this example covers:

  • Creating a form with Alpine.Form() using inline validation functions
  • Writing custom validators with config.validations
  • No external dependencies (no Joi, no Yup, etc.)
  • Displaying field errors with getFieldState()

Live Demo

JavaScript

Define validation functions directly in config.validations. Each function receives the field value and returns { message } on failure, or undefined on success.

js
Alpine.data('basicFormNoJoi', () => ({
    result: '',
    showResult: false,
    form: Alpine.Form(
        {},
        {
            config: {
                validations: {
                    email(value) {
                        if (!value) return { message: 'Email is required' };
                        if (!/\S+@\S+\.\S+/.test(value))
                            return { message: 'Please enter a valid email' };
                    },
                    password(value) {
                        if (!value) return { message: 'Password is required' };
                        if (value.length < 6) return { message: 'Must be at least 6 characters' };
                    },
                },
            },
        },
    ),
    submitFunction(data) {
        this.showResult = true;
        this.result = JSON.stringify(Alpine.raw(data), null, 2);
    },
}));

HTML

The template is identical to the Joi example — the HTML doesn't care which validation approach is used.

html
<div x-data="basicFormNoJoi">
    <form @submit.prevent="form.submit(submitFunction)">
        <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="Enter password"
            />
            <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">Save</button>
            <button type="button" @click="form.reset(); showResult = false;">Reset</button>
        </div>
    </form>
    <pre x-show="showResult" x-text="result"></pre>
</div>

Cross-Field Validation

Custom validators receive the full form data as the second argument, enabling cross-field checks:

js
validations: {
    confirmPassword(value, data) {
        if (value !== data.password) return { message: 'Passwords do not match' };
    }
}

Released under the MIT License.