Overview
Forms are fluent field trees. You describe the shape; Orbit handles state, dehydration, and HTML.
from almasix.orbit.forms import Form, TextInput, Textarea, Selectfrom almasix.orbit.schemas import Section
form = Form.make("post").schema([ Section.make("basics") .heading("Basics") .description("Title and routing.") .schema([ TextInput.make("title").required().max_length(200), TextInput.make("slug").required().helper_text("Used in URLs"), Select.make("status") .options({"draft": "Draft", "published": "Published"}) .searchable(), Textarea.make("body").rows(10), ]),])
errors = form.validate({"title": ""})# {"title": ["…"]}Form extends Schema — you get .state(), .fill(), .dehydrate(), .columns(), and .render() for free. Prefer .schema([...]) over .components([...]); both work.
Guides
Section titled “Guides”| Page | What you’ll find |
|---|---|
| Standalone forms | Forms outside a Resource |
| Closures | Callable label / options / visible / disabled |
| Field reference | Every field type, one page each |
Field API
Section titled “Field API”Every field starts with Field.make("name") (subclasses call the same factory):
TextInput.make("email") .label("Email address") .placeholder("you@acme.test") .helper_text("We never sell this.") .hint("Work email preferred") .prefix("@") .suffix(".test") .required() .email() .live(on_blur=True) # or live(debounce=300) .after_state_updated(lambda **_: None) .dehydrated(True) .default("you@acme.test") .rules("required", "email")Handy shortcuts: .email(), .password(), .numeric(), .integer(), .tel(), .url(), .max_length(n), .min_length(n), .rows(n), .options(...), .enum(MyEnum), .multiple(), .searchable(), .relationship(name, title_attribute), .unique(...), .exists(...), .regex(...), .between(lo, hi).
Form.validate() / Schema.dehydrate() walk nested layouts (Section / Tabs / Wizard / Repeater schemas).
Visibility / disabled / options / labels can be callables — see Closures.
Validation
Section titled “Validation”form.validate(data) walks all nested fields and applies string rules and callable rules. Use .validation_attribute() / .validation_messages({…}) for friendlier copy. Register DB hooks with Form.unique_using(...) / Form.exists_using(...) (or pass unique= / exists= into validate()).
| Rule | Meaning |
|---|---|
required / filled / nullable |
Presence |
accepted / boolean / array |
Type-ish |
email / url / ip / uuid / json |
Formats |
alpha / alpha_num / alpha_dash |
Character sets |
numeric / integer / digits:N |
Numbers |
min:N / max:N / between:A,B |
Bounds (string length, list size, or numeric) |
gt: / gte: / lt: / lte: |
Compare to another field or number |
confirmed / same:other / different:other |
Cross-field |
in:a,b / not_in:… |
Membership |
regex: / starts_with: / ends_with: |
Pattern |
date / after:… / before:… |
Dates |
unique:table,column[,ignore] / exists:… |
Pluggable DB checks |
mimes:png,jpg / distinct |
Files / uniqueness among siblings |
unique:table,column |
Via unique_using / ctx callback |
exists:table,column |
Via exists_using / ctx callback |
TextInput.make("age").integer().rules("required", "min:18")TextInput.make("password").rules("confirmed")Form.unique_using(lambda value, table=None, column=None, **_: db_is_free(table, column, value))Field types
Section titled “Field types”| Field | Page |
|---|---|
TextInput |
TextInput |
Textarea |
Textarea |
Select / MultiSelect |
Select · MultiSelect |
Checkbox / Toggle |
Checkbox · Toggle |
CheckboxList / Radio |
CheckboxList · Radio |
Hidden / Placeholder |
Hidden · Placeholder |
| Date / time | DatePicker · DateTimePicker · TimePicker |
FileUpload / ColorPicker / Slider |
FileUpload · ColorPicker · Slider |
TagsInput / KeyValue / ToggleButtons |
TagsInput · KeyValue · ToggleButtons |
| Editors | RichEditor · MarkdownEditor · CodeEditor |
OneTimeCodeInput |
OneTimeCodeInput |
| Repeaters | Repeater · Builder · RelationshipRepeater |
ViewField |
ViewField |
| Relation-ish selects | MorphToSelect · TableSelect · ModalTableSelect |
Import them from almasix.orbit.forms.
Layouts inside forms
Section titled “Layouts inside forms”Nest schema layouts — Section, Grid, Tabs, Fieldset, Wizard — right in the schema list. Fields and layouts mix freely.
from almasix.orbit.schemas import Tabs, Grid
form.schema([ Tabs.make("profile").tabs( ("Account", [ Grid.make("row").columns(2).schema([ TextInput.make("first_name").required(), TextInput.make("last_name").required(), ]), ]), ("Security", [ TextInput.make("password").password(), ]), ),])Rendering & state
Section titled “Rendering & state”form.fill({"title": "Hello"})html = form.render(form.get_state())payload = form.dehydrate() # only dehydrated fields with a state pathFields emit wire:model attributes so Conduit can sync them when you mount the form inside a live component.
Need the form without a Resource? → Standalone forms.
Preview
Section titled “Preview”
