FormWorkflow
A form orchestration layer for the failure paths teams repeatedly rebuild: validation, server conflicts, focused summaries, duplicate-submit protection, draft recovery, unsaved changes, and step navigation.
Installation
The CLI copies editable source and installs React Hook Form, its Zod resolver, and Zod. Elsecase is a shadcn registry, not a runtime package.
pnpm dlx shadcn@latest add https://elsecase.vercel.app/r/form-workflow.jsonBasic usage
import { z } from "zod"
import { useFormContext } from "react-hook-form"
import { FormWorkflow } from "@/components/form-workflow"
const schema = z.object({
name: z.string().min(2),
website: z.string().url(),
})
<FormWorkflow
schema={schema}
defaultValues={{ name: "", website: "" }}
onSubmit={saveOrganization}
warnOnUnsavedChanges
>
<OrganizationFields />
</FormWorkflow>API reference
| Prop | Purpose | Default |
|---|---|---|
schema | A Zod object schema used before submit or autosave. | Required |
defaultValues | Initial form values and reset target. | Required |
onSubmit | Async function returning a FormSubmissionResult. | Required |
children | Fields registered through useFormContext. | Required |
mode | Single-page or multi-step rendering. | "single" |
autosave | Debounced onSave callback, delay, and enabled flag. | None |
warnOnUnsavedChanges | Warn for page unload and clicked links while dirty. | false |
successBehavior | Message, reset, or preserve behavior after success. | "message" |
Submission result
type FormSubmissionResult = {
success: boolean
message?: string
fieldErrors?: Record<string, string>
formError?: string
}Field errors are mapped back into React Hook Form. A form-level message and every field error appear in a focusable summary while the entered values stay intact.
Multi-step forms
Set mode="multi-step" and wrap direct children inFormWorkflowStep. Each step declares the field paths it must validate before continuing; the final step owns submission.
Autosave and recovery
Autosave runs only after the current values pass the schema. The built-in status announces saving, success, and failure, and a failed draft exposes an explicit retry action.
Unsaved changes
The warning covers browser unloads and ordinary same-window anchor clicks. Router-specific programmatic navigation should be blocked by your routing layer because browsers do not expose one universal interception API.
Accessibility behavior
Invalid submissions move focus to a summary, summary actions focus the corresponding field, async statuses use live regions, steps use fieldsets and legends, and built-in actions meet a 44px minimum target.
Responsive behavior
Workflow actions stack at narrow widths, long summaries wrap, and the component imposes no fixed width. The application retains ownership of field layout and can collapse grids around the workflow.
Testing example
fireEvent.click(screen.getByRole("button", { name: "Save changes" }))
const summary = await screen.findByRole("alert")
await waitFor(() => expect(summary).toHaveFocus())
expect(onSubmit).not.toHaveBeenCalled()Customization
FormWorkflow owns orchestration and its action/status surfaces. Your field components own labels, descriptions, inputs, and inline errors throughuseFormContext, so the copied source fits an existing design system.
Dependencies
React, react-hook-form@^7.85.0,@hookform/resolvers@^5.8.0, and zod@^4.4.3.
Known limitations
Version 0.1 supports Zod object schemas because registered form fields must have named paths. File upload transport, cross-tab draft merging, programmatic router guards, and remote step persistence remain application concerns.
Source
Review the implementation and tests on GitHub:
github.com/heysinghaaa/Elsecase/tree/main/registry/form-workflow