Skip to main content
Forms collect structured data from users. You can build them by hand with individual input components, or generate them automatically from a Pydantic model with Form.from_model().

Basic Usage

A Form groups labeled inputs with a submit action. Named inputs automatically sync their values to client state, so {{ name }} always reflects the current value of Input(name="name"). Wrap in a Card for visual structure. Building forms by hand gives full control over layout and behavior, but requires wiring up every label, input type, and argument template yourself. For models with many fields, Form.from_model() handles all of that automatically.

Generating Forms Automatically

Form.from_model() introspects a Pydantic model and generates a complete form — labels, typed inputs, validation constraints, and a submit button — from the model’s field definitions. Pass a CallTool as the submit action, and from_model() automatically wires up the arguments from the model’s fields so you don’t have to write {{ template }} expressions for every field. Notice that the CallTool only specifies the tool name — no arguments. from_model() generates them automatically, wrapping each field as {{ field_name }} under a data key. It also adds a default error toast so validation failures are visible without any extra configuration.

Custom Layout

The turnkey from_model() handles everything: the Form wrapper, the submit button, and the CallTool argument wiring. When you need more control — a Card with a footer, a cancel button, a title — pass fields_only=True to generate just the labeled inputs, and take over the rest. With fields_only, three things that were automatic become your responsibility. You create the Form yourself and provide on_submit with explicit arguments (the turnkey version auto-generates these from the model, but fields_only doesn’t touch your CallTool). You add your own submit button wherever it belongs in the layout. And you handle spacing — wrap the fields in a Column(gap=4) since they’re no longer inside a Form that provides gap for you. Compare this to the turnkey version above: the CallTool now has explicit arguments, the buttons are placed in CardFooter, and the fields are wrapped in Column(gap=4) for spacing. The model still defines all the labels, input types, and validation — fields_only just stops short of deciding how to lay them out.

Type Mapping

Each field’s Python type determines which input component is rendered. A field named email gets an email input automatically, a bool becomes a checkbox, number types get number inputs with constraints, and so on. The full type mapping:
Python typeInput component
strText input (auto-detects email, password, tel, url from field name)
int, floatNumber input
boolCheckbox
Literal["a", "b", "c"]Select dropdown
SecretStrPassword input
datetime.dateDate picker
datetime.timeTime picker
datetime.datetimeDatetime picker

Field Metadata

Pydantic Field() parameters control labels, placeholders, and HTML validation constraints. In this example, title sets the label, description sets the placeholder, min_length enforces a minimum, and the ui hint overrides the default text input with a textarea. The full metadata mapping:
Field metadataForm effect
Field(title="...")Label text (fallback: humanized field name)
Field(description="...")Placeholder text
Field(min_length=..., max_length=...)HTML input constraints
Field(ge=..., le=...)Number input min/max
Field(json_schema_extra={"ui": {"type": "textarea", "rows": 4}})Textarea override
Field(exclude=True)Skip field entirely

Error Handling

When on_submit is a CallTool with no explicit on_error, from_model() adds a default error toast that displays the server’s error message. You can override it with your own feedback. The $error variable captures the error message from a failed tool call. When no on_error is provided, from_model() adds ShowToast("{{ $error }}", variant="error") automatically.

Unsupported Types

from_model() skips fields with complex types that have no natural form input mapping: list, dict, set, tuple, and nested BaseModel instances. If you need these, build those parts of the form manually and handle the arguments yourself.

API Reference

Form Parameters

gap
int
default:"4"
Spacing between form children (maps to Tailwind gap-N).
on_submit
Action | list[Action] | None
default:"None"
Action(s) to execute when the form is submitted.
css_class
str | None
default:"None"
Additional Tailwind CSS classes.

Form.from_model() Parameters

model
type[BaseModel]
required
Pydantic model class to generate the form from.
fields_only
bool
default:"False"
When True, returns a list of field components without a Form wrapper or submit button. The fields auto-parent to the active context manager. Use this for custom layouts where you provide your own Form, button, and CallTool arguments.
submit_label
str
default:"\"Submit\""
Text for the submit button. Ignored when fields_only=True.
on_submit
Action | list[Action] | None
default:"None"
Action(s) fired on submit. A CallTool with no arguments gets auto-filled from model fields under a data key.
css_class
str | None
default:"None"
Additional CSS classes on the form container.

Protocol Reference

Form
{
  "type": "Form",
  "children?": "[Component]",
  "let?": "object",
  "gap?": 4,
  "onSubmit?": "Action | Action[]",
  "cssClass?": "string"
}
For the complete protocol schema, see Form.