Rent a car: States and conditional rendering
The form from the previous chapter renders every field unconditionally. We’ll now:
- Declare two states —
differentReturnandhasDiscount— that mirror the matching toggles. - Gate the optional fields with
include: { in: [...] }.
Validators come next — they’re covered end-to-end in the Validation errors chapter.
Declaring states
Section titled “Declaring states”States are named boolean expressions evaluated against $form. Where they live depends on which API you picked:
In the programmatic API, states live under formConfig.states — a top-level property of the same config object you wired to the form component in the previous chapter, right next to formDef:
const config = { formDef: rentACarForm, formConfig: { states: { differentReturn: '$form.differentReturn === true', hasDiscount: '$form.hasDiscountCode === true', }, },};export class RentACarPage { protected config = { formDef: rentACarForm, formConfig: { states: { differentReturn: '$form.differentReturn === true', hasDiscount: '$form.hasDiscountCode === true', }, }, };}export class RentACarPage extends LitElement { config = { formDef: rentACarForm, formConfig: { states: { differentReturn: '$form.differentReturn === true', hasDiscount: '$form.hasDiscountCode === true', }, }, }; // …rest of the class unchanged…}<script setup lang="ts">const config = { formDef: rentACarForm, formConfig: { states: { differentReturn: '$form.differentReturn === true', hasDiscount: '$form.hasDiscountCode === true', }, },};</script>const config = { formDef: rentACarForm, formConfig: { states: { differentReturn: '$form.differentReturn === true', hasDiscount: '$form.hasDiscountCode === true', }, },};In the JSON API, states live at the form file’s root, alongside "form":
{ "states": { "differentReturn": "$form.differentReturn === true", "hasDiscount": "$form.hasDiscountCode === true" }, "form": [ // …widgets… ]}The page component stays exactly as it was in the intro chapter — config just carries formDef:
const config = { formDef: rentACarForm };export class RentACarPage { protected config = { formDef: rentACarForm };}export class RentACarPage extends LitElement { config = { formDef: rentACarForm }; // …rest of the class unchanged…}<script setup lang="ts">const config = { formDef: rentACarForm };</script>const config = { formDef: rentACarForm };Gating fields with include
Section titled “Gating fields with include”Add include: { in: ['<stateName>'] } to each optional widget — it will only render while the named state is active. In our form, that’s two widgets: the return-office dropdown (gated by differentReturn) and the discount-code input (gated by hasDiscount).
gui.inputs.dropdown('returnOffice', { label: 'Return to office', include: { in: ['differentReturn'] }, // …items, validator…});
gui.inputs.textInput('discountCode', { label: 'Discount code', include: { in: ['hasDiscount'] }, validator: { required: true, minLength: 4 },});{ "kind": "input", "type": "dropdown", "path": "returnOffice", "label": "Return to office", "include": { "in": ["differentReturn"] }}
{ "kind": "input", "type": "textinput", "path": "discountCode", "label": "Discount code", "include": { "in": ["hasDiscount"] }, "validator": { "required": true, "minLength": 4 }}The toggles that drive the states (differentReturn and hasDiscountCode) stay in the form unconditionally — they’re what the user interacts with to flip the state on or off.
Result
Section titled “Result”import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.dropdown('car', { labelField: 'label', valueField: 'id', items: [ { id: 'compact', label: 'Compact', }, { id: 'suv', label: 'SUV', }, { id: 'convertible', label: 'Convertible', }, { id: 'luxury', label: 'Luxury', }, ], label: 'Select car', validator: { type: 'string', required: true, }, }), gui.layouts.grid([ gui.inputs.dropdown('collectOffice', { labelField: 'label', valueField: 'id', items: [ { id: 'lhr', label: 'London Heathrow', }, { id: 'cdg', label: 'Paris CDG', }, { id: 'fra', label: 'Frankfurt Main', }, ], label: 'Collect from office', validator: { type: 'string', required: true, }, }), gui.inputs.dropdown('returnOffice', { labelField: 'label', valueField: 'id', items: [ { id: 'lhr', label: 'London Heathrow', }, { id: 'cdg', label: 'Paris CDG', }, { id: 'fra', label: 'Frankfurt Main', }, ], label: 'Return to office', validator: { type: 'string', required: true, }, include: { in: ['differentReturn'], }, }), ], { direction: 'row', autoFit: true, }), gui.inputs.booleanInput('differentReturn', { label: 'Choose a different return location', }), gui.inputs.rangeCalendar('rentalDates', { numberOfMonths: 2, label: 'Rental dates', validator: { required: true, minItems: 1, maxItems: 1, }, }), gui.inputs.radiogroup('rentalType', { options: [ { label: 'Daily', value: 'daily', }, { label: 'Weekly', value: 'weekly', }, { label: 'Monthly', value: 'monthly', }, ], label: 'Rental type', validator: { type: 'string', required: true, }, }), gui.inputs.booleanInput('driverOver25', { label: 'Driver aged over 25', validator: { const: true, required: true, }, }), gui.inputs.booleanInput('hasDiscountCode', { label: 'I have a discount code', }), gui.inputs.textInput('discountCode', { label: 'Discount code', validator: { required: true, minLength: 4, }, include: { in: ['hasDiscount'], }, }), gui.actions.button({ label: 'Reserve', actionType: 'submit', }),];{ "states": { "differentReturn": "$form.differentReturn === true", "hasDiscount": "$form.hasDiscountCode === true" }, "form": [ { "kind": "input", "type": "dropdown", "path": "car", "label": "Select car", "validator": { "type": "string", "required": true }, "props": { "labelField": "label", "valueField": "id", "items": [ { "id": "compact", "label": "Compact" }, { "id": "suv", "label": "SUV" }, { "id": "convertible", "label": "Convertible" }, { "id": "luxury", "label": "Luxury" } ] } }, { "kind": "layout", "type": "grid", "props": { "direction": "row", "autoFit": true }, "children": [ { "kind": "input", "type": "dropdown", "path": "collectOffice", "label": "Collect from office", "validator": { "type": "string", "required": true }, "props": { "labelField": "label", "valueField": "id", "items": [ { "id": "lhr", "label": "London Heathrow" }, { "id": "cdg", "label": "Paris CDG" }, { "id": "fra", "label": "Frankfurt Main" } ] } }, { "kind": "input", "type": "dropdown", "path": "returnOffice", "label": "Return to office", "include": { "in": ["differentReturn"] }, "validator": { "type": "string", "required": true }, "props": { "labelField": "label", "valueField": "id", "items": [ { "id": "lhr", "label": "London Heathrow" }, { "id": "cdg", "label": "Paris CDG" }, { "id": "fra", "label": "Frankfurt Main" } ] } } ] }, { "kind": "input", "type": "toggle", "path": "differentReturn", "label": "Choose a different return location" }, { "kind": "input", "type": "rangeCalendar", "path": "rentalDates", "label": "Rental dates", "props": { "numberOfMonths": 2 }, "validator": { "type": "array", "required": true, "minItems": 1, "maxItems": 1 } }, { "kind": "input", "type": "radiogroup", "path": "rentalType", "label": "Rental type", "validator": { "type": "string", "required": true }, "props": { "options": [ { "label": "Daily", "value": "daily" }, { "label": "Weekly", "value": "weekly" }, { "label": "Monthly", "value": "monthly" } ] } }, { "kind": "input", "type": "toggle", "path": "driverOver25", "label": "Driver aged over 25", "validator": { "type": "boolean", "const": true, "required": true } }, { "kind": "input", "type": "toggle", "path": "hasDiscountCode", "label": "I have a discount code" }, { "kind": "input", "type": "textinput", "path": "discountCode", "label": "Discount code", "include": { "in": ["hasDiscount"] }, "validator": { "type": "string", "required": true, "minLength": 4 } }, { "kind": "action", "type": "button", "label": "Reserve", "actionType": "submit" } ]}- The Return to office dropdown only appears when Choose a different return location is on.
- The Discount code input only appears when I have a discount code is on.
The embedded snippet already includes the validator on every required field — those are introduced in the next chapter.
See also
Section titled “See also”- Features / States — gating, properties, composing.
- Form Definition / States —
include/exclude/whenreference.