Validation Guide
Validation in @enlolab/forms
Section titled “Validation in @enlolab/forms”@enlolab/forms leverages the Zod schema library and the validation capabilities of react-hook-form to offer concise yet powerful input validation. This guide explains how validation works out-of-the-box and how you can customise it to fit complex scenarios.
TL;DR – Define a
schemafor each field; @enlolab/forms automatically wires it toreact-hook-formand surfaces errors next to the corresponding input. No additional code is needed.
1. Field-level validation
Section titled “1. Field-level validation”The simplest way to add validation is to assign a schema to your field configuration:
import { z } from "zod";
fields: { email: { type: "email", label: "Email", placeholder: "you@example.com", schema: z.string().email("Invalid email address") }}@enlolab/forms extracts those schemas, builds a single Zod object, and passes it to react-hook-form’s resolver. The error message defined in the schema ("Invalid email address") is rendered automatically via <FormMessage />.
2. Required vs optional fields
Section titled “2. Required vs optional fields”Use Zod helpers just as you would outside @enlolab/forms:
schema: z.string().min(1, "This field is required");If you need an optional field:
schema: z.string().optional();3. Step-level validation flow
Section titled “3. Step-level validation flow”Multi-step forms validate only the fields present in the current step. Internally the hook useFormValidation triggers validation for steps[currentStepId].fields. If validation passes, nextStep() is called; otherwise @enlolab/forms:
- Focuses the first invalid field.
- Displays an error toast (
sonner) – configurable in upcoming versions. - Prevents navigation until all errors are resolved.
Skipping empty steps
Section titled “Skipping empty steps”If a step contains no fields the validation helper returns true immediately, enabling you to create purely informational steps.
4. Cross-field validation
Section titled “4. Cross-field validation”Zod’s refine, superRefine, or composing schemas lets you express relationships between fields:
import { z } from "zod";
const passwordSchema = z .string() .min(8, "Password must be at least 8 characters");
fields: { password: { type: "text", label: "Password", schema: passwordSchema }, confirmPassword: { type: "text", label: "Confirm Password", schema: z.string() .refine((val, ctx) => val === ctx.parent.password, { message: "Passwords do not match" }) }}Because the resolver holds the full form values, you can inspect ctx.parent or use z.object composition to compare multiple fields.
5. Runtime validation & custom triggers
Section titled “5. Runtime validation & custom triggers”By default validation runs on onChange and onBlur events (React Hook Form’s mode = "onChange"). To change the strategy wrap the component and override RHF options:
const MyForm = FormFactory(config);
export const Wrapped = () => { return <MyForm methodsOptions={{ mode: "onSubmit" }} />;};Note: Passing
methodsOptionsis on the roadmap – for now forkuseFormStateif you need full control.
6. Custom error messaging UI
Section titled “6. Custom error messaging UI”@enlolab/forms exposes the raw methods object from react-hook-form through callbacks (onError, etc.). You can therefore implement any error UX you like:
onError: (error, values) => { sendErrorToSentry(error, values);};Or override styling via the error_className prop on a per-field basis.
7. Best practices
Section titled “7. Best practices”- Be explicit – Always define a schema; implicit
anyvalues bypass validation. - Reuse schemas – Extract common rules into helpers to avoid duplication.
- Provide actionable messages – Users should understand how to fix errors.
- Validate progressively – Multi-step validation improves UX and performance.
Related guides
Section titled “Related guides”- Getting Started – First steps with @enlolab/forms
- Steps Guide – Configure navigation and branching
- Submit Guide – Learn how data is sent (coming soon)