Advanced
Layouts
Control how form fields are arranged — single column, multi-column grids, horizontal labels, and custom wrappers.
Single column (default)
By default, fields stack vertically in a single column:
const fields = [
FormFieldClass.text('name', { label: 'Name' }),
FormFieldClass.email('email', { label: 'Email' }),
FormFieldClass.textArea('message', { label: 'Message' }),
]
<Form id="contact" fields={fields} submit={save}>
<button type="submit">Submit</button>
</Form>
Multi-column with CSS Grid
Use wrapperClassName on fields to control their grid placement. Wrap the form in a grid container.
Two-column layout
const fields = [
FormFieldClass.text('firstName', {
label: 'First Name',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.text('lastName', {
label: 'Last Name',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.email('email', {
label: 'Email',
wrapperClassName: 'col-span-2', // Full width
}),
FormFieldClass.phone('phone', {
label: 'Phone',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.text('company', {
label: 'Company',
wrapperClassName: 'col-span-1',
}),
]
For declarative forms, apply the grid to the form's container. The form renders fields inside a div that you can target with CSS.
Three-column layout
const addressFields = [
FormFieldClass.text('street', {
label: 'Street Address',
wrapperClassName: 'col-span-3', // Full width
}),
FormFieldClass.text('city', {
label: 'City',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.select('state', {
label: 'State',
options: stateOptions,
wrapperClassName: 'col-span-1',
}),
FormFieldClass.text('zip', {
label: 'ZIP Code',
wrapperClassName: 'col-span-1',
}),
]
Multi-column with Flexbox
Use flex classes on wrapperClassName:
const fields = [
FormFieldClass.text('firstName', {
label: 'First Name',
wrapperClassName: 'flex-1',
}),
FormFieldClass.text('lastName', {
label: 'Last Name',
wrapperClassName: 'flex-1',
}),
]
Imperative layout control
The imperative API gives you direct control over HTML structure:
<Form id="complex" submit={save}>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-2">
<RenderFormField
field={FormFieldClass.text('title', { label: 'Title' })}
/>
</div>
<div>
<RenderFormField
field={FormFieldClass.select('status', {
label: 'Status',
options: statusOptions,
})}
/>
</div>
</div>
<div className="mt-6">
<RenderFormField
field={FormFieldClass.textArea('description', {
label: 'Description',
rows: 6,
})}
/>
</div>
<div className="mt-6 grid grid-cols-2 gap-4">
<RenderFormField
field={FormFieldClass.datePicker('startDate', { label: 'Start' })}
/>
<RenderFormField
field={FormFieldClass.datePicker('endDate', { label: 'End' })}
/>
</div>
<div className="mt-8 flex justify-end gap-3">
<button type="button" className="btn-secondary">
Cancel
</button>
<button type="submit" className="btn-primary">
Save
</button>
</div>
</Form>
Horizontal labels
Use the layout option to display labels alongside inputs instead of above them:
FormFieldClass.text('name', {
label: 'Full Name',
layout: 'horizontal', // Label appears to the left of the input
})
Settings-style form
const settingsFields = [
FormFieldClass.text('displayName', {
label: 'Display Name',
layout: 'horizontal',
}),
FormFieldClass.email('email', {
label: 'Email Address',
layout: 'horizontal',
}),
FormFieldClass.select('language', {
label: 'Language',
layout: 'horizontal',
options: languageOptions,
}),
FormFieldClass.select('timezone', {
label: 'Timezone',
layout: 'horizontal',
options: timezoneOptions,
}),
FormFieldClass.switch('notifications', {
label: 'Email Notifications',
layout: 'horizontal',
}),
]
Custom wrappers
Wrap individual fields in custom HTML for complete layout control:
FormFieldClass.text('priority', {
label: 'Priority Level',
customWrapper: (children) => (
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4">
<div className="mb-2 flex items-center gap-2">
<span className="text-sm font-bold text-amber-600">HIGH PRIORITY</span>
</div>
{children}
</div>
),
})
Section-based forms
Use content fields and imperative layout to create sections:
With content fields (declarative)
const fields = [
FormFieldClass.content('personalSection', {
content: 'Personal Information',
wrapperClassName:
'col-span-2 text-lg font-bold border-b border-gray-200 pb-2',
}),
FormFieldClass.text('firstName', {
label: 'First Name',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.text('lastName', {
label: 'Last Name',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.content('addressSection', {
content: 'Address',
wrapperClassName:
'col-span-2 text-lg font-bold border-b border-gray-200 pb-2 mt-6',
}),
FormFieldClass.text('street', {
label: 'Street',
wrapperClassName: 'col-span-2',
}),
FormFieldClass.text('city', {
label: 'City',
wrapperClassName: 'col-span-1',
}),
FormFieldClass.text('zip', { label: 'ZIP', wrapperClassName: 'col-span-1' }),
]
With JSX sections (imperative)
<Form id="application" submit={save}>
<section className="mb-8">
<h2 className="mb-4 text-xl font-bold">Personal Information</h2>
<div className="grid grid-cols-2 gap-4">
<RenderFormField
field={FormFieldClass.text('firstName', { label: 'First Name' })}
/>
<RenderFormField
field={FormFieldClass.text('lastName', { label: 'Last Name' })}
/>
</div>
</section>
<section className="mb-8">
<h2 className="mb-4 text-xl font-bold">Professional Details</h2>
<RenderFormField
field={FormFieldClass.text('company', { label: 'Company' })}
/>
<RenderFormField
field={FormFieldClass.text('title', { label: 'Job Title' })}
/>
</section>
<button type="submit">Submit Application</button>
</Form>
Responsive layouts
Use Tailwind's responsive prefixes in wrapperClassName:
FormFieldClass.text('firstName', {
label: 'First Name',
wrapperClassName: 'col-span-2 sm:col-span-1', // Full width on mobile, half on desktop
})