Skip to content

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:

ComponentValue TypeDescription
textstringSingle-line text input.
textareastringMulti-line text area. Supports rows prop.
emailstringEmail input with browser validation.
passwordstringPassword input with masked characters.
urlstringURL input with browser validation.
intnumberInteger input. Supports min, max, step.
floatnumberDecimal number input. Supports min, max, step.
selectstringDropdown select. Requires options array.
multiselectstring[]Multi-select dropdown. Requires options array.
radiostringRadio button group. Requires options array.
checkboxbooleanSingle checkbox toggle.
switchbooleanToggle switch control.
datestringDate picker (ISO date string).
datetimestringDate and time picker (ISO datetime string).
durationnumberDuration input in seconds.
fileFileFile upload. Supports accept and multiple props.
entity_pickerstringAutocomplete entity search via autocompleteHref.
hiddenstringHidden field, not rendered visually.
staticstringRead-only display value (not editable).
headern/aSection header label inside a group.

Common Field Props

PropTypeDefaultDescription
labelstring(required)Field label displayed above the input.
placeholderstringPlaceholder text inside the input.
helpTextstringHelp text displayed below the input.
requiredbooleanfalseWhether the field is required.
disabledbooleanfalseWhether the field is disabled.
options{ value, label }[]Options for select, multiselect, and radio fields.
minnumberMinimum value for number fields.
maxnumberMaximum value for number fields.
visible_whenFormConditionShow field only when condition is met.
hidden_whenFormConditionHide field when condition is met.
required_whenFormConditionMake field required when condition is met.
disabled_whenFormConditionDisable field when condition is met.

Conditional Fields

Fields can be shown, hidden, required, or disabled based on other field values using FormCondition objects:

ts
{
    kind: 'field',
    key: 'notification_email',
    component: 'email',
    props: {
        label: 'Notification Email',
        visible_when: {
            field: 'email_notifications',
            operator: 'equals',
            value: 'true',
        },
    },
}

Available operators:

OperatorDescription
equalsField value strictly equals the given value.
not_equalsField value does not equal the given value.
inField value is included in the given array of values.
not_inField 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.
ts
{
    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:

php
// Backend returns errors:
return back()->withErrors(['email' => 'Invalid email format']);
ts
// 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:

php
$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',
        ],
    ],
];

Released under the proprietary license.