Auto Form
Component creates form based on Zod schemas & react-hook-form with validation
Preview
Usage
import { AutoForm } from "@vitnode/core/components/form/auto-form";
import { AutoFormCheckbox } from "@vitnode/core/components/form/fields/checkbox";
import { AutoFormInput } from "@vitnode/core/components/form/fields/input";
import { AutoFormSelect } from "@vitnode/core/components/form/fields/select";
import { AutoFormTextarea } from "@vitnode/core/components/form/fields/textarea";
import { AutoFormArray } from "@vitnode/core/components/form/fields/array";
import { InputGroupAddon } from "@vitnode/core/components/ui/input-group";
import { Search } from "lucide-react";
import { z } from "zod";const formSchema = z.object({
username: z.string().min(3, "Username must be at least 3 characters"),
email: z
.email("Please enter a valid email address")
.describe("We'll use this email to contact you. (from zod schema)"),
user_type: z.enum(["admin", "editor", "viewer"]),
links: z
.array(
z.object({
title: z.string().min(1, "Title is required"),
url: z.string().url(),
}),
)
.optional(),
accept_terms: z.boolean().refine(val => val, {
message: "You must accept the terms and conditions",
}),
description: z.string().min(10, "Description must be at least 10 characters"),
search: z.string().optional(),
});<AutoForm
fields={[
{
id: "username",
component: props => (
<AutoFormInput
{...props}
description="This is the username for your application. It should be unique and not shared with anyone."
label="Username"
/>
),
},
{
id: "email",
component: props => <AutoFormInput {...props} label="Email Address" />,
},
{
id: "user_type",
component: props => (
<AutoFormSelect
{...props}
description="Select the type of user."
label="User Type"
labels={[
{ value: "admin", label: "Admin" },
{ value: "editor", label: "Editor" },
{ value: "viewer", label: "Viewer" },
]}
/>
),
},
{
id: "links",
component: props => (
<AutoFormArray
{...props}
label="Profile Links"
fields={[
{
id: "title",
component: subProps => (
<AutoFormInput {...subProps} label="Title" />
),
},
{
id: "url",
component: subProps => (
<AutoFormInput {...subProps} label="URL" />
),
},
]}
/>
),
},
{
id: "accept_terms",
component: props => (
<AutoFormCheckbox
{...props}
label="I accept the terms and conditions"
/>
),
},
{
id: "description",
component: props => (
<AutoFormTextarea
{...props}
description="Write a short description of your application."
label="Description"
placeholder="My application is..."
/>
),
},
{
id: "search",
component: props => (
<AutoFormInput {...props} placeholder="Search..." label="Search">
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</AutoFormInput>
),
},
]}
formSchema={formSchema}
/>Zod Schema Configuration
Auto Form is deeply integrated with Zod, supporting Zod validators. HTML input attributes are automatically applied based on your schema constraints.
Required vs Optional Fields
By default, all fields are required. Make a field optional using the optional method:
const formSchema = z.object({
username: z.string(), // Required field
bio: z.string().optional(), // Optional field
});Labels
Set field labels using the label property in the field definition:
{
id: 'username',
component: props => (
<AutoFormInput {...props} label="Username" />
),
}Right Labels
You can also add a label on the right side of the field using the labelRight property:
{
id: 'username',
component: props => (
<AutoFormInput {...props} label="Username" labelRight="Required" />
),
}Descriptions
Add descriptions to fields using the description property:
{
id: 'username',
component: props => (
<AutoFormInput
{...props}
label="Username"
description="This is the username for your application."
/>
),
}or using the describe method in Zod:
const formSchema = z.object({
username: z.string().describe("This is the username for your application."),
});Default Values
Set default values for fields using the default method:
const formSchema = z.object({
username: z.string().default("user123"),
role: z.enum(["user", "admin"]).default("user"),
});Arrays
You can create dynamic list of fields using AutoFormArray component working with z.array(z.object(...)) schema:
const formSchema = z.object({
guests: z
.array(
z.object({
name: z.string(),
email: z.string().email(),
}),
)
.min(1),
});{
id: "guests",
component: props => (
<AutoFormArray
{...props}
label="Guests"
fields={[
{ id: "name", component: p => <AutoFormInput {...p} label="Name" /> },
{ id: "email", component: p => <AutoFormInput {...p} label="Email" /> },
]}
/>
)
}You can customize the wrapper, items layout, and Add/Remove buttons logic through className, addButtonLabel props:
{
id: "guests",
component: props => (
<AutoFormArray
{...props}
label="Guests"
fields={[
{ id: "name", className: "flex-1", component: p => <AutoFormInput {...p} label="Name" /> },
{ id: "email", component: p => <AutoFormInput {...p} label="Email" /> },
]}
/>
)
}Advanced Validation
Auto Form supports all Zod validators:
const formSchema = z.object({
username: z.string().min(3).max(20),
email: z.email(),
age: z.number().min(18).max(120),
password: z
.string()
.min(8)
.refine(val => /[A-Z]/.test(val), {
message: "Password must contain at least one uppercase letter",
}),
});Custom Fields
Because Auto Form fields are controlled entirely by the component property function, creating a custom field component is just a matter of rendering your own UI using the provided props. The props passed to the component function contain the field properties managed by react-hook-form along with Zod validation details.
Here is an example of creating a custom color picker input:
const formSchema = z.object({
custom_color: z
.string()
.default("#000000")
.describe("Pick your favorite color."),
});<AutoForm
formSchema={formSchema}
fields={[
{
id: "custom_color",
component: props => (
<div className="flex w-full flex-col gap-3">
<div className="text-sm leading-none font-medium">
Custom Color Picker
</div>
<input
{...props.field}
value={(props.field.value as string) ?? "#000000"}
className="h-10 w-24 cursor-pointer rounded-md border p-1"
type="color"
/>
{props.description && (
<div className="text-muted-foreground text-sm">
{props.description}
</div>
)}
</div>
),
},
]}
onSubmit={values => console.log(values)}
/>Form Submission
To activate submit button and handle form submission with the onSubmit callback:
<AutoForm
fields={[
{
id: "username",
component: props => (
<AutoFormInput
{...props}
description="This is the username for your application."
label="Username"
/>
),
},
]}
formSchema={formSchema}
onSubmit={values => {
// Handle form submission
}}
/>Accessing the Form Instance
The onSubmit callback provides access to the React Hook Form instance as a second parameter:
<AutoForm
fields={
[
/* ...field definitions */
]
}
formSchema={formSchema}
onSubmit={(values, form) => {
// Access form methods
form.setError("username", {
type: "manual",
message: "Username already taken",
});
}}
/>You can also define the submission handler separately:
import type { AutoFormOnSubmit } from "@vitnode/core/components/form/auto-form";const onSubmit: AutoFormOnSubmit<typeof formSchema> = async (values, form) => {
try {
await saveData(values);
toast.success("Form submitted successfully");
} catch (error) {
form.setError("root", {
type: "manual",
message: "Failed to submit form",
});
}
};
// Then in your component
<AutoForm
fields={
[
/* ...field definitions */
]
}
formSchema={formSchema}
onSubmit={onSubmit}
/>;Customizing the Submit Button
Customize the submit button using the submitButtonProps:
<AutoForm
fields={
[
/* ...field definitions */
]
}
formSchema={formSchema}
submitButtonProps={{
variant: "outline",
size: "lg",
children: "Save Changes",
className: "w-full mt-4",
}}
onSubmit={values => {
console.log("Form submitted", values);
}}
/>Form Layout and Styling
You can control the form layout using standard CSS techniques:
<AutoForm
className="grid grid-cols-1 gap-4 md:grid-cols-2"
fields={
[
/* ...field definitions */
]
}
formSchema={formSchema}
/>