Skip to main content
Version: 6.x

Validation for Multi-Step Forms

A multi-step form should validate the current step without forgetting the steps already completed. Submission still needs to check the whole form.

Model steps with groups:

import { create, enforce, group, test } from 'vest';

type Step = 'account' | 'profile' | 'billing';

type OnboardingData = {
displayName: string;
email: string;
plan: string;
};

export const onboardingSuite = create<{
fields: keyof OnboardingData;
groups: Step;
}>((data: OnboardingData) => {
group('account', () => {
test('email', 'Email is required', () => {
enforce(data.email).isNotBlank();
});
});

group('profile', () => {
test('displayName', 'Display name is required', () => {
enforce(data.displayName).isNotBlank();
});
});

group('billing', () => {
test('plan', 'Choose a plan', () => {
enforce(data.plan).isNotBlank();
});
});
});

The explicit config types are optional, but they make field and group names discoverable and reject misspelled step names in TypeScript.

Guard the next-step action with a focused, awaited run:

async function canContinue(step: Step, data: OnboardingData) {
const result = await onboardingSuite.focus({ onlyGroup: step }).run(data);

return result.isValidByGroup(step);
}

Earlier step results remain in the suite, so a review screen can show the whole form without repeating every remote check.

Cross-field rules remain ordinary TypeScript. Put password and confirmation tests in the same group, then run that group when either value changes. If a field outside the active group must also rerun, use the dependent-field patterns described in dependent and cross-field validation.

Final submission​

const result = await onboardingSuite.run(data);

if (!result.isValid()) {
navigateToFirstInvalidStep(result);
return;
}

Do not equate “current step has no errors” with “complete workflow is valid.” Use group selectors for step navigation and whole-suite selectors for submission.

onlyGroup excludes ungrouped top-level tests. Put step requirements inside their groups or run deliberate top-level checks separately.

Read more about grouping tests.