Skip to content

Coderkube-App/react-form-steps

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ 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.

npm version license


πŸ’» 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.

Watch react-form-steps CLI Demo Video
🎬 Click the terminal preview above to watch the interactive CLI setup demo!

npx react-form-steps

The 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 File objects (images/PDFs) into drafts and restores them as real Files on resume.
  • ✨ Native Animations β€” Beautiful built-in slide and fade transitions.
  • πŸ“Š Analytics Built-in β€” Track user drop-off with onStepEnter and onStepComplete callbacks.
  • πŸ”„ 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 zod

For 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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors