Painless forms for Vue.js with comprehensive validation, composition API, and component-based approaches.
—
Field-level composables for individual field validation, state management, value binding, and dynamic field arrays. These composables provide fine-grained control over individual form fields and collections of fields.
Creates a managed field with validation, state tracking, and reactive value binding.
/**
* Creates a managed field with validation and state tracking
* @param path - Field path/name, can be reactive
* @param rules - Optional validation rules for the field
* @param options - Optional field configuration
* @returns Field context with value, meta, errors, and methods
*/
function useField<TValue = unknown>(
path: MaybeRefOrGetter<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
options?: Partial<FieldOptions<TValue>>
): FieldContext<TValue>;
interface FieldOptions<TValue> {
initialValue?: MaybeRefOrGetter<TValue>;
validateOnMount?: boolean;
validateOnValueUpdate?: boolean;
validateOnBlur?: boolean;
bails?: boolean;
type?: string;
label?: MaybeRefOrGetter<string | undefined>;
standalone?: boolean;
keepValueOnUnmount?: MaybeRefOrGetter<boolean | undefined>;
syncVModel?: boolean;
checkedValue?: MaybeRefOrGetter<TValue>;
uncheckedValue?: MaybeRefOrGetter<TValue>;
}
interface FieldContext<TValue = unknown> {
// Field state
value: Ref<TValue>; // Reactive field value
meta: FieldMeta<TValue>; // Field metadata
errors: Ref<string[]>; // Field errors array
errorMessage: Ref<string | undefined>; // First error message
// Field methods
resetField(state?: Partial<FieldState<TValue>>): void;
handleReset(): void;
validate(opts?: Partial<ValidationOptions>): Promise<ValidationResult<TValue>>;
handleChange(e: Event | unknown, shouldValidate?: boolean): void;
handleBlur(e?: Event, shouldValidate?: boolean): void;
setState(state: Partial<FieldState<TValue>>): void;
setTouched(isTouched: boolean): void;
setErrors(message: string | string[]): void;
setValue(value: TValue, shouldValidate?: boolean): void;
}
type RuleExpression<TValue> =
| string
| Record<string, unknown>
| GenericValidateFunction<TValue>
| GenericValidateFunction<TValue>[]
| TypedSchema<TValue>;Usage Examples:
import { useField } from "vee-validate";
// Basic field with validation
const { value: email, errorMessage, meta } = useField('email', (value) => {
if (!value) return 'Email is required';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Email is invalid';
return true;
});
// Field with multiple validation rules
const { value: password, errors, validate } = useField('password', [
(value) => value ? true : 'Password is required',
(value) => value.length >= 8 || 'Password must be at least 8 characters',
(value) => /[A-Z]/.test(value) || 'Password must contain uppercase letter',
(value) => /[0-9]/.test(value) || 'Password must contain a number'
]);
// Field with custom configuration
const { value: username, meta, setValue, resetField } = useField('username',
(value) => value.length >= 3 || 'Username too short',
{
initialValue: '',
validateOnMount: true,
label: 'Username',
bails: false // Continue validation even after first error
}
);
// Checkbox field
const { value: isSubscribed, handleChange } = useField('newsletter', undefined, {
type: 'checkbox',
checkedValue: true,
uncheckedValue: false,
initialValue: false
});
// Reactive field path
const fieldPath = ref('user.email');
const { value, errorMessage } = useField(fieldPath, 'required|email');
// Programmatic field manipulation
const updateField = () => {
setValue('new-value');
setTouched(true);
};
const validateField = async () => {
const result = await validate();
if (!result.valid) {
console.log('Field errors:', result.errors);
}
};
// Reset field to initial state
const resetToDefault = () => {
resetField({
value: '',
errors: [],
touched: false
});
};Manages dynamic arrays of form fields with manipulation methods for adding, removing, and reordering items.
/**
* Manages dynamic arrays of form fields
* @param arrayPath - Path to the array field, can be reactive
* @returns Field array context with fields and manipulation methods
*/
function useFieldArray<TValue = unknown>(
arrayPath: MaybeRefOrGetter<string>
): FieldArrayContext<TValue>;
interface FieldArrayContext<TValue = unknown> {
fields: Ref<FieldEntry<TValue>[]>; // Reactive array of field entries
remove(idx: number): void; // Remove item at index
replace(newArray: TValue[]): void; // Replace entire array
update(idx: number, value: TValue): void; // Update item at index
push(value: TValue): void; // Add item to end
swap(indexA: number, indexB: number): void; // Swap two items
insert(idx: number, value: TValue): void; // Insert item at index
prepend(value: TValue): void; // Add item to beginning
move(oldIdx: number, newIdx: number): void; // Move item to new position
}
interface FieldEntry<TValue = unknown> {
value: TValue; // Entry value
key: string | number; // Unique key for tracking
isFirst: boolean; // True if first entry
isLast: boolean; // True if last entry
}Usage Examples:
import { useFieldArray, useField } from "vee-validate";
// Basic field array for a list of strings
const { fields, push, remove } = useFieldArray<string>('tags');
// Add new tag
const addTag = () => {
push('');
};
// Remove tag by index
const removeTag = (index: number) => {
remove(index);
};
// Complex field array for objects
interface User {
name: string;
email: string;
age: number;
}
const {
fields: userFields,
push: addUser,
remove: removeUser,
swap: swapUsers,
move: moveUser
} = useFieldArray<User>('users');
// Add new user
const addNewUser = () => {
addUser({
name: '',
email: '',
age: 0
});
};
// Remove user by index
const removeUserAt = (index: number) => {
removeUser(index);
};
// Swap two users
const swapUserPositions = (indexA: number, indexB: number) => {
swapUsers(indexA, indexB);
};
// Move user to new position
const moveUserToPosition = (fromIndex: number, toIndex: number) => {
moveUser(fromIndex, toIndex);
};
// Render field array in template
// Template usage with individual field validation
const renderUserFields = () => {
return userFields.value.map((entry, index) => {
// Create individual fields for each user property
const { value: name, errorMessage: nameError } = useField(
`users[${index}].name`,
'required'
);
const { value: email, errorMessage: emailError } = useField(
`users[${index}].email`,
'required|email'
);
const { value: age, errorMessage: ageError } = useField(
`users[${index}].age`,
(value) => value >= 18 || 'Must be at least 18'
);
return {
key: entry.key,
index,
fields: {
name: { value: name, error: nameError },
email: { value: email, error: emailError },
age: { value: age, error: ageError }
},
isFirst: entry.isFirst,
isLast: entry.isLast
};
});
};
// Bulk operations
const replaceAllUsers = (newUsers: User[]) => {
replace(newUsers);
};
const updateUser = (index: number, updatedUser: User) => {
update(index, updatedUser);
};
// Insert user at specific position
const insertUserAt = (index: number, user: User) => {
insert(index, user);
};
// Add user to beginning
const prependUser = (user: User) => {
prepend(user);
};Provides metadata about an individual field's validation and interaction state.
interface FieldMeta<TValue> {
touched: boolean; // True if field has been interacted with
dirty: boolean; // True if value differs from initial value
valid: boolean; // True if field passes validation
validated: boolean; // True if field has been validated at least once
required: boolean; // True if field is required by validation rules
pending: boolean; // True if validation is in progress
initialValue?: TValue; // Original field value
}Complete field state structure for reset and setState operations.
interface FieldState<TValue = unknown> {
value: TValue; // Current field value
touched: boolean; // Field interaction state
errors: string[]; // Field error messages
}State Management Examples:
import { useField } from "vee-validate";
const { value, meta, setState, setTouched, setErrors, setValue } = useField('username');
// Check field state
const isFieldReady = computed(() => {
return meta.validated && meta.valid && !meta.pending;
});
const fieldStatus = computed(() => {
if (meta.pending) return 'validating';
if (meta.errors.length > 0) return 'error';
if (meta.valid && meta.touched) return 'success';
return 'default';
});
// Programmatic state updates
const markFieldAsTouched = () => {
setTouched(true);
};
const setCustomError = () => {
setErrors(['This username is already taken']);
};
const updateFieldValue = (newValue: string) => {
setValue(newValue, true); // true = trigger validation
};
const resetFieldState = () => {
setState({
value: '',
touched: false,
errors: []
});
};Dynamic validation rules based on form state or other conditions.
import { useField, useFormContext } from "vee-validate";
const form = useFormContext();
// Conditional validation based on other field values
const { value: confirmPassword, errorMessage } = useField(
'confirmPassword',
computed(() => {
return (value) => {
if (!value) return 'Please confirm password';
const password = form.values.password;
if (value !== password) return 'Passwords do not match';
return true;
};
})
);
// Dynamic validation rules based on field type
const fieldType = ref('email');
const { value: inputValue, errorMessage: inputError } = useField(
'userInput',
computed(() => {
if (fieldType.value === 'email') {
return [(value) => !!value || 'Email required', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'Invalid email'];
} else if (fieldType.value === 'phone') {
return [(value) => !!value || 'Phone required', (value) => /^\d{10}$/.test(value) || 'Invalid phone'];
}
return (value) => !!value || 'This field is required';
})
);Validation that depends on multiple field values.
import { useField, useFormContext } from "vee-validate";
const form = useFormContext();
// Price range validation
const { value: minPrice } = useField('minPrice', (value) => {
if (!value) return 'Minimum price is required';
const maxPrice = form.values.maxPrice;
if (maxPrice && value >= maxPrice) return 'Minimum must be less than maximum';
return true;
});
const { value: maxPrice } = useField('maxPrice', (value) => {
if (!value) return 'Maximum price is required';
const minPrice = form.values.minPrice;
if (minPrice && value <= minPrice) return 'Maximum must be greater than minimum';
return true;
});
// Date range validation
const { value: startDate } = useField('startDate', (value) => {
if (!value) return 'Start date is required';
const endDate = form.values.endDate;
if (endDate && new Date(value) >= new Date(endDate)) {
return 'Start date must be before end date';
}
return true;
});
const { value: endDate } = useField('endDate', (value) => {
if (!value) return 'End date is required';
const startDate = form.values.startDate;
if (startDate && new Date(value) <= new Date(startDate)) {
return 'End date must be after start date';
}
return true;
});Install with Tessl CLI
npx tessl i tessl/npm-vee-validate