A lightweight, CSS-driven form validation plugin for Alpine.js that provides real-time client-side validation with data attributes.
- 🪶 Lightweight - Minimal overhead, maximum performance
- 🎨 CSS-driven - Style validation states with data attributes
- ⚡ Real-time - Immediate feedback as users type
- 🔧 Flexible - Multiple validation rules per input
- 📱 Accessible - Works with screen readers and keyboard navigation
- 🚫 Zero dependencies - Only requires Alpine.js
<scriptdefersrc="https://unpkg.com/alpinejs-form-validation@latest/dist/validation.min.js"
></script><scriptdefersrc="https://unpkg.com/alpinejs@latest/dist/cdn.min.js"></script>yarn add -D alpinejs-form-validation
npm install -D alpinejs-form-validationimportAlpinefrom'alpinejs'importvalidationfrom'alpinejs-form-validation'Alpine.plugin(validation)Alpine.start()- Add the validation directive to your inputs with
x-validation - Use modifiers to specify validation rules (e.g.,
x-validation.required) - Trigger validation with
$dispatch('validate') - Style validation states using data attributes
| Rule | Example | Description |
|---|---|---|
required | x-validation.required | Field must have a value |
min.X | x-validation.min.18 | Numeric minimum value |
max.X | x-validation.max.65 | Numeric maximum value |
min:length.X | x-validation.min:length.5 | Minimum string length |
max:length.X | x-validation.max:length.100 | Maximum string length |
checked | x-validation.checked | Checkbox must be checked |
You can combine multiple validation rules on a single input:
<inputx-model="age" x-validation.required.min.18.max.65="age" /><textareax-model="message"
x-validation.required.min:length.10.max:length.500="message"
></textarea>The plugin automatically sets these data attributes on validated elements:
data-validation-valid-"true"or"false"data-validation-reason- The specific validation rule that faileddata-validation-dirty-"true"once the user has interacted with the fielddata-validation-status- JSON object with all validation resultsdata-validation-options- JSON object with all validation rules
This example uses Tailwind CSS for styling but that is not required.
<formx-data="{ contactName: '', contactAge: '', contactMessage: '', contactTerms: false }"
class="max-w-xl mx-auto space-y-4"
@submit.prevent="$dispatch('validate')"
><divclass="group"><labelfor="contactName" class="font-medium"> Name </label><inputid="contactName"
type="text"
x-model="contactName"
x-validation.required="contactName"
class="w-full mt-1 rounded data-[validation-valid=true]:border-green-500 data-[validation-valid=false]:border-red-500"
/><smallclass="text-red-600 mt-1 hidden group-has-[[data-validation-valid=false]]:block"
>
Need a name
</small></div><divclass="group"><labelfor="contactAge" class="font-medium"> Age </label><inputid="contactAge"
type="number"
x-model="contactAge"
x-validation.min.18.max.24="contactAge"
class="w-full rounded mt-1 data-[validation-valid=true]:border-green-500 data-[validation-valid=false]:border-red-500"
/><smallclass="text-red-600 mt-1 group-has-[[data-validation-reason=min]]:block hidden"
>
Must be at least 18 years old
</small><smallclass="text-red-600 mt-1 group-has-[[data-validation-reason=max]]:block hidden"
>
Can't be older than 24 years old
</small></div><divclass="group"><labelfor="contactMessage" class="font-medium"> Message </label><textareaid="contactMessage"
x-model="contactMessage"
x-validation.min:length.10.max:length.50="contactMessage"
class="rounded w-full mt-1 data-[validation-valid=true]:border-green-500 data-[validation-valid=false]:border-red-500"
></textarea><smallclass="text-red-600 mt-1 group-has-[[data-validation-reason=minLength]]:block hidden"
>
Must be at least 10 characters
</small><smallclass="text-red-600 mt-1 group-has-[[data-validation-reason=maxLength]]:block hidden"
>
Can't be more than 50 characters
</small></div><divclass="group"><labelfor="contactTerms" class="inline-flex items-center gap-2"><inputid="contactTerms"
type="checkbox"
value="true"
x-model="contactTerms"
x-validation.checked="contactTerms"
class="size-5 rounded data-[validation-valid=false]:border-red-500"
/><spanclass="font-medium">I accept the terms and conditions</span></label><smallclass="text-red-600 mt-1 group-has-[[data-validation-valid=false]]:block hidden"
>
Must accept
</small></div><divclass="flex justify-end gap-4"><buttontype="reset" class="px-5 py-2.5 rounded font-medium text-blue-600">
Reset
</button><buttonclass="px-5 py-2.5 rounded bg-blue-600 font-medium text-white">
Submit
</button></div></form>Dispatch the validate event to trigger validation for all inputs within the
target element:
<!-- Validate entire form --><form@submit.prevent="$dispatch('validate')"></form>Validation automatically runs as users interact with inputs. The plugin tracks whether an input is "dirty" (has been interacted with) to avoid showing errors prematurely.
/* Valid inputs */
[data-validation-valid='true'] {
border-color:#10b981;
}
/* Invalid inputs */
[data-validation-valid='false'] {
border-color:#ef4444;
}
/* Error messages (hidden by default) */
.error-message {
display: none;
color:#ef4444;
}
/* Show error when parent contains invalid input */
.form-group:has([data-validation-valid='false']) .error-message {
display: block;
}Target specific validation failures for tailored messaging:
/* Show specific error for minimum value */
.form-group:has([data-validation-reason='min']) .min-error {
display: block;
}
/* Show specific error for required field */
.form-group:has([data-validation-reason='required']) .required-error {
display: block;
}<divclass="group"><inputx-model="email"
x-validation.required.min:length.5="email"
class="w-full border rounded px-3 py-2 data-[validation-valid=true]:border-green-500 data-[validation-valid=false]:border-red-500"
/><!-- Different error messages for different validation failures --><smallclass="text-red-600 mt-1 hidden group-has-[[data-validation-reason=required]]:block"
>
Email is required
</small><smallclass="text-red-600 mt-1 hidden group-has-[[data-validation-reason=minLength]]:block"
>
Email must be at least 5 characters
</small></div>This plugin uses modern CSS features like :has() selector in the examples. For
broader browser support, consider:
- Using JavaScript to toggle classes instead of CSS
:has() - Providing fallback styles for older browsers
- Using utility CSS frameworks that handle browser compatibility
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This plugin relies primarily on CSS to show or hide error messages. When an
input is validated, data attributes such as data-validation-valid and
data-validation-reason are set on the input element.
Why use data-validation-reason?
The data-validation-reason attribute is useful for inputs with multiple
validation criteria. By targeting specific reasons, you can display different
error messages for each type of validation failure, improving the user
experience.
[data-validation-valid='false'] {
border-color: red;
}
[data-validation-valid='true'] {
border-color: green;
}
.group:has([data-validation-valid='false']) small {
display: block;
}
.groupsmall {
display: none;
}
.group:has([data-validation-reason='min']) .min-error {
display: block;
}Note: The
:has()CSS selector is supported in all modern browsers. For older browser support, consider alternative approaches.