FormValidation is discontinued — here's the TypeScript successor I built

작성자

카테고리:

← 피드로
DEV Community · Vinicius · 2026-06-19 개발(SW)

If you’ve ever worked on a complex form without a framework, there’s a good chance you’ve used or at least heard of FormValidation. It was the go-to solution for plugin-based, framework-agnostic form validation for years.

Then it went silent. No updates, no responses to issues. Just an archived library slowly rotting against modern JavaScript.

I needed a replacement for a project. I couldn’t find one that matched the flexibility of the plugin model, so I built Validare — a modern TypeScript rewrite of the same architecture, from scratch.

What Validare is

@validare/core is a form validation library with:

  • 51 built-in validators — from notEmpty and email to iban, creditCard, uuid, vin, and more
  • 17 plugins — UI feedback, accessibility, CSS framework integrations, async flows
  • TypeScript-first — full type safety, no @types/* packages needed
  • Zero dependencies — no jQuery, no frameworks, no polyfills
  • ESM + CJS + UMD — works everywhere

The core idea is the same as FormValidation: a small engine that does nothing on its own, extended entirely through plugins. You add exactly what you need.

Quick start

npm install @validare/core

Enter fullscreen mode Exit fullscreen mode

<form id="myForm" novalidate>
  <input type="email" name="email" />
  <div class="vd-plugins-message-container"></div>
  <button type="submit">Submit</button>
</form>

Enter fullscreen mode Exit fullscreen mode

import { validare, Trigger, Message, SubmitButton } from '@validare/core';

const fv = validare(document.getElementById('myForm'), {
  fields: {
    email: {
      validators: {
        notEmpty: { message: 'Email is required' },
        email:    { message: 'Please enter a valid email address' },
      },
    },
  },
  plugins: {
    trigger:      new Trigger({ event: 'blur' }),
    message:      new Message(),
    submitButton: new SubmitButton(),
  },
});

document.getElementById('myForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const result = await fv.validate(); // 'Valid' | 'Invalid' | 'NotValidated'
  if (result === 'Valid') {
    // safe to submit
  }
});

Enter fullscreen mode Exit fullscreen mode

That’s it. The Trigger plugin wires up DOM events, Message renders error messages, and SubmitButton disables the button during validation.

The plugin model

Plugins are where Validare earns its keep. Each one is a focused, composable unit:

Plugin What it does Trigger Validates on DOM events (blur, input, change) Message Renders error messages in the DOM Icon Shows ✓ / ✗ icons next to fields Bootstrap5 Applies is-valid / is-invalid classes Tailwind Applies configurable Tailwind utility classes Aria Adds aria-invalid and aria-describedby automatically Sequence Stops at the first failing validator per field Excluded Skips disabled or hidden fields Declarative Configures validators via data-vd-* HTML attributes PasswordStrength Scores password strength (0–4) CharCounter Live character counter with overflow state Summary Renders a consolidated error list in a container Transformer Normalises field values before validation (trim, strip, etc.)

You compose only what your project needs:

plugins: {
  trigger:   new Trigger({ event: 'input' }),
  message:   new Message(),
  ui:        new Bootstrap5(),
  aria:      new Aria(),
  sequence:  new Sequence(),
  excluded:  new Excluded(),
}

Enter fullscreen mode Exit fullscreen mode

51 validators, organized by category

Core (23): notEmpty, blank, email, creditCard, date, digits, integer, numeric, regexp, uri, identical, different, between, greaterThan, lessThan, stringLength, stringCase, choice, file, callback, promise, remote, ip

Format & Encoding: base64, hex, mac, bic, uuid, color

Financial: iban, vat, cusip, isin, sedol, grid

Publication: ean, isbn, ismn, issn

Device & Vehicle: imei, imo, meid, step, vin

Tax & Business: ein, rtn, siren, siret

Identity & Geographic: id, phone, zipCode

Async validation and remote checks

The remote validator hits an endpoint and expects { valid: boolean }:

username: {
  validators: {
    notEmpty: { message: 'Username is required' },
    remote: {
      url: '/api/check-username',
      method: 'POST',
      message: 'This username is already taken',
    },
  },
},

Enter fullscreen mode Exit fullscreen mode

Or write your own async validator with callback or promise:

validators: {
  callback: {
    message: 'Invalid value',
    callback: async ({ value }) => {
      const res = await fetch(`/api/validate?q=${value}`);
      const { valid } = await res.json();
      return { valid };
    },
  },
},

Enter fullscreen mode Exit fullscreen mode

Writing a custom validator

Every validator is a zero-argument factory:

import type { ValidatorFactory } from '@validare/core';

export const noSpaces: ValidatorFactory = () => ({
  validate({ value }) {
    return { valid: !value.includes(' '), message: 'No spaces allowed' };
  },
});

Enter fullscreen mode Exit fullscreen mode

Use it directly in your config — no registration needed:

import { noSpaces } from './validators/noSpaces';

fields: {
  username: {
    validators: {
      notEmpty: { message: 'Required' },
      noSpaces: noSpaces,
    },
  },
},

Enter fullscreen mode Exit fullscreen mode

Coming from FormValidation?

The architecture is intentionally familiar. The main differences:

  • Package name: @validare/core (not @form-validation/cjs)
  • CSS classes: vd-plugins-* instead of fv-plugins-*
  • HTML attributes (Declarative plugin): data-vd-* instead of data-fv-*
  • Native TypeScript — no extra type packages

A full migration guide is available in MIGRATING.md.

Try it

Feedback, issues, and PRs are very welcome. This is an active project and I’m happy to prioritize validators or plugins based on what people actually need.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다