Forms Guide
The FormPanelBlock renders schema-driven forms from the page contract. The backend defines the form structure, field types, and validation -- the frontend renders it automatically.
Form Schema Structure
A form is described by FormPanelBlockData, which contains the submission endpoint, HTTP method, schema tree, current values, and errors:
FormPanelBlockData
├── action: "/api/endpoint"
├── method: "post" | "put" | "patch"
├── schema: FormSchemaNode[]
│ ├── FormSectionNode { kind: 'section', id, label, children[] }
│ ├── FormGroupNode { kind: 'group', id, columns, children[] }
│ ├── FormHeaderNode { kind: 'header', label }
│ └── FormFieldNode { kind: 'field', key, component, props }
├── values: { field_key: current_value }
├── errors: { field_key: "error message" }
└── meta: { submitLabel, cancelLabel, cancelHref, multiStep }Available Field Components
The component field on each FormFieldNode determines which input component renders:
| Component | Value Type | Description |
|---|---|---|
text | string | Single-line text input. |
textarea | string | Multi-line text area. Supports rows prop. |
email | string | Email input with browser validation. |
password | string | Password input with masked characters. |
url | string | URL input with browser validation. |
int | number | Integer input. Supports min, max, step. |
float | number | Decimal number input. Supports min, max, step. |
select | string | Dropdown select. Requires options array. |
multiselect | string[] | Multi-select dropdown. Requires options array. |
radio | string | Radio button group. Requires options array. |
checkbox | boolean | Single checkbox toggle. |
switch | boolean | Toggle switch control. |
date | string | Date picker (ISO date string). |
datetime | string | Date and time picker (ISO datetime string). |
duration | number | Duration input in seconds. |
file | File | File upload. Supports accept and multiple props. |
entity_picker | string | Autocomplete entity search via autocompleteHref. |
hidden | string | Hidden field, not rendered visually. |
static | string | Read-only display value (not editable). |
header | n/a | Section header label inside a group. |
Common Field Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | (required) | Field label displayed above the input. |
placeholder | string | Placeholder text inside the input. | |
helpText | string | Help text displayed below the input. | |
required | boolean | false | Whether the field is required. |
disabled | boolean | false | Whether the field is disabled. |
options | { value, label }[] | Options for select, multiselect, and radio fields. | |
min | number | Minimum value for number fields. | |
max | number | Maximum value for number fields. | |
visible_when | FormCondition | Show field only when condition is met. | |
hidden_when | FormCondition | Hide field when condition is met. | |
required_when | FormCondition | Make field required when condition is met. | |
disabled_when | FormCondition | Disable field when condition is met. |
Conditional Fields
Fields can be shown, hidden, required, or disabled based on other field values using FormCondition objects:
{
kind: 'field',
key: 'notification_email',
component: 'email',
props: {
label: 'Notification Email',
visible_when: {
field: 'email_notifications',
operator: 'equals',
value: 'true',
},
},
}Available operators:
| Operator | Description |
|---|---|
equals | Field value strictly equals the given value. |
not_equals | Field value does not equal the given value. |
in | Field value is included in the given array of values. |
not_in | Field value is not included in the given array of values. |
Sections and Groups
Form schemas support structural nodes for organizing fields:
- Sections -- collapsible containers with a label and optional
defaultCollapsed. - Groups -- multi-column layouts (
columns: 1 | 2). - Nesting -- Section > Group > Field.
{
kind: 'section',
id: 'contact',
label: 'Contact Information',
defaultCollapsed: false,
children: [
{
kind: 'group',
id: 'name-group',
columns: 2,
children: [
{ kind: 'field', key: 'first_name', component: 'text', props: { label: 'First Name', required: true } },
{ kind: 'field', key: 'last_name', component: 'text', props: { label: 'Last Name', required: true } },
],
},
{ kind: 'field', key: 'email', component: 'email', props: { label: 'Email', required: true } },
],
}Server Errors
Validation errors returned by the backend are passed in the errors field of FormPanelBlockData. Each key maps to a field key:
// Backend returns errors:
return back()->withErrors(['email' => 'Invalid email format']);// Contract (received by frontend)
{
errors: {
email: 'Invalid email format'
}
}Real-time feedback
Errors are displayed inline next to each field automatically. The form block matches error keys to field keys in the schema.
PHP Example
Full PHP example showing how to build a form contract from the backend:
$form = [
'type' => 'form_panel',
'key' => 'settings-form',
'data' => [
'action' => '/api/settings',
'method' => 'put',
'schema' => [
[
'kind' => 'section',
'id' => 'general',
'label' => 'General Settings',
'children' => [
[
'kind' => 'field',
'key' => 'site_name',
'component' => 'text',
'props' => [
'label' => 'Site Name',
'required' => true,
],
],
[
'kind' => 'field',
'key' => 'language',
'component' => 'select',
'props' => [
'label' => 'Language',
'options' => [
['value' => 'en', 'label' => 'English'],
['value' => 'pt_br', 'label' => 'Portuguese'],
],
],
],
],
],
],
'values' => $currentValues,
'errors' => $request->errors() ?? [],
'meta' => [
'submitLabel' => 'Save Settings',
'cancelHref' => '/dashboard',
],
],
];