TanStack Form + Standard Schema

Testing TanStack Form field-level validation with @cleverbrush/schema via the Standard Schema v1 interface.

How it works

TanStack Form accepts any Standard Schema v1 compliant validator in its validators prop. Every @cleverbrush/schema builder exposes a ["~standard"] property that implements the spec, so you can pass schemas directly — no adapter needed.

import { useForm } from '@tanstack/react-form';
import { string, number } from '@cleverbrush/schema';

const nameSchema = string()
  .required('Name is required')
  .minLength(2, 'Name must be at least 2 characters');

const ageSchema = number()
  .required('Age is required')
  .min(18, 'Must be at least 18 years old');

function MyForm() {
  const form = useForm({
    defaultValues: { name: '', age: undefined },
    onSubmit: async ({ value }) => console.log(value),
  });

  return (
    <form onSubmit={e => { e.preventDefault(); form.handleSubmit(); }}>
      <form.Field
        name="name"
        validators={{ onChange: nameSchema, onBlur: nameSchema }}
      >
        {field => (
          <>
            <input
              value={field.state.value}
              onChange={e => field.handleChange(e.target.value)}
              onBlur={field.handleBlur}
            />
            {field.state.meta.errors.map(err => (
              <span key={String(err)}>{String(err)}</span>
            ))}
          </>
        )}
      </form.Field>
      <button type="submit">Submit</button>
    </form>
  );
}

Live Demo — Registration Form

Each field uses a @cleverbrush/schema validator passed directly as a Standard Schema. Errors appear on change after the first interaction, and again on blur.

Schema Definitions

The schemas used in the form above — each is a fully Standard Schema v1 compliant object (inspect nameSchema["~standard"] in the console to verify):

import { string, number, boolean, object } from '@cleverbrush/schema';

const nameSchema = string()
  .required('Name is required')
  .minLength(2, 'Name must be at least 2 characters')
  .maxLength(80, 'Name must be at most 80 characters');

const emailSchema = string()
  .required('Email is required')
  .matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Please enter a valid email address');

const ageSchema = number()
  .required('Age is required')
  .min(18, 'Must be at least 18 years old')
  .max(120, 'Must be at most 120');

const bioSchema = string()
  .maxLength(300, 'Bio must be at most 300 characters');

// Standard Schema interface:
// nameSchema['~standard'].version  // => 1
// nameSchema['~standard'].vendor   // => '@cleverbrush/schema'
// nameSchema['~standard'].validate('Hi')
// => { issues: [{ message: 'Name must be at least 2 characters' }] }