FormRenderer
Headless form renderer
FormRenderer
A headless component for rendering forms created with the Form Builder plugin. Uses native HTML elements and is fully customizable via render functions.
Basic Usage
import { FormRenderer, toSubmissionData } from '@01.software/sdk/ui/form'
function ContactForm({ form }) {
const handleSubmit = async (values) => {
await client.from('form-submissions').create({
form: form.id,
submissionData: toSubmissionData(values),
})
}
return <FormRenderer form={form} onSubmit={handleSubmit} />
}Props
| Prop | Type | Description |
|---|---|---|
form | Form | Form Builder form object (required) |
onSubmit | (data: FormValues) => void | Promise<void> | Submit handler (required) |
className | string | <form> CSS class |
fieldClassName | string | Field wrapper CSS class |
buttonClassName | string | Submit button CSS class |
renderField | (field, context, defaultRender) => ReactElement | Custom field renderer |
renderMessage | (data: RichTextData) => ReactElement | Message block renderer |
renderButton | (props: { isSubmitting, label }) => ReactElement | Custom button renderer |
Supported Field Types
text, email, number, textarea, checkbox, select, country, state, message
Styling
Class Name Props
| Prop | Target | Notes |
|---|---|---|
className | <form> element | Controls overall form layout |
fieldClassName | Wrapper <div> around each field (label + input) | Applied to the wrapper, not the <input> itself |
buttonClassName | Submit <button> | Overrides default button styles |
Multi-Column Layout
Fields with a width value (e.g. 50) receive an inline style of width: X%. To create a multi-column layout, apply display: flex and flex-wrap: wrap to the form, and use calc() on each field to account for the gap between columns.
For example, a field with width: 50 renders as:
<div style="width: 50%">
<label>...</label>
<input />
</div>Since the inline width does not account for gaps, you need to override field widths with calc(50% - gap/2) or use CSS to handle spacing.
Tailwind Example
<FormRenderer
form={form}
onSubmit={handleSubmit}
className="flex flex-wrap gap-4"
fieldClassName="flex flex-col gap-1 [&:has([style*='width'])]:w-[calc(50%-0.5rem)]"
buttonClassName="mt-4 w-full rounded-lg bg-black px-4 py-2 text-white hover:bg-gray-800"
/>In this example:
classNamesets the form to a flex container with wrapping and a1remgapfieldClassNamestacks labels above inputs. The[&:has([style*='width'])]selector targets fields that have an inlinewidthstyle and overrides them withcalc(50% - 0.5rem)to account for the gapbuttonClassNamestyles the submit button with full width and rounded corners
For full control over individual field styling, use renderField to return custom markup per field type.
Custom Field Rendering
<FormRenderer
form={form}
onSubmit={handleSubmit}
renderField={(field, context, defaultRender) => {
if (field.blockType === 'email') {
return (
<div className="custom-email">
<input
type="email"
value={String(context.value)}
onChange={(e) => context.onChange(e.target.value)}
/>
</div>
)
}
return defaultRender
}}
/>Exports
| Export | Description |
|---|---|
FormValues | Record<string, string | number | boolean> |
FormField | Form field union type |
InputField | Input field type (excludes message) |
SubmissionData | { field: string; value: string }[] |
FieldRenderContext | { value, onChange } |
toSubmissionData(values) | Converts FormValues → SubmissionData |