Package Exports
- react-form-steps
- react-form-steps/native
- react-form-steps/types
Readme
๐ react-form-steps
A premium, production-ready multi-step form manager for React & React Native. Built on top of react-hook-form and zod, it handles complex data flows, file persistence, draft recovery, and smooth animations โ all with zero configuration.
๐ป Interactive CLI Scaffolding
react-form-steps ships with a powerful interactive CLI that generates complete multi-step wizard forms in seconds. No boilerplate โ just answer a few questions and start building.
๐ฌ Click the terminal preview above to watch the interactive CLI setup demo!
npx react-form-stepsThe CLI walks you through:
| Prompt | Options |
|---|---|
| Platform | Web (React) ยท React Native (Mobile) |
| Language | JavaScript ยท TypeScript |
| Rendering Style | Normal Inline Page ยท Popup / Modal Overlay |
| State Strategy | Normal (React Hook Form) ยท Redux (React Hook Form + Redux Toolkit) |
| Steps | 1โ10 steps |
| Fields per Step | 1โ10 fields (uniform across all steps) |
Generated File Structure
./form-steps/
โโโ FormStepsWizard.tsx # Main wizard wrapper
โโโ Step1.tsx # Step 1 component
โโโ Step2.tsx # Step 2 component
โโโ Step3.tsx # Step 3 component
โโโ form-steps.css # (Web only) Prebuilt styles
โโโ formSlice.ts # (Redux only) Redux Toolkit slice
โโโ index.ts # Barrel export๐ Key Features
- ๐๏ธ Simple Architecture โ Use
<FormSteps>and<Step>components to build forms in minutes. - โ First-Class Validation โ Optional Zod integration for per-step or global validation.
- ๐พ Smart Persistence โ Automatically saves drafts to
localStorage,sessionStorage,AsyncStorage, or your custom database. - ๐ Base64 File Helper โ Serializes
Fileobjects (images/PDFs) into drafts and restores them as real Files on resume. - โจ Native Animations โ Beautiful built-in
slideandfadetransitions. - ๐ Analytics Built-in โ Track user drop-off with
onStepEnterandonStepCompletecallbacks. - ๐ Edit Mode โ Switch between "Create" and "Edit" modes with automatic field diffing.
- ๐จ Fully Customizable โ Render props for banners, sidebars, and complete UI control.
- ๐ฑ Cross-Platform โ First-class React Native support with platform-specific components.
๐ฆ Installation
# Core dependencies
npm install react-form-steps react-hook-form @hookform/resolvers
# Optional: Add Zod for schema validation
npm install zodFor React Native, also install:
npm install @react-native-async-storage/async-storage๐ Quick Start (Web)
1. Define Your Step Components
// steps/PersonalInfo.tsx
import { useFormSteps } from 'react-form-steps';
export function PersonalInfo() {
const { register, formState: { errors }, goBack, goNext } = useFormSteps();
return (
<div>
<h3>Personal Information</h3>
<div>
<label>Full Name</label>
<input
{...register('fullName', { required: 'Name is required' })}
placeholder="John Doe"
/>
{errors.fullName && <span>{errors.fullName.message}</span>}
</div>
<div>
<label>Email Address</label>
<input
type="email"
{...register('email', { required: 'Email is required' })}
placeholder="john@example.com"
/>
{errors.email && <span>{errors.email.message}</span>}
</div>
<div>
<button type="submit">Next Step</button>
</div>
</div>
);
}// steps/AddressInfo.tsx
import { useFormSteps } from 'react-form-steps';
export function AddressInfo() {
const { register, formState: { errors }, goBack } = useFormSteps();
return (
<div>
<h3>Address Details</h3>
<div>
<label>Street Address</label>
<input
{...register('street', { required: 'Street is required' })}
placeholder="123 Main St"
/>
{errors.street && <span>{errors.street.message}</span>}
</div>
<div>
<label>City</label>
<input
{...register('city', { required: 'City is required' })}
placeholder="New York"
/>
{errors.city && <span>{errors.city.message}</span>}
</div>
<div>
<button type="button" onClick={goBack}>Back</button>
<button type="submit">Submit</button>
</div>
</div>
);
}2. Compose the Wizard
// App.tsx
import { FormSteps, Step } from 'react-form-steps';
import { PersonalInfo } from './steps/PersonalInfo';
import { AddressInfo } from './steps/AddressInfo';
function App() {
const handleSubmit = (payload: any, diff: any) => {
console.log('โ
Form submitted:', payload);
// Send to your API
};
return (
<FormSteps
formKey="user-registration"
storageStrategy="localStorage"
transition="slide"
allowJump={true}
onSubmit={handleSubmit}
onStepEnter={(idx) => console.log('Entered step', idx)}
onStepComplete={(idx) => console.log('Completed step', idx)}
>
<Step label="Personal Info">
<PersonalInfo />
</Step>
<Step label="Address">
<AddressInfo />
</Step>
</FormSteps>
);
}
export default App;๐ฑ Quick Start (React Native)
1. Define Your Step Components
// steps/PersonalInfo.tsx
import React from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
import { useFormSteps } from 'react-form-steps';
export function PersonalInfo() {
const { setValue, watch, formState: { errors }, goNext } = useFormSteps();
return (
<View style={styles.container}>
<Text style={styles.title}>Personal Information</Text>
<View style={styles.field}>
<Text style={styles.label}>Full Name</Text>
<TextInput
style={styles.input}
placeholder="John Doe"
value={watch('fullName') || ''}
onChangeText={(val) => setValue('fullName', val, { shouldValidate: true })}
/>
{errors.fullName && (
<Text style={styles.error}>{errors.fullName.message}</Text>
)}
</View>
<View style={styles.field}>
<Text style={styles.label}>Email</Text>
<TextInput
style={styles.input}
placeholder="john@example.com"
keyboardType="email-address"
value={watch('email') || ''}
onChangeText={(val) => setValue('email', val, { shouldValidate: true })}
/>
{errors.email && (
<Text style={styles.error}>{errors.email.message}</Text>
)}
</View>
<TouchableOpacity style={styles.button} onPress={goNext}>
<Text style={styles.buttonText}>Next Step</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
title: { fontSize: 20, fontWeight: '700', marginBottom: 16, color: '#1e293b' },
field: { marginBottom: 16 },
label: { fontSize: 13, fontWeight: '600', color: '#64748b', marginBottom: 6 },
input: { borderWidth: 1, borderColor: '#cbd5e1', borderRadius: 8, padding: 12, fontSize: 14 },
error: { color: '#ef4444', fontSize: 12, marginTop: 4 },
button: { backgroundColor: '#3b82f6', padding: 14, borderRadius: 8, alignItems: 'center', marginTop: 12 },
buttonText: { color: '#fff', fontSize: 14, fontWeight: '600' },
});2. Compose the Wizard
// App.tsx
import React from 'react';
import { SafeAreaView, ScrollView } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { FormSteps, Step } from 'react-form-steps';
import { PersonalInfo } from './steps/PersonalInfo';
import { AddressInfo } from './steps/AddressInfo';
export default function App() {
const handleSubmit = (payload: any) => {
console.log('โ
Form submitted:', payload);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<FormSteps
formKey="native-registration"
asyncStorage={AsyncStorage}
transition="slide"
allowJump={true}
onSubmit={handleSubmit}
>
<Step label="Personal Info">
<PersonalInfo />
</Step>
<Step label="Address">
<AddressInfo />
</Step>
</FormSteps>
</ScrollView>
</SafeAreaView>
);
}๐ ๏ธ Advanced Features
๐พ Persistence & Draft Recovery
The library auto-saves user progress as they navigate between steps. If a user leaves and returns later, a customizable banner asks them to resume or start fresh.
<FormSteps
formKey="checkout-form"
storageStrategy="localStorage" // Web: localStorage or sessionStorage
asyncStorage={AsyncStorage} // React Native: AsyncStorage
draftTTL={86400} // Draft expires after 24 hours (in seconds)
Autofilldata={true} // Skip the banner, auto-resume silently
onDraftFound={(draft) => console.log('Draft found!', draft)}
onSubmit={handleSubmit}
>
{/* steps */}
</FormSteps>โ Zod Schema Validation
Pass a Zod schema to any <Step> for per-step validation. The form will not advance until the schema passes:
import { z } from 'zod';
const contactSchema = z.object({
phone: z.string().min(10, 'Enter a valid phone number'),
address: z.string().min(5, 'Address is too short'),
});
<Step label="Contact Details" schema={contactSchema}>
<ContactForm />
</Step>๐ Automatic File Persistence
Most libraries lose file uploads if the page refreshes. react-form-steps automatically converts File and FileList objects into Base64 strings for storage, and restores them as real File objects when the user resumes.
// In your step component โ just use a file input normally:
<input type="file" {...register('avatar')} />
// The library automatically serializes & restores files from drafts!๐ Edit Mode
Pass defaultValues to switch to edit mode. The library automatically tracks which fields changed:
<FormSteps
formKey="edit-profile"
defaultValues={existingUserData} // Activates edit mode
onSubmit={(payload, changedFields) => {
console.log('Full payload:', payload);
console.log('Only changed fields:', changedFields);
// Send a PATCH request with only the changed fields
}}
>
{/* steps */}
</FormSteps>๐๏ธ Redux Integration
Sync form state with your Redux store in real-time:
import { useDispatch } from 'react-redux';
import { updateFormData } from './formSlice';
function App() {
const dispatch = useDispatch();
return (
<FormSteps
formKey="redux-form"
onDataChange={(data) => dispatch(updateFormData(data))}
onSubmit={handleSubmit}
>
{/* steps */}
</FormSteps>
);
}๐ Analytics & Tracking
Monitor conversion rates and user drop-off:
<FormSteps
onStepEnter={(idx) => analytics.track('Step Viewed', { step: idx })}
onStepComplete={(idx) => analytics.track('Step Completed', { step: idx })}
onSubmit={handleSubmit}
>
{/* steps */}
</FormSteps>โจ Transitions
Add smooth animations between steps:
<FormSteps transition="slide"> {/* Slide from right */}
<FormSteps transition="fade"> {/* Fade in/out */}
<FormSteps transition="none"> {/* Instant (default) */}๐ Step Navigation
Control how users can navigate between steps:
<FormSteps
allowJump={true} // Allow clicking step indicators to jump
unrestrictedNav={true} // Allow jumping even to incomplete steps
onSubmit={handleSubmit}
>
{/* steps */}
</FormSteps>๐ API Reference
<FormSteps /> Props
| Prop | Type | Default | Description |
|---|---|---|---|
formKey |
string |
โ | Unique key for draft storage. |
storageStrategy |
'localStorage' | 'sessionStorage' | 'database' | 'none' |
'none' |
Where to persist drafts (Web). |
asyncStorage |
{ getItem, setItem, removeItem } |
โ | Custom async storage adapter (React Native). |
Autofilldata |
boolean |
false |
Auto-resume drafts without prompting the user. |
draftTTL |
number |
โ | Draft time-to-live in seconds. |
transition |
'slide' | 'fade' | 'none' |
'none' |
Animation between steps. |
allowJump |
boolean |
false |
Allow non-linear step navigation. |
unrestrictedNav |
boolean |
false |
Allow jumping to incomplete steps. |
defaultValues |
any |
โ | Initial data (activates Edit Mode). |
onSubmit |
(payload, diff) => void |
Required | Called on final step submission. |
onAutoSave |
(step, data, merged) => Promise<void> |
โ | Custom auto-save callback (database strategy). |
onClearDraft |
() => void |
โ | Called when draft is cleared (clean up DB). |
onDataChange |
(data) => void |
โ | Sync state with Redux or external stores. |
onDraftFound |
(draft) => void |
โ | Notified when a saved draft is detected. |
onStepEnter |
(index) => void |
โ | Called when a step becomes active. |
onStepComplete |
(index) => void |
โ | Called when a step is completed. |
bannerConfig |
object |
โ | Customize default banner text and styles. |
renderDraftBanner |
(props) => ReactNode |
โ | Render prop for fully custom draft banners. |
<Step /> Props
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
Required | Display name for the step. |
schema |
ZodSchema |
โ | Zod schema for per-step validation. |
useFormSteps() Hook
Returns the full FormStepsContext merged with react-hook-form's useFormContext():
| Value | Type | Description |
|---|---|---|
values / mergedData |
any |
Current merged values of all steps. |
currentStep |
number |
Index of the active step. |
steps |
StepInfo[] |
Array of { index, label, status }. |
isEditMode |
boolean |
true when defaultValues is provided. |
isSubmitting |
boolean |
true during onSubmit execution. |
changedFields |
Record<string, boolean> |
Map of changed fields (edit mode only). |
goNext() |
() => Promise<void> |
Validate current step and move forward. |
goBack() |
() => void |
Move to previous step. |
goToStep(idx) |
(index: number) => void |
Jump to a specific step. |
getAllErrors() |
() => Record<number, any> |
Get validation errors across all steps. |
resumeDraft(draft) |
(draft) => void |
Programmatically resume a draft. |
clearDraft() |
() => void |
Clear saved draft and reset form. |
register |
function |
Register input fields (from React Hook Form). |
watch |
function |
Watch specific fields (from React Hook Form). |
setValue |
function |
Set field values (from React Hook Form). |
handleSubmit |
function |
Form submit handler (from React Hook Form). |
formState |
object |
Full form state including errors, isDirty, etc. |
๐จ Customizing the Draft Banner
Don't like the default banner? Replace it entirely with your own UI:
<FormSteps
formKey="my-form"
storageStrategy="localStorage"
renderDraftBanner={({ draft, resume, startFresh, dismiss }) => (
<div className="my-custom-banner">
<p>๐ We found your previous progress!</p>
<p>Last saved: {new Date(draft.savedAt).toLocaleString()}</p>
<button onClick={resume}>Continue Where I Left Off</button>
<button onClick={startFresh}>Start Over</button>
<button onClick={dismiss}>Dismiss</button>
</div>
)}
onSubmit={handleSubmit}
>
{/* steps */}
</FormSteps>Or customize just the text using bannerConfig:
<FormSteps
bannerConfig={{
title: 'Welcome Back!',
description: 'You have unsaved progress from your last visit.',
resumeLabel: 'Continue',
freshLabel: 'Start Over',
}}
onSubmit={handleSubmit}
>
{/* steps */}
</FormSteps>๐ค Contributing
Contributions are welcome! Please open an issue or submit a pull request.
๐ License
MIT ยฉ CoderKube Technologies