Painless forms for Vue.js with comprehensive validation, composition API, and component-based approaches.
—
Form-level composables for managing complete form state, validation, submission handling, and providing context to child components. These composables are the foundation for building complex forms with VeeValidate.
Creates a form context with comprehensive validation management and state tracking.
/**
* Creates a form context with validation management
* @param options - Optional form configuration
* @returns Form context object with state and methods
*/
function useForm<TValues extends GenericObject = GenericObject, TOutput extends GenericObject = TValues>(
options?: FormOptions<TValues>
): FormContext<TValues, TOutput>;
interface FormOptions<TValues extends GenericObject> {
validationSchema?: MaybeRef<RawFormSchema<TValues> | TypedSchema<TValues, TOutput> | YupSchema<TValues> | undefined>;
initialValues?: PartialDeep<TValues>;
initialErrors?: Partial<FlattenAndSetPathsType<TValues, string | string[] | undefined>>;
initialTouched?: Partial<FlattenAndSetPathsType<TValues, boolean>>;
validateOnMount?: boolean;
keepValuesOnUnmount?: MaybeRef<boolean>;
name?: string;
}
interface FormContext<TValues extends GenericObject = GenericObject, TOutput extends GenericObject = TValues> {
// Form state
values: TValues;
errors: ComputedRef<FormErrors<TValues>>;
meta: ComputedRef<FormMeta<TValues>>;
isSubmitting: Ref<boolean>;
isValidating: Ref<boolean>;
submitCount: Ref<number>;
controlledValues: Ref<TValues>;
// Form methods
handleSubmit: HandleSubmitFactory<TValues, TOutput> & { withControlled: HandleSubmitFactory<TValues, TOutput> };
validate(opts?: Partial<ValidationOptions>): Promise<FormValidationResult<TValues, TOutput>>;
validateField<TPath extends Path<TValues>>(field: TPath, opts?: Partial<ValidationOptions>): Promise<ValidationResult<TOutput[TPath]>>;
// State mutations
setFieldValue<T extends Path<TValues>>(field: T, value: PathValue<TValues, T>, shouldValidate?: boolean): void;
setFieldError(field: Path<TValues>, message: string | string[] | undefined): void;
setErrors(fields: Partial<FlattenAndSetPathsType<TValues, string | string[] | undefined>>): void;
setValues(fields: PartialDeep<TValues>, shouldValidate?: boolean): void;
setFieldTouched(field: Path<TValues>, isTouched: boolean): void;
setTouched(fields: Partial<Record<Path<TValues>, boolean>> | boolean): void;
resetForm(state?: Partial<FormState<TValues>>, opts?: Partial<ResetFormOpts>): void;
resetField(field: Path<TValues>, state?: Partial<FieldState>): void;
// Form context methods
handleReset(): void;
submitForm(e?: unknown): Promise<void>;
createPathState<TPath extends Path<TValues>>(path: MaybeRef<TPath>, config?: Partial<PathStateConfig<TOutput[TPath]>>): PathState<PathValue<TValues, TPath>>;
defineField<TPath extends Path<TValues>, TValue = PathValue<TValues, TPath>, TExtras extends GenericObject = GenericObject>(
path: MaybeRefOrGetter<TPath>,
config?: Partial<InputBindsConfig<TValue, TExtras>> | LazyInputBindsConfig<TValue, TExtras>
): [Ref<TValue>, Ref<BaseFieldProps & TExtras>];
}
type HandleSubmitFactory<TValues extends GenericObject, TOutput extends GenericObject = TValues> = <TReturn = unknown>(
cb: SubmissionHandler<TValues, TOutput, TReturn>,
onSubmitValidationErrorCb?: InvalidSubmissionHandler<TValues, TOutput>
) => (e?: Event) => Promise<TReturn | undefined>;Usage Examples:
import { useForm } from "vee-validate";
import * as yup from "yup";
// Basic form setup
const { handleSubmit, errors, meta, values } = useForm();
// Form with validation schema
const schema = yup.object({
name: yup.string().required("Name is required"),
email: yup.string().email("Email is invalid").required("Email is required"),
age: yup.number().min(18, "Must be at least 18").required("Age is required")
});
const {
handleSubmit,
errors,
meta,
values,
setFieldValue,
resetForm
} = useForm({
validationSchema: schema,
initialValues: {
name: "",
email: "",
age: 0
}
});
// Handle form submission
const onSubmit = handleSubmit((values) => {
console.log("Form submitted:", values);
// Submit to API
}, (ctx) => {
console.log("Form validation failed:", ctx.errors);
});
// Programmatic field manipulation
const updateUser = () => {
setFieldValue("name", "John Doe");
setFieldValue("email", "john@example.com");
};
// Reset form to initial state
const handleReset = () => {
resetForm();
};
// Advanced form with custom validation
const {
handleSubmit,
validate,
validateField,
setErrors
} = useForm({
validationSchema: {
username: (value) => {
if (!value) return "Username is required";
if (value.length < 3) return "Username too short";
return true;
},
password: (value) => {
if (!value) return "Password is required";
if (value.length < 8) return "Password must be at least 8 characters";
return true;
},
confirmPassword: (value, { form }) => {
if (!value) return "Please confirm password";
if (value !== form.password) return "Passwords do not match";
return true;
}
}
});
// Manual validation
const validateForm = async () => {
const result = await validate();
if (!result.valid) {
console.log("Form is invalid:", result.errors);
}
};
// Validate specific field
const validateUsername = async () => {
const result = await validateField("username");
if (!result.valid) {
console.log("Username is invalid:", result.errors);
}
};Accesses the current form context via dependency injection from a parent Form component or useForm call.
/**
* Accesses the current form context via dependency injection
* @returns Form context from nearest parent Form component or useForm call
* @throws Error if no form context is found
*/
function useFormContext<TValues extends GenericObject = GenericObject, TOutput extends GenericObject = TValues>(): FormContext<TValues, TOutput>;Usage Examples:
import { useFormContext } from "vee-validate";
// In a child component that needs access to form context
const ChildComponent = {
setup() {
const form = useFormContext();
// Access form state
const isFormValid = computed(() => form.meta.value.valid);
const formErrors = computed(() => form.errors.value);
// Use form methods
const clearForm = () => {
form.resetForm();
};
const submitForm = async () => {
const result = await form.validate();
if (result.valid) {
// Handle submission
}
};
return {
isFormValid,
formErrors,
clearForm,
submitForm
};
}
};Provides aggregate metadata about the form's validation state.
interface FormMeta<TValues extends GenericObject> {
touched: boolean; // True if any field has been touched
dirty: boolean; // True if any field value differs from initial
valid: boolean; // True if all fields pass validation
pending: boolean; // True if any validation is in progress
initialValues?: Partial<TValues>; // Original form values
}Complete form state structure for reset operations and state management.
interface FormState<TValues> {
values: PartialDeep<TValues>; // Current field values
errors: Partial<Record<Path<TValues>, string | undefined>>; // Field error messages
touched: Partial<Record<Path<TValues>, boolean>>; // Field touched states
submitCount: number; // Number of submission attempts
}Type-safe error mapping for form fields.
type FormErrors<TValues extends GenericObject> = Partial<Record<Path<TValues> | '', string | undefined>>;
type FormErrorBag<TValues extends GenericObject> = Partial<Record<Path<TValues> | '', string[]>>;Type definition for form submission callbacks.
type SubmissionHandler<TInput extends GenericObject = GenericObject, TOutput = TInput, TReturn = unknown> = (
values: TOutput,
ctx: SubmissionContext<TInput>
) => TReturn;
interface SubmissionContext<TInput extends GenericObject = GenericObject> extends FormActions<TInput> {
evt?: Event; // Original submit event
controlledValues: Partial<TInput>; // Current controlled form values
}Type definition for handling invalid form submissions.
type InvalidSubmissionHandler<TInput extends GenericObject = GenericObject, TOutput extends GenericObject = TInput> = (
ctx: InvalidSubmissionContext<TInput, TOutput>
) => void;
interface InvalidSubmissionContext<TInput extends GenericObject = GenericObject, TOutput extends GenericObject = TInput> {
values: TInput; // Current form values
evt?: Event; // Original submit event
errors: Partial<Record<Path<TInput>, string>>; // Current field errors
results: FormValidationResult<TInput, TOutput>['results']; // Detailed validation results
}Submission Examples:
import { useForm } from "vee-validate";
const { handleSubmit } = useForm();
// Basic submission
const onSubmit = handleSubmit((values) => {
console.log("Submitting:", values);
return fetch("/api/submit", {
method: "POST",
body: JSON.stringify(values)
});
});
// Submission with error handling
const onSubmitWithErrorHandling = handleSubmit(
async (values, { setFieldError, setErrors }) => {
try {
const response = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify(values)
});
if (!response.ok) {
const errors = await response.json();
setErrors(errors);
return;
}
console.log("Success!");
} catch (error) {
setFieldError("", "Submission failed");
}
},
({ errors, results }) => {
console.log("Form is invalid:", errors);
console.log("Validation results:", results);
}
);
// Controlled form submission
const onControlledSubmit = handleSubmit.withControlled((values, ctx) => {
// Uses controlled values instead of current form values
console.log("Controlled values:", ctx.controlledValues);
return submitToAPI(ctx.controlledValues);
});Using validation schemas for comprehensive form validation.
import { useForm } from "vee-validate";
import * as yup from "yup";
// Yup schema
const yupSchema = yup.object({
user: yup.object({
name: yup.string().required(),
email: yup.string().email().required(),
profile: yup.object({
bio: yup.string().max(500),
age: yup.number().min(18)
})
})
});
const { handleSubmit, errors } = useForm({
validationSchema: yupSchema,
initialValues: {
user: {
name: "",
email: "",
profile: {
bio: "",
age: 0
}
}
}
});
// Raw schema with function validators
const rawSchema = {
"user.name": (value) => value ? true : "Name is required",
"user.email": [
(value) => value ? true : "Email is required",
(value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || "Email is invalid"
],
"user.profile.bio": (value) => value.length <= 500 || "Bio too long"
};
const { handleSubmit: handleRawSubmit } = useForm({
validationSchema: rawSchema
});Install with Tessl CLI
npx tessl i tessl/npm-vee-validate