Draft-aware, end-to-end typed multi-step form flows for React + tRPC + Zod.
Epoch Flow is a thin, headless workflow orchestrator that composes Zod and tRPC into typed, persistent, multi-step form flows — without replacing either of them.
- Step-based navigation — Linear
next/back/goTowith built-in guards - Per-step Zod validation — Only validate the fields visible on the current step
- Draft persistence — Auto-save to
localStoragewith debounce; restore on return - Typed tRPC mutations — Submit flows directly to your tRPC router with full inference
- Headless by design — Bring your own UI; zero styling opinions
- Framework-agnostic core — Vanilla JS state machine + store; React bindings in a separate package
- Lightweight — ~9 KB total (gzipped) including React bindings
npm install @epochflow/react zod react react-dom
# or
pnpm add @epochflow/react zod react react-dom
@epochflow/coreis included automatically as a dependency of@epochflow/react.
If you plan to submit data via tRPC, also install your tRPC client packages:
npm install @trpc/client @trpc/server| Package | Supported versions |
|---|---|
| React | ^18.0.0 | ^19.0.0 |
| React DOM | ^18.0.0 | ^19.0.0 |
| Zod | ^3.22.0 |
| tRPC Client | ^10.0.0 | ^11.0.0 (optional) |
| tRPC Server | ^10.0.0 | ^11.0.0 (optional) |
React Server Components (RSC): Epoch Flow is currently client-only. It works inside
"use client"boundaries in Next.js App Router.
import { z } from 'zod'
import { createFormFlow } from '@epochflow/core'
import { FormFlowProvider, useFormFlow } from '@epochflow/react'
const LeadSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
})
type LeadData = z.infer<typeof LeadSchema>
const flow = createFormFlow({
schema: LeadSchema,
steps: {
personal: ['firstName', 'lastName'],
contact: ['email'],
},
persist: { key: 'lead-draft', debounceMs: 1000 },
mutation: async (data) => {
const response = await fetch('/api/leads', {
method: 'POST',
body: JSON.stringify(data),
})
return response.json()
},
})
function PersonalStep() {
const { values, errors, setValues, next, validateCurrentStep } =
useFormFlow<LeadData>()
function handleNext() {
if (validateCurrentStep() === null) next()
}
return (
<>
<input
value={values.firstName ?? ''}
onChange={(event) => setValues({ firstName: event.target.value })}
/>
{errors.firstName && <span>{errors.firstName}</span>}
<button onClick={handleNext}>Next</button>
</>
)
}
function App() {
return (
<FormFlowProvider flow={flow}>
<PersonalStep />
</FormFlowProvider>
)
}Epoch Flow provides its own lightweight controlled-state form handling (values / setValues). You can use it alongside React Hook Form if you prefer — for example, using RHF inside individual steps for advanced field-level validation — but React Hook Form is not required.
| Package | Purpose | Install |
|---|---|---|
@epochflow/core |
Framework-agnostic state machine, store, validation, persistence | Included with @epochflow/react |
@epochflow/react |
React provider, hooks (useFormFlow, useStepFields, usePersistedDraft) |
npm i @epochflow/react |
| Tool | What it does | How Epoch Flow differs |
|---|---|---|
| React Hook Form | Performant form validation with uncontrolled inputs | Epoch Flow focuses on multi-step orchestration, persistence, and tRPC submission — not field-level performance. Use both together if you like. |
| Wizzard | Modular headless wizard engine with multiple framework bindings | Epoch Flow is smaller and more opinionated: one factory (createFormFlow), one validation strategy (Zod), and first-class tRPC typing. |
| Rolling your own | useReducer + zodResolver + localStorage |
Epoch Flow saves you from writing and maintaining the same glue code in every project. |
A polished SaaS onboarding wizard lives in apps/demo/. It showcases:
- Step transitions with animations
- Draft persistence (refresh the page, data returns)
- Per-step validation
- Typed tRPC mutation (mocked backend)
- Mobile-responsive layout
cd apps/demo
pnpm dev- Docs site — Run locally with
pnpm docs:dev. When published to GitHub Pages from this repo, the site is served under the/epoch/base path (seedocs/.vitepress/config.mts). - Getting Started — Step-by-step tutorial
- API Reference — Complete API documentation
- Examples — Common patterns and recipes
- Architecture — How it works under the hood
- Contributing — Development workflow
- Changelog — Release history (per-package via Changesets)
- Security — Vulnerability disclosure
- Code of Conduct — Community guidelines
MIT © Epoch Flow Contributors