Multi Step Forms in the Remix Supabase SaaS kit
Building multi-step forms in the Remix Supabase SaaS kit
The Multi-Step Form Component is a powerful and flexible wrapper around React Hook Form, Zod, and Shadcn UI. It provides a simple API to create multi-step forms with ease, perfect for complex registration processes, surveys, or any scenario where you need to break down a long form into manageable steps.
Features
- Easy integration with React Hook Form and Zod for form management and validation
- Built-in step management
- Customizable layout and styling
- Progress tracking with optional Stepper component
- TypeScript support for type-safe form schemas
Usage
Here's a basic example of how to use the Multi-Step Form Component:
import { MultiStepForm, MultiStepFormStep } from '@kit/ui/multi-step-form';import { useForm } from 'react-hook-form';import { zodResolver } from '@hookform/resolvers/zod';import { z } from 'zod';const FormSchema = createStepSchema({ step1: z.object({ /* ... */ }), step2: z.object({ /* ... */ }),});export function MyForm() { const form = useForm({ resolver: zodResolver(FormSchema), // ... }); const onSubmit = (data) => { // Handle form submission }; return ( <MultiStepForm schema={FormSchema} form={form} onSubmit={onSubmit}> <MultiStepFormStep name="step1"> {/* Step 1 fields */} </MultiStepFormStep> <MultiStepFormStep name="step2"> {/* Step 2 fields */} </MultiStepFormStep> </MultiStepForm> );}
Key Components
MultiStepForm
The main wrapper component that manages the form state and step progression.
Props:
schema
: Zod schema for form validationform
: React Hook Form'suseForm
instanceonSubmit
: Function to handle form submissionclassName
: Optional CSS classes
MultiStepFormStep
Represents an individual step in the form.
Props:
name
: Unique identifier for the step (should match a key in your schema)children
: Step content
MultiStepFormHeader
Optional component for adding a header to your form, often used with the Stepper component.
MultiStepFormContextProvider
Provides access to form context within child components.
useMultiStepFormContext
The hook returns an object with the following properties:
form: UseFormReturn<z.infer<Schema>>
- The original form object.currentStep: string
- The name of the current step.currentStepIndex: number
- The index of the current step (0-based).totalSteps: number
- The total number of steps in the form.isFirstStep: boolean
- Whether the current step is the first step.isLastStep: boolean
- Whether the current step is the last step.nextStep: (e: React.SyntheticEvent) => void
- Function to move to the next step.prevStep: (e: React.SyntheticEvent) => void
- Function to move to the previous step.goToStep: (index: number) => void
- Function to jump to a specific step by index.direction: 'forward' | 'backward' | undefined
- The direction of the last step change.isStepValid: () => boolean
- Function to check if the current step is valid.isValid: boolean
- Whether the entire form is valid.errors: FieldErrors<z.infer<Schema>>
- Form errors from React Hook Form.mutation: UseMutationResult
- A mutation object for handling form submission.
Example
Here's a more complete example of a multi-step form with three steps: Account, Profile, and Review. The form uses Zod for schema validation and React Hook Form for form management.
'use client';import { zodResolver } from '@hookform/resolvers/zod';import { useForm } from 'react-hook-form';import { z } from 'zod';import { Button } from '@kit/ui/button';import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage,} from '@kit/ui/form';import { Input } from '@kit/ui/input';import { MultiStepForm, MultiStepFormContextProvider, MultiStepFormHeader, MultiStepFormStep, createStepSchema, useMultiStepFormContext,} from '@kit/ui/multi-step-form';import { Stepper } from '@kit/ui/stepper';const FormSchema = createStepSchema({ account: z.object({ username: z.string().min(3), email: z.string().email(), }), profile: z.object({ password: z.string().min(8), age: z.coerce.number().min(18), }),});type FormValues = z.infer<typeof FormSchema>;export function MultiStepFormDemo() { const form = useForm<FormValues>({ resolver: zodResolver(FormSchema), defaultValues: { account: { username: '', email: '', }, profile: { password: '', }, }, reValidateMode: 'onBlur', mode: 'onBlur', }); const onSubmit = (data: FormValues) => { console.log('Form submitted:', data); }; return ( <MultiStepForm className={'space-y-10 p-8 rounded-xl border'} schema={FormSchema} form={form} onSubmit={onSubmit} > <MultiStepFormHeader className={'flex w-full flex-col justify-center space-y-6'} > <h2 className={'text-xl font-bold'}>Create your account</h2> <MultiStepFormContextProvider> {({ currentStepIndex }) => ( <Stepper variant={'numbers'} steps={['Account', 'Profile', 'Review']} currentStep={currentStepIndex} /> )} </MultiStepFormContextProvider> </MultiStepFormHeader> <MultiStepFormStep name="account"> <AccountStep /> </MultiStepFormStep> <MultiStepFormStep name="profile"> <ProfileStep /> </MultiStepFormStep> <MultiStepFormStep name="review"> <ReviewStep /> </MultiStepFormStep> </MultiStepForm> );}function AccountStep() { const { form, nextStep, isStepValid } = useMultiStepFormContext(); return ( <Form {...form}> <div className={'flex flex-col gap-4'}> <FormField name="account.username" render={({ field }) => ( <FormItem> <FormLabel>Username</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField name="account.email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <div className="flex justify-end"> <Button onClick={nextStep} disabled={!isStepValid()}> Next </Button> </div> </div> </Form> );}function ProfileStep() { const { form, nextStep, prevStep } = useMultiStepFormContext(); return ( <Form {...form}> <div className={'flex flex-col gap-4'}> <FormField name="profile.password" render={({ field }) => ( <FormItem> <FormLabel>Password</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField name="profile.age" render={({ field }) => ( <FormItem> <FormLabel>Age</FormLabel> <FormControl> <Input type="number" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <div className="flex justify-end space-x-2"> <Button type={'button'} variant={'outline'} onClick={prevStep}> Previous </Button> <Button onClick={nextStep}>Next</Button> </div> </div> </Form> );}function ReviewStep() { const { prevStep, form } = useMultiStepFormContext<typeof FormSchema>(); const values = form.getValues(); return ( <div className={'flex flex-col space-y-4'}> <div className={'flex flex-col space-y-4'}> <div>Great! Please review the values.</div> <div className={'flex flex-col space-y-2 text-sm'}> <div> <span>Username</span>: <span>{values.account.username}</span> </div> <div> <span>Email</span>: <span>{values.account.email}</span> </div> <div> <span>Age</span>: <span>{values.profile.age}</span> </div> </div> </div> <div className="flex justify-end space-x-2"> <Button type={'button'} variant={'outline'} onClick={prevStep}> Back </Button> <Button type={'submit'}>Create Account</Button> </div> </div> );}
The inner components AccountStep
, ProfileStep
, and ReviewStep
represent the individual steps of the form. They use the useMultiStepFormContext
hook to access form utilities like nextStep
, prevStep
, and isStepValid
.
These are built using ShadcnUI - so please do refer to the ShadcnUI documentation for more information on how to use the components.
Tips
- Use the
createStepSchema
helper to easily create Zod schemas for your multi-step form. - Leverage the
useMultiStepFormContext
hook in your step components to access form utilities. - Combine with the Stepper component for visual progress indication.
- Customize the look and feel using the provided
className
props and your own CSS.
The Multi-Step Form Component simplifies the creation of complex, multi-step forms while providing a great user experience. It's flexible enough to handle a wide variety of use cases while keeping your code clean and maintainable.