Skip to content

Form Validation Examples

Problem

Implement form validation examples in a production-friendly way with @vielzeug/validit while keeping setup and cleanup explicit.

Runnable Example

The snippet below is copy-paste runnable in a TypeScript project with @vielzeug/validit installed.

Registration Schema

ts
import { v, type Infer } from '@vielzeug/validit';

export const RegistrationSchema = v
  .object({
    name: v.string().min(1, 'Name is required'),
    email: v.string().trim().email('Invalid email address'),
    password: v
      .string()
      .min(8, 'Password must be at least 8 characters')
      .refine((value) => /[A-Z]/.test(value), 'Add at least one uppercase letter')
      .refine((value) => /\d/.test(value), 'Add at least one number'),
    confirmPassword: v.string(),
    newsletter: v.boolean().default(false),
  })
  .refine((value) => value.password === value.confirmPassword, 'Passwords must match');

export type Registration = Infer<typeof RegistrationSchema>;

Mapping Errors For UI

ts
const result = RegistrationSchema.safeParse(formData);

if (!result.success) {
  const { fieldErrors, formErrors } = result.error.flatten();
  // fieldErrors => { email: ['Invalid email'], password: ['...'] }
  // formErrors => ['Passwords must match']
}

Optional Profile Fields

ts
const ProfileSchema = v.object({
  displayName: v.string().min(1),
  bio: v.string().max(280).optional(),
  birthday: v.date().optional(),
  website: v.string().url().optional(),
});

Expected Output

  • The example runs without type errors in a standard TypeScript setup.
  • The main flow produces the behavior described in the recipe title.

Common Pitfalls

  • Forgetting cleanup/dispose calls can leak listeners or stale state.
  • Skipping explicit typing can hide integration issues until runtime.
  • Not handling error branches makes examples harder to adapt safely.