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
<script
defer
src="https://unpkg.com/alpinejs-form-validation@latest/dist/validation.min.js"
></script>
<script defer src="https://unpkg.com/alpinejs@latest/dist/cdn.min.js"></script>
yarn add -D alpinejs-form-validation
npm install -D alpinejs-form-validation
import Alpine from 'alpinejs'
import validation from '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:
<input x-model="age" x-validation.required.min.18.max.65="age" />
<textarea
x-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.
<form
x-data="{ contactName: '', contactAge: '', contactMessage: '', contactTerms: false }"
class="max-w-xl mx-auto space-y-4"
@submit.prevent="$dispatch('validate')"
>
<div class="group">
<label for="contactName" class="font-medium"> Name </label>
<input
id="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"
/>
<small
class="text-red-600 mt-1 hidden group-has-[[data-validation-valid=false]]:block"
>
Need a name
</small>
</div>
<div class="group">
<label for="contactAge" class="font-medium"> Age </label>
<input
id="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"
/>
<small
class="text-red-600 mt-1 group-has-[[data-validation-reason=min]]:block hidden"
>
Must be at least 18 years old
</small>
<small
class="text-red-600 mt-1 group-has-[[data-validation-reason=max]]:block hidden"
>
Can't be older than 24 years old
</small>
</div>
<div class="group">
<label for="contactMessage" class="font-medium"> Message </label>
<textarea
id="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>
<small
class="text-red-600 mt-1 group-has-[[data-validation-reason=minLength]]:block hidden"
>
Must be at least 10 characters
</small>
<small
class="text-red-600 mt-1 group-has-[[data-validation-reason=maxLength]]:block hidden"
>
Can't be more than 50 characters
</small>
</div>
<div class="group">
<label for="contactTerms" class="inline-flex items-center gap-2">
<input
id="contactTerms"
type="checkbox"
value="true"
x-model="contactTerms"
x-validation.checked="contactTerms"
class="size-5 rounded data-[validation-valid=false]:border-red-500"
/>
<span class="font-medium">I accept the terms and conditions</span>
</label>
<small
class="text-red-600 mt-1 group-has-[[data-validation-valid=false]]:block hidden"
>
Must accept
</small>
</div>
<div class="flex justify-end gap-4">
<button type="reset" class="px-5 py-2.5 rounded font-medium text-blue-600">
Reset
</button>
<button class="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;
}
<div class="group">
<input
x-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 -->
<small
class="text-red-600 mt-1 hidden group-has-[[data-validation-reason=required]]:block"
>
Email is required
</small>
<small
class="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;
}
.group small {
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.