ReForm - React Hook Form as Nested
Building "nested" forms with React Hook Form and Zod.
Yes, forms are tricky, but they don't have to be complicated! ReForm is a re-designed version of the shadcn/ui form elements that are nested and ready to use.
We are stick to well-designed HTML forms are:
- Well-structured and semantically correct.
- Easy to use and navigate (keyboard).
- Accessible with ARIA attributes and proper labels.
- Has support for client and server side validation.
- Well-styled and consistent with the rest of the application.
Features
The <ReForm />
component is a wrapper around the react-hook-form
library. It provides a few things:
- Composable components for building forms.
- Each ReForm elements ex.
<ReInput />
component for building nested form fields. - Form validation using
zod
. - Handles accessibility and error messages.
- Uses
React.useId()
for generating unique IDs. - Applies the correct
aria
attributes to form fields based on states. - Built to work with all shadcn/ui components.
- Bring your own schema library. We use
zod
but you can use anything you want. - You have full control over the markup and styling.
Anatomy
<ReForm>
<ReInput name="..." label="..." description="..." placeholder="..." />
</ReForm>
Example
const schema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
password: z.string().min(8, {
message: "Password must be at least 8 characters.",
}),
})
type FormType = z.infer<typeof schema>
const handleSubmit: ReformSubmitHandler<FormType> = async (data) => {
await mutation(data)
}
return (<ReForm<FormType> schema={schema} onSubmit={handleSubmit}>
<ReInput
label="Username"
name="username"
placeholder="username, email or phone number"
/>
<RePassword label="Password" name="password" placeholder="password" />
<ReErrorArea />
<ReSubmit>Login</ReSubmit>
</ReForm>)
Installation
Dependencies
The ReForm is built using the <Form />
from shadcn/ui.
See installation instructions for the Form component.
Usage
Create a form schema and type
Define the shape of your form using a Zod schema. You can read more about using Zod in the Zod documentation.
"use client"
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2).max(50),
})
type FormType = z.infer<typeof formSchema>
Define a schema
"use client"
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
// ...
Define a submit handler
// ...
type FormType = z.infer<typeof formSchema>
export function LoginForm() {
// 1. Define a submit handler.
function handleSubmit<FormType>(values) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values)
}
}
Build your form
We can now use the <ReForm />
components to build our form.
// ...
import { ReErrorArea, ReForm } from "@/components/ui/re-form"
import { ReInput } from "@/components/ui/re-input"
import { ReSubmit } from "@/components/ui/re-submit"
// ...
export function LoginForm() {
// ...
return (
<ReForm<FormType> schema={formSchema} onSubmit={handleSubmit}>
<ReInput
label="Username"
name="username"
placeholder="username, email or phone number"
/>
// ...
<ReSubmit>Login</ReSubmit>
</ReForm>
)
}
Done
That's it. You now have a fully accessible form that is type-safe with client-side validation.
Examples
Error
You can use the <ReErrorArea />
component to display error messages coming from the server.
Validation Error from Backend
See the following links for more examples on how to use the <ReForm />
component with other components: