ctrovalidate
v3.0.2
Published
The lightweight, declarative, and accessible form validation library for modern web apps. Zero-dependency, HTML-first, and ARIA-ready.
Maintainers
Readme
Ctrovalidate
The lightweight, declarative, and accessible form validation library for modern web apps.
Documentation • Live Demo • API Reference • Contributing
🎯 Why Ctrovalidate?
Ctrovalidate is a zero-dependency, TypeScript-native form validation library that bridges the gap between raw DOM power and framework-ready APIs. It embraces a declarative, HTML-first approach, allowing you to define validation rules directly in your markup using data attributes.
✨ Key Features
- 🎨 HTML-First Philosophy - Validation rules live in your markup, not scattered across JavaScript
- ♿ Accessibility by Default - Automatic ARIA management (
aria-invalid,aria-describedby) - ⚡ Async Validation - Built-in support for Promise-based rules with abort controllers
- 🔗 Field Dependencies - Conditional validation based on other field states
- 🎭 Framework Agnostic - Works with React, Vue, Next.js, Alpine.js, HTMX, or vanilla JS
- 📦 Micro-Weight - <5KB gzipped, zero dependencies
- 🔒 TypeScript Native - Full type safety with comprehensive
.d.tsfiles - 🧪 98% Test Coverage - Production-ready reliability
- 🎮 Rich API - 9 public methods for complete control
- 🌐 21 Built-in Rules - Common, format, and numeric validation out of the box
📦 Installation
# npm
npm install ctrovalidate
# yarn
yarn add ctrovalidate
# pnpm
pnpm add ctrovalidate🚀 Quick Start
1. The Markup (HTML-First)
<form id="registrationForm" novalidate>
<div class="form-group">
<label for="email">Email Address</label>
<input
type="email"
name="email"
id="email"
data-ctrovalidate-rules="required|email"
placeholder="[email protected]"
/>
<div class="error-message"></div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
name="password"
id="password"
data-ctrovalidate-rules="required|strongPassword"
/>
<div class="error-message"></div>
</div>
<button type="submit">Register</button>
</form>2. The Implementation
import { Ctrovalidate } from 'ctrovalidate';
// Initialize validator
const validator = new Ctrovalidate(
document.querySelector('#registrationForm'),
{
realTime: true, // Validate on blur/input
errorClass: 'is-invalid', // CSS class for invalid fields
pendingClass: 'is-validating', // CSS class during async validation
}
);
// Validate on submit
form.addEventListener('submit', async (e) => {
e.preventDefault();
const isValid = await validator.validate();
if (isValid) {
// Submit to your API
const formData = new FormData(form);
await fetch('/api/register', {
method: 'POST',
body: formData,
});
}
});📋 Built-in Validation Rules (21 Total)
Common Rules
required- Field must have a valueemail- Valid email formatminLength:n- Minimum character lengthmaxLength:n- Maximum character lengthexactLength:n- Exact character lengthsameAs:fieldName- Must match another field
Format Rules
alpha- Only alphabetic charactersalphaNum- Alphanumeric characters onlyalphaDash- Alphanumeric with dashes/underscoresurl- Valid URL formatphone- Valid phone numbercreditCard- Valid credit card (Luhn algorithm)strongPassword- Strong password requirementsjson- Valid JSON stringipAddress- Valid IPv4 or IPv6 address
Numeric Rules
numeric- Any numeric valueinteger- Integer values onlydecimal- Decimal/float valuesmin:n- Minimum numeric valuemax:n- Maximum numeric valuebetween:min,max- Value within range
🎮 Public API Methods
// Validation
await validator.validate() // Validate entire form
// Field Management
validator.addField(element) // Add field dynamically
validator.removeField(element) // Remove field dynamically
validator.refresh() // Re-discover fields after DOM changes
// State Inspection
validator.isDirty('fieldName') // Check if field was touched
validator.getError('fieldName') // Get current error message
// Lifecycle
validator.reset() // Reset all validation states
validator.destroy() // Clean up validator instance
// Static Methods (Global)
Ctrovalidate.addRule(name, logic, message?) // Add custom rule
Ctrovalidate.addAsyncRule(name, logic, message?) // Add async rule
Ctrovalidate.setCustomMessages(messages) // Override messages🔧 Advanced Features
Async Validation
// Register async rule (e.g., check username availability)
Ctrovalidate.addAsyncRule(
'usernameAvailable',
async (value, params, element, signal) => {
const response = await fetch(`/api/check-username?username=${value}`, {
signal, // Abort if user types again
});
const data = await response.json();
return data.available;
},
'This username is already taken.'
);<input name="username" data-ctrovalidate-rules="required|usernameAvailable" />Conditional Validation (Dependencies)
<!-- Controller field -->
<select name="contact_method">
<option value="email">Email</option>
<option value="phone">Phone</option>
</select>
<!-- Validates only if contact_method = "email" -->
<input
name="email"
data-ctrovalidate-rules="required|email"
data-ctrovalidate-if="contact_method:value:email"
/>
<!-- Validates only if contact_method = "phone" -->
<input
name="phone"
data-ctrovalidate-rules="required|phone"
data-ctrovalidate-if="contact_method:value:phone"
/>Custom Rules
// Add custom synchronous rule
Ctrovalidate.addRule(
'isCompanyEmail',
(value) => value.endsWith('@company.com'),
'Please use your company email address.'
);Custom Messages
// Override default error messages
Ctrovalidate.setCustomMessages({
required: 'This field cannot be empty!',
email: 'Please enter a valid email address.',
minLength: 'Please enter at least {0} characters.',
});Dynamic Forms
// Add field programmatically
const newInput = document.createElement('input');
newInput.name = 'additional_email';
newInput.setAttribute('data-ctrovalidate-rules', 'required|email');
form.appendChild(newInput);
validator.addField(newInput);
// Remove field
validator.removeField(newInput);
newInput.remove();🌐 Framework Integration
Ctrovalidate works seamlessly with all major frameworks:
| Framework | Demo | Documentation | | --------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------- | | Vanilla JS | demo-vanilla-js | Getting Started | | React 18+ | demo-react | React Integration | | Next.js 15+ | demo-nextjs | Next.js Integration | | Vue 3 | demo-vue | Vue Integration | | Alpine.js | demo-alpine | Alpine.js Integration | | HTMX | - | HTMX Best Practices |
🎓 Complete Feature Showcase
Check out our comprehensive demo that demonstrates every single feature:
- ✅ All 21 validation rules in real-world scenarios
- ✅ All 9 API methods with interactive controls
- ✅ Async validation with abort controllers
- ✅ Field dependencies (conditional validation)
- ✅ Custom rules and messages
- ✅ Dynamic field management
- ✅ State inspection panel
- ✅ Production-ready code with detailed comments
📖 Documentation
- 📚 Full Documentation
- 🚀 Getting Started Guide
- 🔌 API Reference
- 🎨 Framework Integrations
- 📋 Complete Feature List
🧪 Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build library
npm run build
# Run documentation site locally
npm run docs:dev
# Lint code
npm run lint
# Format code
npm run format:fix📊 Project Stats
- Bundle Size: <5KB gzipped
- Dependencies: Zero
- Test Coverage: 98.34% statements, 94.96% branches
- TypeScript: Full support with
.d.tsfiles - Module Formats: ESM + UMD
- Browser Support: Modern browsers (ES2020+)
- Node.js: >=18.0.0
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Workflow
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes with tests
- Run
npm run lintandnpm test - Commit using Conventional Commits
- Push and create a Pull Request
📄 License
MIT © Ctrotech
🙏 Acknowledgments
- Built with ❤️ for the industrial web
- Inspired by modern form validation best practices
- Powered by Vite, Vitest, and TypeScript
Made with ❤️ by Ctrotech • Report Bug • Request Feature
