
Form Validation
- 441 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
form-validation is a useful-ai-prompts agent skill that implements React Hook Form, Formik, and Vee-Validate patterns with TypeScript for developers building accessible validated signup and checkout forms.
About
form-validation is a skill from aj-geddes/useful-ai-prompts that guides agents to implement comprehensive form validation with client-side rules, server-side synchronization, and real-time error feedback. It ships a TypeScript quick-start with LoginFormData and RegisterFormData interfaces plus a React Hook Form register and handleSubmit skeleton, and five reference guides covering React Hook Form, Formik with Yup, Vue Vee-Validate, custom validator hooks, and server-side validation integration. Developers invoke it for signup and checkout flows, multi-step forms, complex cross-field rules, and accessible error messaging—not for non-form API validation alone. The skill lives in the useful-ai-prompts collection of 260+ skills installable via npx skills add, making it a pattern library agents load when users mention form validation, react-hook-form, or real-time field errors.
- Schema and field-level rules
- Accessible error messaging
- Client-server validation parity
- Async and cross-field checks
- Library-specific patterns (Zod, Yup, etc.)
Form Validation by the numbers
- 441 all-time installs (skills.sh)
- Ranked #645 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill form-validationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 441 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you validate React forms with TypeScript?
Design client- and server-side form validation rules, error messages, accessibility, and UX patterns for signup, checkout, and data-entry flows.
Who is it for?
Frontend developers building signup, checkout, or multi-step forms who need React Hook Form, Formik, or Vee-Validate with TypeScript safety.
Skip if: Skip form-validation when you only need non-UI API request validation without client-side form components or accessibility error UX.
When should I use this skill?
User asks for form validation, react-hook-form setup, Yup schemas, real-time field errors, or server-side validation sync on submit.
What you get
Typed form components, validation schemas, error messages, and server-sync handlers
- typed form components
- validation schemas
- error message patterns
By the numbers
- Includes 5 reference guides for React Hook Form, Formik, Vee-Validate, and server sync
- Parent useful-ai-prompts repository contains 260+ agent skills
- Quick-start defines LoginFormData and RegisterFormData TypeScript interfaces
Files
Form Validation
Table of Contents
Overview
Implement comprehensive form validation including client-side validation, server-side synchronization, and real-time error feedback with TypeScript type safety.
When to Use
- User input validation
- Form submission handling
- Real-time error feedback
- Complex validation rules
- Multi-step forms
Quick Start
Minimal working example:
// types/form.ts
export interface LoginFormData {
email: string;
password: string;
rememberMe: boolean;
}
export interface RegisterFormData {
email: string;
password: string;
confirmPassword: string;
name: string;
terms: boolean;
}
// components/LoginForm.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
import { LoginFormData } from '../types/form';
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export const LoginForm: React.FC = () => {
const {
register,
handleSubmit,
// ... (see reference guides for full implementation)Reference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| React Hook Form with TypeScript | React Hook Form with TypeScript |
| Formik with Yup Validation | Formik with Yup Validation |
| Vue Vee-Validate | Vue Vee-Validate |
| Custom Validator Hook | Custom Validator Hook |
| Server-Side Validation Integration | Server-Side Validation Integration |
Best Practices
✅ DO
- Follow established patterns and conventions
- Write clean, maintainable code
- Add appropriate documentation
- Test thoroughly before deploying
❌ DON'T
- Skip testing or validation
- Ignore error handling
- Hard-code configuration values
Custom Validator Hook
Custom Validator Hook
// hooks/useFieldValidator.ts
import { useState, useCallback } from "react";
export interface ValidationRule {
validate: (value: any) => boolean | string;
message: string;
}
export interface FieldError {
isValid: boolean;
message: string | null;
}
export const useFieldValidator = (rules: ValidationRule[] = []) => {
const [error, setError] = useState<FieldError>({
isValid: true,
message: null,
});
const validate = useCallback(
(value: any) => {
for (const rule of rules) {
const result = rule.validate(value);
if (result !== true) {
setError({
isValid: false,
message: typeof result === "string" ? result : rule.message,
});
return false;
}
}
setError({
isValid: true,
message: null,
});
return true;
},
[rules],
);
const clearError = useCallback(() => {
setError({
isValid: true,
message: null,
});
}, []);
return { error, validate, clearError };
};
// Usage
const { error: emailError, validate: validateEmail } = useFieldValidator([
{
validate: (v) => v.length > 0,
message: "Email is required",
},
{
validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
message: "Invalid email format",
},
]);Formik with Yup Validation
Formik with Yup Validation
// validationSchema.ts
import * as Yup from 'yup';
export const registerValidationSchema = Yup.object().shape({
email: Yup.string()
.email('Invalid email')
.required('Email is required'),
password: Yup.string()
.min(8, 'Password must be at least 8 characters')
.matches(/[A-Z]/, 'Must contain uppercase letter')
.matches(/[0-9]/, 'Must contain number')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm password is required'),
name: Yup.string()
.min(2, 'Name too short')
.required('Name is required'),
terms: Yup.boolean()
.oneOf([true], 'You must accept terms')
.required()
});
// components/RegisterForm.tsx
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { registerValidationSchema } from '../validationSchema';
import { RegisterFormData } from '../types/form';
export const RegisterForm: React.FC = () => {
const initialValues: RegisterFormData = {
email: '',
password: '',
confirmPassword: '',
name: '',
terms: false
};
const handleSubmit = async (
values: RegisterFormData,
{ setSubmitting, setFieldError }: any
) => {
try {
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(values)
});
if (!response.ok) {
const error = await response.json();
if (error.emailExists) {
setFieldError('email', 'Email already registered');
}
throw new Error(error.message);
}
} catch (error) {
console.error(error);
} finally {
setSubmitting(false);
}
};
return (
<Formik
initialValues={initialValues}
validationSchema={registerValidationSchema}
onSubmit={handleSubmit}
>
{({ isSubmitting, isValid }) => (
<Form>
<div>
<label htmlFor="name">Name</label>
<Field name="name" type="text" />
<ErrorMessage name="name" component="span" className="error" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" type="email" />
<ErrorMessage name="email" component="span" className="error" />
</div>
<div>
<label htmlFor="password">Password</label>
<Field name="password" type="password" />
<ErrorMessage name="password" component="span" className="error" />
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password</label>
<Field name="confirmPassword" type="password" />
<ErrorMessage name="confirmPassword" component="span" className="error" />
</div>
<div>
<label>
<Field name="terms" type="checkbox" />
I agree to terms
</label>
<ErrorMessage name="terms" component="span" className="error" />
</div>
<button type="submit" disabled={isSubmitting || !isValid}>
{isSubmitting ? 'Registering...' : 'Register'}
</button>
</Form>
)}
</Formik>
);
};React Hook Form with TypeScript
React Hook Form with TypeScript
// types/form.ts
export interface LoginFormData {
email: string;
password: string;
rememberMe: boolean;
}
export interface RegisterFormData {
email: string;
password: string;
confirmPassword: string;
name: string;
terms: boolean;
}
// components/LoginForm.tsx
import { useForm, SubmitHandler } from 'react-hook-form';
import { LoginFormData } from '../types/form';
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export const LoginForm: React.FC = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
watch
} = useForm<LoginFormData>({
defaultValues: {
email: '',
password: '',
rememberMe: false
}
});
const onSubmit: SubmitHandler<LoginFormData> = async (data) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(data)
});
if (!response.ok) throw new Error('Login failed');
// Handle success
} catch (error) {
console.error(error);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Email</label>
<input
type="email"
{...register('email', {
required: 'Email is required',
pattern: {
value: emailRegex,
message: 'Invalid email format'
}
})}
/>
{errors.email && <span className="error">{errors.email.message}</span>}
</div>
<div>
<label>Password</label>
<input
type="password"
{...register('password', {
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters'
}
})}
/>
{errors.password && <span className="error">{errors.password.message}</span>}
</div>
<div>
<label>
<input type="checkbox" {...register('rememberMe')} />
Remember me
</label>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</button>
</form>
);
};
// Custom validator
const usePasswordStrength = () => {
return (password: string): boolean | string => {
if (password.length < 8) return 'At least 8 characters';
if (!/[A-Z]/.test(password)) return 'At least one uppercase letter';
if (!/[0-9]/.test(password)) return 'At least one number';
return true;
};
};Server-Side Validation Integration
Server-Side Validation Integration
// Async server validation
const useAsyncValidation = () => {
const validateEmail = async (email: string) => {
const response = await fetch(`/api/validate/email?email=${email}`);
const { available } = await response.json();
return available ? true : "Email already registered";
};
const validateUsername = async (username: string) => {
const response = await fetch(`/api/validate/username?username=${username}`);
const { available } = await response.json();
return available ? true : "Username taken";
};
return { validateEmail, validateUsername };
};
// React Hook Form with async validation
const { validateEmail } = useAsyncValidation();
register("email", {
required: "Email required",
validate: async (value) => {
return await validateEmail(value);
},
});Vue Vee-Validate
Vue Vee-Validate
// validationRules.ts
import { defineRule } from 'vee-validate';
import { email, required, min, confirmed } from '@vee-validate/rules';
defineRule('required', required);
defineRule('email', email);
defineRule('min', min);
defineRule('confirmed', confirmed);
defineRule('password-strength', (value: string) => {
if (value.length < 8) return 'Password must be at least 8 characters';
if (!/[A-Z]/.test(value)) return 'Must contain uppercase letter';
if (!/[0-9]/.test(value)) return 'Must contain number';
return true;
});
// components/LoginForm.vue
<template>
<Form @submit="onSubmit" :validation-schema="validationSchema">
<div class="form-group">
<label for="email">Email</label>
<Field name="email" type="email" as="input" class="form-control" />
<ErrorMessage name="email" class="error" />
</div>
<div class="form-group">
<label for="password">Password</label>
<Field name="password" type="password" as="input" class="form-control" />
<ErrorMessage name="password" class="error" />
</div>
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? 'Logging in...' : 'Login' }}
</button>
</Form>
</template>
<script setup lang="ts">
import { Form, Field, ErrorMessage } from 'vee-validate';
import { object, string } from 'yup';
import { ref } from 'vue';
const isSubmitting = ref(false);
const validationSchema = object({
email: string().email('Invalid email').required('Email is required'),
password: string().required('Password is required')
});
const onSubmit = async (values: any) => {
isSubmitting.value = true;
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(values)
});
if (!response.ok) throw new Error('Login failed');
} catch (error) {
console.error(error);
} finally {
isSubmitting.value = false;
}
};
</script>// Component: [Name]
// TODO: Customize for your framework (React, Vue, Svelte, etc.)
import React from 'react';
interface Props {
// TODO: Define props
}
export function ComponentName({ }: Props) {
// TODO: Add state and effects
return (
<div>
{/* TODO: Add component markup */}
</div>
);
}
Related skills
How it compares
Pick form-validation for UI form libraries and error UX patterns; use schema-only backend validation skills when there is no React or Vue form layer.
FAQ
Which form libraries does form-validation support?
form-validation documents React Hook Form with TypeScript, Formik with Yup, Vue Vee-Validate, custom validator hooks, and server-side validation integration. Reference guides under references/ hold full implementations beyond the quick-start skeleton.
Does form-validation handle server-side validation?
Yes—form-validation includes a server-side validation integration reference guide for synchronizing client rules with backend responses and surfacing API validation errors in the UI with TypeScript-typed form data.