| [ Web Proxy ] |
| Viewing: https://marrowstack.dev/docs/blocks/forms | [Back] [Original] |
Content Starter
Typed form primitives and three pre-built forms for Next.js 14+ built on React Hook Form and Zod, with server-side validation via Server Actions and a multi-step form hook.
Read the Getting Access guideif you haven't yet.
Sign in at marrowstack.dev, open the Form Validation block, and click Copy all files. Paste lib/forms.ts (and the pre-built form components) into your project.
npm install react-hook-form zod @hookform/resolversUse useZodForm() to get a fully-typed React Hook Form instance wired to a Zod schema:
'use client'
import { useZodForm, FormField, FormError } from '@/lib/forms'
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
message: z.string().min(10),
})
export function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useZodForm(schema)
const onSubmit = handleSubmit(async (data) => {
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(data),
})
})
return (
<form onSubmit={onSubmit}>
<FormField label="Email" error={errors.email?.message}>
<input {...register('email')} type="email" />
</FormField>
<FormField label="Message" error={errors.message?.message}>
<textarea {...register('message')} />
</FormField>
<button type="submit">Send</button>
</form>
)
}Or drop in one of the three pre-built forms directly:
import { ContactForm, LoginForm, NewsletterForm } from '@/lib/forms'
// Each form calls a Server Action for server-side validation.
// Wire the action prop to your own handler.
export default function Page() {
return <ContactForm action={submitContactForm} />
}For multi-step flows, use the useMultiStepForm() hook:
'use client'
import { useMultiStepForm } from '@/lib/forms'
const steps = [StepOne, StepTwo, StepThree]
export function OnboardingFlow() {
const { step, next, back, isFirst, isLast } = useMultiStepForm(steps.length)
const CurrentStep = steps[step]
return (
<div>
<CurrentStep />
<div>
{!isFirst && <button onClick={back}>Back</button>}
{!isLast && <button onClick={next}>Next</button>}
</div>
</div>
)
}Type error on useZodForm schema type mismatch
Cause: Zod version mismatch between the block and @hookform/resolvers.
Fix: Ensure both use zod@3.x. Run npm ls zod to check for version conflicts.
Server Action not called on submit
Cause: The form is missing the action prop, or the component is used outside a Next.js App Router page.
Fix: Pass your Server Action to the action prop. Server Actions require App Router.
FormField styles not applying
Cause: Tailwind is not scanning the block file for class names.
Fix: Add the block file path to your tailwind.config.ts content array.
| Web Proxy Viewer | New URL | Original Page |