Layout
Without a layout, a form shows its fields one after another, in the order of fields. A layout puts them in sections, rows of a 12-column grid and steps, and leaves slots for content of your app. Below you will learn how to write one.
Adding a layout
layout goes next to fields. It holds items, for a form on one page, or steps, for a form over several pages. Here, a delivery section and a summary sit side by side:
{
"version": "1",
"fields": [
{ "name": "name", "type": "text", "label": "Name", "required": true },
{
"name": "email",
"type": "email",
"label": "Email",
"required": true
},
{
"name": "address",
"type": "group",
"label": "Address",
"fields": [
{
"name": "street",
"type": "text",
"label": "Street",
"required": true
},
{
"name": "city",
"type": "text",
"label": "City",
"required": true
},
{ "name": "zip", "type": "text", "label": "ZIP code" }
]
},
{ "name": "coupon", "type": "text", "label": "Coupon" },
{
"name": "acceptTerms",
"type": "checkbox",
"label": "I accept the terms",
"required": true
}
],
"layout": {
"items": [
{
"type": "row",
"items": [
{
"type": "section",
"id": "delivery",
"label": "Delivery",
"span": 8,
"items": [
{ "type": "row", "items": ["name", "email"] },
{
"field": "address",
"items": [
"street",
{
"type": "row",
"items": [{ "field": "city", "span": 8 }, "zip"]
}
]
}
]
},
{
"type": "section",
"id": "summary",
"label": "Summary",
"items": ["*", { "type": "slot", "name": "submit" }]
}
]
}
]
}
}A layout never changes the value. fields alone decides its shape, its validation and the output, so a definition submits the same with any layout.
Items
A list of items can hold:
| Item | Written as | What it is |
|---|---|---|
| A field | "email", "address.city" | a field, by its dot path from the fields around it |
| A field | { "field": "address", "items": [...] } | a field with options: items lays out the fields of a group or of each item of a list, and span |
| The rest | "*" | every field placed nowhere else, in the order of fields |
| A section | { "type": "section", "items": [...] } | a block with an optional label and description, like a fieldset |
| A row | { "type": "row", "items": [...] } | one line of a 12-column grid |
| A slot | { "type": "slot", "name": "submit" } | where your app puts its own content, like a submit button |
| Text | { "type": "paragraph", "text": "..." } | a display element, written as in fields, with id instead of name |
Lists of items are never empty. Sections, rows and display elements can have an id, unique across the layout, and sections and rows can have a ui for your renderer, like { "variant": "card" }.
Placing every field
Every field is placed once:
- Placing a group or a list places every field inside it. With
items, you lay out its fields yourself, by their paths inside it:"street"within{ "field": "address" }. "*"places the rest, once per list of fields: the form's, across all steps, and each group's or list'sitems.- A field placed nowhere is an issue, so a required field can't vanish and leave a form nobody can submit. A field placed twice is an issue too.
- Paths go through groups, so
"address.city"places the city on its own, even on another step than the rest of its group. The value still nests it. A field of a list's items can't be placed on its own, as it has no index outside its item: place the list, and lay out its items withitems.
Rows
A row is one line of a 12-column grid. Each item of a row takes span columns, from 1 to 12. Items without a span share the columns the others leave equally, so ["name", "email"] is 6 and 6, and [{ "field": "city", "span": 8 }, "zip"] is 8 and 4.
- The spans add up to 12 at most, and the shares must come out whole.
- A row never wraps. A second line is a second row.
spanis for the items of a row only, and"*"can't be a cell. A section in a row can hold it.
The spans are for a wide screen. The layout says nothing about narrow ones: your renderer decides, like giving each cell the whole row below a width of its choice.
Slots
A slot is a place for content of your app that isn't a field, like a submit button, a price summary or a captcha. The renderer fills it by its name: with a named slot in Vue, or with slots in React. See layout components.
Text in the layout
Display elements can be written straight into the layout, where they go:
{
"version": "1",
"fields": [{ "name": "email", "type": "email", "required": true }],
"layout": {
"items": [
{ "type": "heading", "id": "intro", "text": "Stay in touch" },
"email"
]
}
}Display elements in fields are placed like other fields, and "*" places them too. section, row and slot are the layout's own types, so a field type of yours with one of those names can't be a display element in the layout.
Next steps
A layout with steps splits the form into pages. Continue to steps to learn how they validate.