Quick Start
Build a complete working form with validation and conditional logic in under five minutes.
Quick Start
This guide walks you through building a real form from scratch. By the end you'll have a working contact form with validation, a conditional field, and typed submission data.
Make sure you've completed Installation before continuing.
Step 1: Define Your Schema
A schema is a plain object with a fields array. Each field has a type, a name, and optional configuration:
import { defineSchema, type InferType } from "@buildnbuzz/form-react";
export const contactSchema = defineSchema({
title: "Contact Form",
fields: [
{ type: "text", name: "name", label: "Full Name", required: true },
{ type: "email", name: "email", label: "Email", required: true },
{ type: "textarea", name: "message", label: "Message", required: true },
],
});
export type ContactData = InferType<typeof contactSchema.fields>;
// Type: { name: string, email: string, message: string }Defining schemas in separate files (like lib/schemas/contact.ts) allows you to reuse them in forms, server actions, and API routes. See Schema for details.
Step 2: Create the Form
Render the form with the shadcn registry components. The schema drives rendering and validation:
"use client";
import { contactSchema } from "@/lib/schemas/contact";
import { Form, FormContent, FormFields, FormSubmit } from "@/components/buzzform/form";
import { toast } from "sonner";
export function ContactForm() {
return (
<Form
schema={contactSchema}
onSubmit={({ value }) => {
// value is typed as ContactData
toast("Message sent!", {
description: `From: ${value.email}`,
});
}}
>
<FormContent>
<FormFields />
<FormSubmit>Send Message</FormSubmit>
</FormContent>
</Form>
);
}<FormFields /> renders all fields using the FieldRegistry from <FormProvider>. Use <FormSubmit /> for a submit button that auto-disables while submitting.
Step 3: Add Validation
BuzzForm auto-derives validation from field properties like required, minLength, etc. You can also add explicit validation rules:
import { defineSchema, type InferType } from "@buildnbuzz/form-react";
export const contactSchema = defineSchema({
title: "Contact Form",
fields: [
{
type: "text",
name: "name",
label: "Full Name",
required: true,
minLength: 2,
},
{
type: "email",
name: "email",
label: "Email",
required: true,
},
{
type: "textarea",
name: "message",
label: "Message",
required: true,
minLength: 10,
maxLength: 500,
},
],
});Now the form validates:
- Name must be at least 2 characters
- Email must be valid format (auto-derived from
type: "email") - Message must be between 10-500 characters
See Validation for the complete list of validators.
Step 4: Add Conditional Logic
Show a field only when certain conditions are met. The condition property uses a declarative AST — no callbacks:
export const contactSchema = defineSchema({
title: "Contact Form",
fields: [
{
type: "text",
name: "name",
label: "Full Name",
required: true,
minLength: 2,
},
{
type: "email",
name: "email",
label: "Email",
required: true,
},
{
type: "select",
name: "subject",
label: "Subject",
options: ["General", "Support", "Sales", "Other"],
required: true,
},
{
type: "textarea",
name: "message",
label: "Message",
required: true,
minLength: 10,
maxLength: 500,
},
// Only shown when subject is "Other"
{
type: "text",
name: "otherSubject",
label: "Please Specify",
condition: { $data: "/subject", eq: "Other" },
required: true,
},
],
});When subject is not "Other", the otherSubject field is completely removed from the form state.
Note: When using condition, you don't need to repeat required in the condition. The field is only present when the condition is met, so required: true only applies when visible.
Complete Example
Here's the complete form with all features:
import { defineSchema, type InferType } from "@buildnbuzz/form-react";
export const contactSchema = defineSchema({
title: "Contact Form",
fields: [
{
type: "text",
name: "name",
label: "Full Name",
placeholder: "John Doe",
required: true,
minLength: 2,
},
{
type: "email",
name: "email",
label: "Email",
placeholder: "john@example.com",
required: true,
},
{
type: "select",
name: "subject",
label: "Subject",
options: ["General", "Support", "Sales", "Other"],
required: true,
},
{
type: "textarea",
name: "message",
label: "Message",
placeholder: "How can we help?",
required: true,
minLength: 10,
maxLength: 500,
description: "Be as detailed as possible so we can help you better.",
},
{
type: "text",
name: "otherSubject",
label: "Please Specify",
condition: { $data: "/subject", eq: "Other" },
required: true,
},
],
});
export type ContactData = InferType<typeof contactSchema.fields>;"use client";
import { contactSchema, type ContactData } from "@/lib/schemas/contact";
import { Form, FormContent, FormFields, FormSubmit } from "@/components/buzzform/form";
import { toast } from "sonner";
export function ContactForm() {
return (
<Form
schema={contactSchema}
onSubmit={async ({ value }) => {
// value is typed as ContactData
await fetch("/api/contact", {
method: "POST",
body: JSON.stringify(value),
});
toast("Message sent!", {
description: `From: ${value.email}`,
});
}}
>
<FormContent>
<FormFields />
<FormSubmit>Send Message</FormSubmit>
</FormContent>
</Form>
);
}"use server";
import { contactSchema, type ContactData } from "@/lib/schemas/contact";
import { db } from "@/lib/db";
export async function submitContact(data: ContactData) {
// data is fully typed!
await db.contacts.create(data);
}Next Steps
Now that you have a working form, explore these topics:
- Schema — Deep dive into schema definition and type inference
- Validation — Learn about custom validators and async validation
- Conditional Logic — Master declarative conditions
- Nested Structures — Work with groups and arrays
- Server Actions — Handle form submission with server actions