A minimal React Router example for testing Conform form validation upgrades.
This is a reduced example of a multi-step form flow using:
- React Router 7 - File-based routing with SSR
- Conform - Form validation library (
@conform-to/react,@conform-to/zod) - Zod 4 - Schema validation
- Base UI - Accessible UI components
- Tailwind CSS 4 - Styling
# Install dependencies
npm install
# Generate React Router types
npm run types
# Start development server
npm run devVisit http://localhost:5173 and click "Start Test Form" to test the multi-step form.
app/
├── routes/
│ ├── home.tsx # Landing page
│ └── flow/
│ ├── $formId.$stepSlug.tsx # Main form step route
│ ├── $formId.checkout.tsx # Checkout/review page
│ └── $formId.finished.tsx # Completion page
├── components/
│ └── flow/
│ ├── form-renderer.tsx # Dynamic form renderer
│ └── progress-indicator.tsx # Step progress bar
├── services/
│ ├── form/
│ │ ├── config.server.ts # Form configurations
│ │ ├── schema.ts # Zod schema builder
│ │ ├── validation.ts # Custom validators
│ │ ├── validation.server.ts # Server-side validation
│ │ ├── constants.ts # Button text constants
│ │ ├── form-options.ts # Select options (US states, etc.)
│ │ └── meta.server.ts # Meta tag generation
│ ├── form-session.server.ts # Session storage (cookies + SQLite database)
│ └── db.server.ts # Prisma database client
├── ui/ # Form UI components
│ ├── input/
│ ├── select/
│ ├── checkbox-group/
│ ├── radio-group/
│ ├── switch/
│ ├── textarea/
│ ├── combobox/
│ ├── masked-input/
│ ├── progress/
│ └── popover/
└── utils/
└── cn.ts # Tailwind class merge utility
To test Conform upgrades:
-
Update the Conform packages in
package.json:{ "@conform-to/react": "NEW_VERSION", "@conform-to/zod": "NEW_VERSION" } -
Run
npm install -
Start the dev server and test the form flow
-
Check for:
- Form submission and validation
- Error message display
- Field value coercion
- Multi-step navigation
- Session data persistence
import { parseSubmission, report } from "@conform-to/react/future";
import { parseWithZod } from "@conform-to/zod/v4";
import { coerceFormValue } from "@conform-to/zod/v4/future";
// Parse submission
const submission = parseSubmission(formData);
// Build schema and coerce values
const baseSchema = buildSchemaFromStep(step);
const schema = coerceFormValue(baseSchema);
// Validate
const result = parseWithZod(formData, { schema });
// Return errors
if (result.status !== "success") {
return { result: report(submission, result) };
}import { useForm } from "@conform-to/react/future";
import { coerceFormValue } from "@conform-to/zod/v4/future";
const { form, fields } = useForm({
lastResult: actionData?.result,
schema: coerceFormValue(baseSchema),
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
defaultValue: existingData,
});The project includes comprehensive Playwright e2e tests for the form flow.
# Install Playwright browsers (first time only)
npx playwright install
# Run all e2e tests
npm run test:e2e
# Run e2e tests with UI
npm run test:e2e:uiThe e2e tests cover:
- Form field interactions (text, checkbox, radio, switch, select, combobox, textarea, masked inputs)
- Form validation (required fields, field-specific validators)
- Multi-step navigation (forward/back)
- Form state persistence across steps
- Accessibility attributes
- Info popovers
- URL parameter preservation during redirects
tests/
└── e2e/
└── form-flow-basic.spec.ts # Comprehensive form flow tests
- Session storage uses cookies + SQLite database (via Prisma). Session IDs are stored in cookies, form data is stored in the database.
- The
testform config has debug mode enabled - All field types are demonstrated in the 3-step test form
This project uses Prisma with SQLite for session storage. To set up the database:
# Run migrations (creates database if it doesn't exist)
npm run db:setupThe database file (dev.db) will be created in the project root.