npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ctrovalidate/web

v4.0.0

Published

web adapter for Ctrovalidate - The lightweight, declarative validation library.

Readme

@ctrovalidate/web

DOM-first validation for vanilla JavaScript and HTML.

@ctrovalidate/web brings the power of Ctrovalidate to traditional web forms using simple data attributes. No framework required—just clean, declarative validation that works with your existing HTML.

npm version License: MIT


✨ Features

  • 🎯 Data attribute driven - Define rules directly in HTML
  • Real-time validation - Blur and input event handling
  • 🎨 Framework agnostic - Works with vanilla JS, jQuery, or any library
  • Accessible - Full ARIA support for screen readers
  • 🎭 Tailwind-friendly - Supports space-separated class lists
  • 🔗 Conditional validation - Field dependencies made easy
  • 💬 Custom messages - Per-field or per-rule message overrides
  • 🌍 i18n ready - Built on @ctrovalidate/core's translation system
  • 🚫 Async support - Handles async rules with abort signals

📦 Installation

npm install @ctrovalidate/web @ctrovalidate/core
pnpm add @ctrovalidate/web @ctrovalidate/core
yarn add @ctrovalidate/web @ctrovalidate/core

🚀 Quick Start

1. Add Data Attributes to Your HTML

<form id="signupForm" novalidate>
  <div>
    <label for="email">Email</label>
    <input
      type="email"
      id="email"
      name="email"
      data-ctrovalidate-rules="required|email"
    />
    <div class="error-message"></div>
  </div>

  <div>
    <label for="password">Password</label>
    <input
      type="password"
      id="password"
      name="password"
      data-ctrovalidate-rules="required|minLength:8|strongPassword"
    />
    <div class="error-message"></div>
  </div>

  <div>
    <label for="age">Age</label>
    <input
      type="number"
      id="age"
      name="age"
      data-ctrovalidate-rules="required|min:18|max:120"
    />
    <div class="error-message"></div>
  </div>

  <button type="submit">Sign Up</button>
</form>

2. Initialize Ctrovalidate

import { Ctrovalidate } from '@ctrovalidate/web';

const form = document.querySelector('#signupForm');

const validator = new Ctrovalidate(form, {
  realTime: true, // Enable real-time validation
  errorClass: 'is-invalid', // Class added to invalid fields
  errorMessageClass: 'error-message', // Selector for error containers
  pendingClass: 'is-validating', // Class during async validation
});

form.addEventListener('submit', async (e) => {
  e.preventDefault();

  const isValid = await validator.validate();

  if (isValid) {
    // Submit form data
    console.log('Form is valid!');
  }
});

3. Add Some CSS

.is-invalid {
  border-color: #dc3545;
}

.error-message {
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.is-validating {
  opacity: 0.6;
  pointer-events: none;
}

📚 Data Attributes

Validation Rules

Define validation rules using the data-ctrovalidate-rules attribute:

<!-- Single rule -->
<input data-ctrovalidate-rules="required" />

<!-- Multiple rules (pipe-separated) -->
<input data-ctrovalidate-rules="required|email" />

<!-- Rules with parameters -->
<input data-ctrovalidate-rules="required|minLength:8|maxLength:255" />

<!-- Complex validation -->
<input data-ctrovalidate-rules="required|between:18,120|integer" />

Custom Messages

Override error messages at the field level:

<!-- Catch-all message for any rule -->
<input
  data-ctrovalidate-rules="required|email"
  data-ctrovalidate-message="Please fix this field!"
/>

<!-- Rule-specific messages -->
<input
  data-ctrovalidate-rules="required|email|minLength:5"
  data-ctrovalidate-required-message="Email is required!"
  data-ctrovalidate-email-message="Invalid email format!"
  data-ctrovalidate-minlength-message="Too short!"
/>

Conditional Validation (Dependencies)

Validate fields only when certain conditions are met:

<!-- Validate only if checkbox is checked -->
<input type="checkbox" name="agreeToTerms" />
<input
  name="signature"
  data-ctrovalidate-if="agreeToTerms:checked"
  data-ctrovalidate-rules="required"
/>

<!-- Validate only if another field has a specific value -->
<select name="country">
  <option value="USA">USA</option>
  <option value="Canada">Canada</option>
</select>
<input
  name="state"
  data-ctrovalidate-if="country:value=USA"
  data-ctrovalidate-rules="required"
/>

<!-- Validate only if another field has any value -->
<input name="email" />
<input
  name="confirmEmail"
  data-ctrovalidate-if="email:present"
  data-ctrovalidate-rules="required|email"
/>

🎯 Available Rules

All rules from @ctrovalidate/core are available:

| Category | Rules | | -------------- | ---------------------------------------------------------- | | Required | required | | Format | email, url, ipAddress, phone, json, creditCard | | String | alpha, alphaNum, alphaDash, alphaSpaces | | Numeric | numeric, integer, decimal, min:n, max:n | | Length | minLength:n, maxLength:n, exactLength:n | | Range | between:min,max | | Comparison | sameAs:value | | Complex | strongPassword |

See @ctrovalidate/core documentation for detailed rule descriptions.


⚙️ Configuration Options

const validator = new Ctrovalidate(form, {
  // Enable/disable real-time validation (default: true)
  realTime: true,

  // Class added to invalid fields (supports space-separated for Tailwind)
  errorClass: 'is-invalid border-red-500',

  // Selector for error message containers (supports space-separated)
  errorMessageClass: 'error-message text-red-500',

  // Class added during async validation
  pendingClass: 'is-validating opacity-50',

  // Programmatic schema (alternative to data attributes)
  schema: {
    email: 'required|email',
    password: 'required|minLength:8',
  },

  // Rule aliases
  aliases: {
    username: 'required|minLength:3|maxLength:20|alphaDash',
  },

  // Logging level (LogLevel.NONE, ERROR, WARN, INFO, DEBUG)
  logLevel: Ctrovalidate.LogLevel.NONE,
});

🎮 Instance Methods

validate()

Validates the entire form and returns a promise:

const isValid = await validator.validate();

if (isValid) {
  console.log('All fields are valid!');
}

reset()

Clears all validation states and error messages:

validator.reset();

refresh()

Re-scans the form for added/removed fields:

// After dynamically adding fields
validator.refresh();

addField(element)

Manually registers a field for validation:

const newField = document.createElement('input');
newField.setAttribute('data-ctrovalidate-rules', 'required');
form.appendChild(newField);

validator.addField(newField);

removeField(element)

Manually unregisters a field:

validator.removeField(element);

getError(fieldName)

Gets the current error message for a field:

const error = validator.getError('email');
console.log(error); // "Please enter a valid email address." or null

isDirty(fieldName)

Checks if a field has been interacted with:

if (validator.isDirty('email')) {
  console.log('User has touched the email field');
}

destroy()

Removes all event listeners and cleans up:

validator.destroy();

🌍 Static Methods (Global Configuration)

Ctrovalidate.addRule(name, logic, message?)

Add a custom synchronous validation rule globally:

Ctrovalidate.addRule(
  'isEven',
  (value) => Number(value) % 2 === 0,
  'Value must be an even number.'
);

Ctrovalidate.addAsyncRule(name, logic, message?)

Add a custom asynchronous validation rule globally:

Ctrovalidate.addAsyncRule(
  'isUniqueEmail',
  async (value, params, element, signal) => {
    const response = await fetch(`/api/check-email?email=${value}`, { signal });
    const { isUnique } = await response.json();
    return isUnique;
  },
  'This email is already registered.'
);

Ctrovalidate.defineAlias(name, rules)

Define a reusable rule combination globally:

Ctrovalidate.defineAlias('password', 'required|minLength:8|strongPassword');
Ctrovalidate.defineAlias(
  'username',
  'required|minLength:3|maxLength:20|alphaDash'
);

Ctrovalidate.setCustomMessages(messages)

Set custom error messages globally:

Ctrovalidate.setCustomMessages({
  required: 'This field cannot be empty!',
  email: 'Please provide a valid email address.',
  minLength: 'Must be at least {0} characters long.',
});

🎨 Styling Examples

Basic CSS

.is-invalid {
  border: 2px solid #dc3545;
  background-color: #fff5f5;
}

.error-message {
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: 0.25rem;
  display: block;
}

.is-validating {
  opacity: 0.6;
  position: relative;
}

.is-validating::after {
  content: '';
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border: 2px solid #ccc;
  border-top-color: #333;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

@keyframes spin {
  to {
    transform: translateY(-50%) rotate(360deg);
  }
}

Tailwind CSS

const validator = new Ctrovalidate(form, {
  errorClass: 'border-red-500 bg-red-50',
  errorMessageClass: 'text-red-500 text-sm mt-1',
  pendingClass: 'opacity-50 pointer-events-none',
});
<div class="mb-4">
  <input
    class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
    data-ctrovalidate-rules="required|email"
  />
  <div class="text-red-500 text-sm mt-1"></div>
</div>

♿ Accessibility

Ctrovalidate automatically handles ARIA attributes for screen reader support:

  • aria-invalid="true" - Added to invalid fields
  • aria-describedby - Links field to error message
  • role="status" - Added to error containers
  • aria-live="polite" - Announces errors to screen readers

Example output:

<input
  name="email"
  aria-invalid="true"
  aria-describedby="ctrovalidate-error-email"
/>
<div
  id="ctrovalidate-error-email"
  class="error-message"
  role="status"
  aria-live="polite"
>
  Please enter a valid email address.
</div>

🔄 Real-Time Validation Behavior

Blur Event

  • Field is validated when user leaves the field
  • Field is marked as "dirty"
  • Error message is displayed if validation fails

Input Event

  • Field is only validated if it's already dirty
  • Provides instant feedback after first blur
  • Prevents annoying errors while typing initially

Dependency Changes

  • Dependent fields are re-validated when controller field changes
  • Example: Re-validate "confirm email" when "email" changes

🎓 Complete Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Ctrovalidate Demo</title>
    <style>
      .is-invalid {
        border-color: #dc3545;
      }
      .error-message {
        color: #dc3545;
        font-size: 0.875rem;
      }
    </style>
  </head>
  <body>
    <form id="registrationForm" novalidate>
      <div>
        <label for="username">Username</label>
        <input
          id="username"
          name="username"
          data-ctrovalidate-rules="required|minLength:3|maxLength:20|alphaDash"
          data-ctrovalidate-required-message="Username is required!"
        />
        <div class="error-message"></div>
      </div>

      <div>
        <label for="email">Email</label>
        <input
          id="email"
          name="email"
          type="email"
          data-ctrovalidate-rules="required|email"
        />
        <div class="error-message"></div>
      </div>

      <div>
        <label for="password">Password</label>
        <input
          id="password"
          name="password"
          type="password"
          data-ctrovalidate-rules="required|minLength:8|strongPassword"
        />
        <div class="error-message"></div>
      </div>

      <div>
        <label for="confirmPassword">Confirm Password</label>
        <input
          id="confirmPassword"
          name="confirmPassword"
          type="password"
          data-ctrovalidate-if="password:present"
          data-ctrovalidate-rules="required"
        />
        <div class="error-message"></div>
      </div>

      <div>
        <label>
          <input type="checkbox" name="agreeToTerms" />
          I agree to the terms and conditions
        </label>
      </div>

      <div>
        <input
          name="signature"
          data-ctrovalidate-if="agreeToTerms:checked"
          data-ctrovalidate-rules="required|minLength:2"
          placeholder="Type your name to agree"
        />
        <div class="error-message"></div>
      </div>

      <button type="submit">Register</button>
    </form>

    <script type="module">
      import { Ctrovalidate } from '@ctrovalidate/web';

      const form = document.querySelector('#registrationForm');

      // Add custom rule
      Ctrovalidate.addRule(
        'noSpaces',
        (value) => !/\\s/.test(value),
        'Spaces are not allowed.'
      );

      const validator = new Ctrovalidate(form, {
        realTime: true,
        errorClass: 'is-invalid',
        errorMessageClass: 'error-message',
      });

      form.addEventListener('submit', async (e) => {
        e.preventDefault();

        const isValid = await validator.validate();

        if (isValid) {
          const formData = new FormData(form);
          console.log('Form data:', Object.fromEntries(formData));
          alert('Registration successful!');
        } else {
          alert('Please fix the errors before submitting.');
        }
      });
    </script>
  </body>
</html>

📚 Full Documentation

For comprehensive guides, all available rules, and advanced usage:

Visit Ctrovalidate Documentation


🤝 Related Packages


📄 License

MIT © Ctrotech