Your first form
Build a multi-step contact form with validation, conditional logic, and submission — from an empty file to a working form.
What you'll build
A contact form with three sections:
- Contact details — name, email, phone (conditionally required)
- Your message — subject dropdown, message body, priority toggle
- Confirmation — consent checkbox, submit
Along the way you'll use field types, validation, conditional required, showIf, and a submission callback.
Step 1: Create the schema
Every form starts with a schema object. Create a file for it:
import type { FormEngineSchema } from '@squaredr/fieldcraft-react'
export const contactFormSchema: FormEngineSchema = {
id: 'contact-form',
version: '1.0.0',
title: 'Contact Us',
description: 'We usually respond within 24 hours.',
settings: {
displayMode: 'stepped',
showProgress: true,
progressStyle: 'bar',
},
sections: [],
submitAction: { type: 'callback' },
onComplete: {
type: 'message',
message: 'Thank you! We\'ll be in touch shortly.',
},
}The id and version are required — they're included in every submission so your backend knows which form and version generated the response. displayMode: 'stepped' means one section at a time with Next/Back navigation.
Step 2: Add the first section
Add the contact details section to the sections array:
sections: [
{
id: 'contact',
title: 'Contact Details',
questions: [
{
id: 'full_name',
type: 'short_text',
label: 'Full name',
required: true,
placeholder: 'Jane Smith',
},
{
id: 'email',
type: 'email',
label: 'Email address',
required: true,
helpText: 'We\'ll send a confirmation to this address.',
},
{
id: 'preferred_contact',
type: 'single_select',
label: 'Preferred contact method',
required: true,
options: [
{ label: 'Email', value: 'email' },
{ label: 'Phone', value: 'phone' },
],
},
{
id: 'phone',
type: 'phone',
label: 'Phone number',
// Only required when preferred contact is phone
required: {
field: 'preferred_contact',
operator: 'eq',
value: 'phone',
},
// Only visible when preferred contact is phone
showIf: {
field: 'preferred_contact',
operator: 'eq',
value: 'phone',
},
},
],
},
],Two things to notice:
- Conditional required: The phone field uses a
ConditionExpressioninstead oftrue/false. It's only required when the user selects "Phone" as their preferred contact method. showIf: The phone field is hidden entirely until "Phone" is selected. Hidden fields are excluded from validation and submission — you can't fail validation on a field that isn't visible.
Step 3: Add the message section
Add a second section after the first:
{
id: 'message',
title: 'Your Message',
questions: [
{
id: 'subject',
type: 'dropdown',
label: 'Subject',
required: true,
options: [
{ label: 'General enquiry', value: 'general' },
{ label: 'Technical support', value: 'support' },
{ label: 'Billing', value: 'billing' },
{ label: 'Partnership', value: 'partnership' },
],
config: { type: 'dropdown', searchable: false },
},
{
id: 'message',
type: 'long_text',
label: 'Message',
required: true,
placeholder: 'Describe what you need help with...',
config: {
type: 'long_text',
rows: 5,
maxLength: 2000,
showCharCount: true,
},
},
{
id: 'priority',
type: 'boolean',
label: 'This is urgent',
config: {
type: 'boolean',
style: 'toggle',
trueLabel: 'Yes',
falseLabel: 'No',
},
},
],
},The config object holds type-specific settings. A long_text field has rows, maxLength, and showCharCount. A boolean field can render as a toggle, radio, or checkbox. Each field type has its own config shape — see Field types for the full reference.
Step 4: Add the confirmation section
{
id: 'confirm',
title: 'Confirm & Submit',
questions: [
{
id: 'consent',
type: 'consent',
label: 'Data processing consent',
required: true,
config: {
type: 'consent',
text: 'I agree to the processing of my personal data in accordance with the privacy policy.',
checkboxLabel: 'I agree',
},
},
],
},Step 5: Render the form
Create a page component that renders the form:
import { FormRenderer } from '@squaredr/fieldcraft-react'
import { contactFormSchema } from '@/schemas/contact-form'
export default function ContactPage() {
return (
<div style={{ maxWidth: 640, margin: '0 auto', padding: '2rem' }}>
<FormRenderer
schema={contactFormSchema}
onSubmit={async (response) => {
// response includes schemaId, schemaVersion, values, submittedAt, sessionToken
await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(response),
})
}}
/>
</div>
)
}The onSubmit callback receives a FormResponse object — not raw values. It includes metadata like schemaId, schemaVersion, submittedAt (ISO timestamp), sessionToken (UUID), and completionTimeMs (time from form load to submit).
Step 6: Add validation
Go back to the email field and add validation rules:
{
id: 'email',
type: 'email',
label: 'Email address',
required: true,
helpText: 'We\'ll send a confirmation to this address.',
validation: [
{ type: 'email', message: 'Please enter a valid email address.' },
],
},The email field type already has basic format checking built in. The explicit validation array lets you customise the error message or add additional rules. Available rule types: required, min, max, minLength, maxLength, pattern, email, phone, url, date, fileSize, fileType, custom, and async.
See Validation for the complete reference.
Step 7: Apply a theme
Pass a theme preset to change the visual appearance:
import { FormRenderer, cleanPreset } from '@squaredr/fieldcraft-react'
<FormRenderer
schema={contactFormSchema}
theme={cleanPreset}
onSubmit={handleSubmit}
/>Six built-in presets are available: cleanPreset, modernPreset, darkPreset, highContrastPreset, clinicalPreset, playfulPreset. You can also pass a custom FormEngineTheme object — see Theming.
The complete schema
Here's the full schema in one block:
import type { FormEngineSchema } from '@squaredr/fieldcraft-react'
export const contactFormSchema: FormEngineSchema = {
id: 'contact-form',
version: '1.0.0',
title: 'Contact Us',
description: 'We usually respond within 24 hours.',
settings: {
displayMode: 'stepped',
showProgress: true,
progressStyle: 'bar',
},
sections: [
{
id: 'contact',
title: 'Contact Details',
questions: [
{
id: 'full_name',
type: 'short_text',
label: 'Full name',
required: true,
placeholder: 'Jane Smith',
},
{
id: 'email',
type: 'email',
label: 'Email address',
required: true,
helpText: 'We\'ll send a confirmation to this address.',
validation: [
{ type: 'email', message: 'Please enter a valid email address.' },
],
},
{
id: 'preferred_contact',
type: 'single_select',
label: 'Preferred contact method',
required: true,
options: [
{ label: 'Email', value: 'email' },
{ label: 'Phone', value: 'phone' },
],
},
{
id: 'phone',
type: 'phone',
label: 'Phone number',
required: { field: 'preferred_contact', operator: 'eq', value: 'phone' },
showIf: { field: 'preferred_contact', operator: 'eq', value: 'phone' },
},
],
},
{
id: 'message',
title: 'Your Message',
questions: [
{
id: 'subject',
type: 'dropdown',
label: 'Subject',
required: true,
options: [
{ label: 'General enquiry', value: 'general' },
{ label: 'Technical support', value: 'support' },
{ label: 'Billing', value: 'billing' },
{ label: 'Partnership', value: 'partnership' },
],
config: { type: 'dropdown', searchable: false },
},
{
id: 'message',
type: 'long_text',
label: 'Message',
required: true,
placeholder: 'Describe what you need help with...',
config: { type: 'long_text', rows: 5, maxLength: 2000, showCharCount: true },
},
{
id: 'priority',
type: 'boolean',
label: 'This is urgent',
config: { type: 'boolean', style: 'toggle', trueLabel: 'Yes', falseLabel: 'No' },
},
],
},
{
id: 'confirm',
title: 'Confirm & Submit',
questions: [
{
id: 'consent',
type: 'consent',
label: 'Data processing consent',
required: true,
config: {
type: 'consent',
text: 'I agree to the processing of my personal data in accordance with the privacy policy.',
checkboxLabel: 'I agree',
},
},
],
},
],
submitAction: { type: 'callback' },
onComplete: {
type: 'message',
message: 'Thank you! We\'ll be in touch shortly.',
},
}Next steps
- Schema anatomy — understand every property in the schema
- Field types — all 44 field types with their configs
- Conditional logic —
showIf, jump rules, and conditional required - Theming — customise colours, typography, spacing, and shape