# GolemUI — DX (`gui.*`) form-building reference for AI GolemUI forms are **data, not markup**: a form is an array of field items built with the `gui` builder (imported from `@golemui/gui-shared`). There is NO chained/fluent builder (`gui.form().text()...`) and no `gui.actions.submitButton`. Each item is `gui..(...)`. HOW TO USE THIS REFERENCE: read it **once** and write the whole form from it — it is self-sufficient for almost every form, so you should rarely need another fetch. (Each agentic turn re-reads everything you have already fetched, so a single read here is cheaper than many small lookups.) For a widget’s exhaustive options beyond what is here, follow the **Reference:** link printed under that factory below — each is a full absolute URL on https://golemui.com. The complete page index is at https://golemui.com/llms.txt. These docs are the authoritative source — do NOT read the `@golemui` TypeScript declarations in `node_modules` or search the filesystem; everything you need is here or one Reference link away. General: GolemUI builds FORMS — data collection and validation. It is NOT a general-purpose UI toolkit: it never renders documents, page content, or markdown for display. A form is just an array of these items: `export const form = [ /* items */ ];`. RENDER (React) — `import { gui } from '@golemui/gui-shared'; import { GuiForm } from '@golemui/gui-react'; import type { FormSubmitEvent } from '@golemui/core';`, then render ` { /* e.data is the form data */ }} />`. A `gui.displays.display(() =>

)` returns React JSX. Import the component stylesheet ONCE — `@golemui/gui-components/index.css` — or the form renders unstyled. To RECEIVE A SUBMIT: add a `gui.actions.button({ label, actionType: 'submit' })` to the form and listen for the submit on the host component (the RENDER line above shows how for your framework) — the handler gets a `FormSubmitEvent` whose `.data` is the collected form data. To DISABLE submit until the form is valid, add `disabled: { when: '$formIsInvalid' }` to that button (`$formIsInvalid` is a built-in validity flag) — see the conditional-and-state-props pattern. The SAME `formDef` renders in every framework (React/Angular/Vue/Lit/vanilla) — only the host wrapper changes. FORM-LEVEL CONFIG — `formDef` is ALWAYS the bare array. Anything form-wide (named `states`, `validateOn`) goes in a sibling `formConfig` on the config (`config={{ formDef: form, formConfig: { states, validateOn } }}`), NEVER inside `formDef`. Do NOT wrap the array as `{ states, form: [...] }` and pass THAT as `formDef` — `formDef` is typed `Record` so it COMPILES, but the `gui.*` items are never resolved and the form renders BLANK with no error. See the form-level-states pattern. Common fields like `include`/`exclude` (conditional visibility) go INSIDE a factory’s config argument — never spread them onto the result (`{ ...gui.inputs.x(...), include }` compiles but silently does nothing). See the conditional-visibility pattern. STATIC CONTENT — a section heading or any non-input text/block is the HOST’s job, not GolemUI’s: use `gui.displays.display(() =>

)` returning your framework’s own node (React JSX, Vue/Angular/Lit node) — it needs no dependency and always renders. MARKDOWN has exactly ONE use: `gui.inputs.markdown`, an INPUT where the user EDITS markdown (its value is their markdown string). There is NO markdown-for-display widget — never use markdown to render a heading or content; use `display` for that. VALIDATOR `type` — one rule, three cases (so you never have to guess): (1) choice widgets (`dropdown`, `radiogroup`, `select`) REQUIRE an explicit `type`: `validator: { type: 'string', required: true }`. (2) `repeater` (array) validators auto-supply `type: 'array'` — supply only the rules, e.g. `validator: { required: true, minItems: 1 }`, never `type`. (3) everything else (text, number, date) takes the loose validator with NO `type`: `validator: { required: true }`. Every example below is compile-checked against the real `@golemui` type declarations. ## Factory index Every `gui.*` factory and its calling convention. Look up the detail below for the ones you use. - `gui.layouts.accordion(sections)` - `gui.displays.alert({ text })` - `gui.inputs.booleanInput(path, { label, defaultValue? })` - `gui.actions.button({ label, actionType?: 'submit', onClick? })` - `gui.inputs.calendar(path, { label, minDate?, maxDate? })` - `gui.inputs.checkbox(path, { label, defaultValue? })` - `gui.inputs.currency(path, { label, validator? })` - `gui.inputs.dateInput(path, { label, minDate?, maxDate?, validator? })` - `gui.inputs.datePicker(path, { label, minDate?, maxDate?, validator? })` - `gui.displays.display(render)` - `gui.inputs.dropdown(path, { label, items, validator? })` - `gui.layouts.flex(children, props?)` - `gui.layouts.grid(children, props?)` - `gui.layouts.horizontalFlex(children, props?)` - `gui.layouts.horizontalGrid(children, props?)` - `gui.inputs.list(path, { label, items, height?, itemHeight? })` - `gui.inputs.markdown(path, { label })` - `gui.inputs.numberInput(path, { label, defaultValue?, validator? })` - `gui.inputs.password(path, { label, validator? })` - `gui.inputs.radiogroup(path, { label, options, defaultValue?, validator? })` - `gui.inputs.rangeCalendar(path, { label? })` - `gui.inputs.rangeDateInput(path, { label? })` - `gui.inputs.rangeDatePicker(path, { label? })` - `gui.inputs.repeater(path, { label?, addLabel?, removeLabel?, limit?, template })` - `gui.inputs.select(path, { label, options, validator? })` - `gui.layouts.tabs(sections)` - `gui.inputs.tags(path, { label })` - `gui.inputs.textInput(path, { label, placeholder?, defaultValue?, validator? })` - `gui.inputs.textarea(path, { label, placeholder?, validator? })` - `gui.layouts.verticalFlex(children, props?)` - `gui.layouts.verticalGrid(children, props?)` ## gui.layouts.accordion Call: `gui.layouts.accordion(sections)` ```ts gui.layouts.accordion([ { label: 'Billing', children: [ gui.inputs.textInput('card', { label: 'Card' }) ] } ]) ``` - Takes `sections: { label, children, uid? }[]` — same shape as `tabs`, rendered as collapsible panels. Reference: https://golemui.com/dx/widgets-reference/layout-fields/accordion.md ## gui.displays.alert Call: `gui.displays.alert({ text })` ```ts gui.displays.alert({ text: 'Please review your details before submitting.' }) ``` - Static, non-input callout. Uses **`text`** (not `content`). Displays do not take a `path`. Reference: https://golemui.com/dx/widgets-reference/display-fields/alert.md ## gui.inputs.booleanInput Call: `gui.inputs.booleanInput(path, { label, defaultValue? })` ```ts gui.inputs.booleanInput('newsletter', { label: 'Subscribe to newsletter', defaultValue: false }) ``` - The on/off toggle for a single boolean. Use `checkbox` for a checkbox presentation. Reference: https://golemui.com/dx/widgets-reference/input-fields/toggle.md ## gui.actions.button Call: `gui.actions.button({ label, actionType?: 'submit', onClick? })` ```ts gui.actions.button({ label: 'Sign up', actionType: 'submit' }) ``` - Submit button: `gui.actions.button({ label, actionType: 'submit' })`. - There is NO `gui.actions.submitButton` — it was removed. Use `button` with `actionType: 'submit'`. - For a non-submit action use an `onClick: (event) => { /* event.data is the form data */ }` handler. Reference: https://golemui.com/dx/widgets-reference/interactive-fields/button.md ## gui.inputs.calendar Call: `gui.inputs.calendar(path, { label, minDate?, maxDate? })` ```ts gui.inputs.calendar('day', { label: 'Pick a day', minDate: '2025-01-01' }) ``` - An always-visible INLINE calendar (no popover) — use when the calendar should be shown on the page. For a compact single-date field use `datePicker` instead. - `minDate` / `maxDate` (ISO `YYYY-MM-DD` strings) constrain the selectable range — see `datePicker`. Reference: https://golemui.com/dx/widgets-reference/input-fields/calendar.md ## gui.inputs.checkbox Call: `gui.inputs.checkbox(path, { label, defaultValue? })` ```ts gui.inputs.checkbox('terms', { label: 'I accept the terms', defaultValue: false }) ``` - A single boolean rendered as a checkbox. Reference: https://golemui.com/dx/widgets-reference/input-fields/checkbox.md ## gui.inputs.currency Call: `gui.inputs.currency(path, { label, validator? })` ```ts gui.inputs.currency('price', { label: 'Price', validator: { required: true, minimum: 0 } }) ``` - Numeric money input; number-style validator. Reference: https://golemui.com/dx/widgets-reference/input-fields/currency.md ## gui.inputs.dateInput Call: `gui.inputs.dateInput(path, { label, minDate?, maxDate?, validator? })` ```ts gui.inputs.dateInput('startDate', { label: 'Start date', validator: { required: true } }) ``` - Typed date entry, NO calendar UI — use only when keyboard-first entry is wanted. For most dates use `datePicker` (popover calendar) instead. Accepts the loose `{ required: true }`. - `minDate` / `maxDate` (ISO `YYYY-MM-DD` strings) constrain the accepted range — see `datePicker`. Reference: https://golemui.com/dx/widgets-reference/input-fields/dateinput.md ## gui.inputs.datePicker Call: `gui.inputs.datePicker(path, { label, minDate?, maxDate?, validator? })` ```ts gui.inputs.datePicker('startDate', { label: 'Coverage start', minDate: '2025-01-01', validator: { required: true } }) ``` - THE DEFAULT single-date field: a text field with a popover calendar (click to open). Prefer this for most dates. (`calendar` = always-visible inline calendar; `dateInput` = typed entry, no calendar UI.) Accepts the loose `{ required: true }` validator. - Bound the selectable range with **`minDate`** / **`maxDate`** — ISO `YYYY-MM-DD` strings. For "today or later" set `minDate` to today’s date; for "not in the future" set `maxDate` to today. Same `minDate`/`maxDate` on `calendar`, `dateInput`, and the range date widgets. Reference: https://golemui.com/dx/widgets-reference/input-fields/date-picker.md ## gui.displays.display Call: `gui.displays.display(render)` ```ts gui.displays.display(() => 'Order summary') ``` - **The go-to for a static heading or any standalone/formatted block.** Return your framework’s own content from the render function — React: `gui.displays.display(() =>

Member enrollment

)`; Vue/Angular/Lit: return that framework’s node. It renders immediately with **zero registration and no parser dependency** — this is how you put a heading or any static block in a form (GolemUI itself never renders content for display). - Pass the render function **directly** (not wrapped in an object) — `(params) => any`. `params.$form` is the live form data, so content can be dynamic — but a field is **absent until filled**, so guard before indexing: `Array.isArray(params.$form.items) ? params.$form.items : []`, never `params.$form.items.length` raw. Reference: https://golemui.com/dx/widgets-reference/display-fields/renderer.md ## gui.inputs.dropdown Call: `gui.inputs.dropdown(path, { label, items, validator? })` ```ts gui.inputs.dropdown('country', { label: 'Country', items: [{ value: 'us', label: 'United States' }, { value: 'ca', label: 'Canada' }], validator: { type: 'string', required: true } }) ``` - Choice list uses **`items`** (`{ value, label }[]`). - WART: choice widgets (`dropdown`, `radiogroup`, `select`) need a **typed** validator — `{ type: 'string', required: true }` — unlike text inputs which accept the loose `{ required: true }`. Add the `type`, or omit the validator. Reference: https://golemui.com/dx/widgets-reference/input-fields/dropdown.md ## gui.layouts.flex Call: `gui.layouts.flex(children, props?)` ```ts gui.layouts.flex([ gui.inputs.textInput('firstName', { label: 'First name' }), gui.inputs.textInput('lastName', { label: 'Last name' }) ]) ``` - Layouts take the **children array first**, then optional props — unlike inputs (path first). - Direction-locked variants: `verticalFlex`, `horizontalFlex` (and `grid` / `verticalGrid` / `horizontalGrid`). Reference: https://golemui.com/dx/widgets-reference/layout-fields/flex.md ## gui.layouts.grid Call: `gui.layouts.grid(children, props?)` ```ts gui.layouts.grid([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ]) ``` - Grid layout; `horizontalGrid` / `verticalGrid` lock the direction. Reference: https://golemui.com/dx/widgets-reference/layout-fields/grid.md ## gui.layouts.horizontalFlex Call: `gui.layouts.horizontalFlex(children, props?)` ```ts gui.layouts.horizontalFlex([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ]) ``` - A `flex` with direction fixed to horizontal. Reference: https://golemui.com/dx/widgets-reference/layout-fields/flex.md ## gui.layouts.horizontalGrid Call: `gui.layouts.horizontalGrid(children, props?)` ```ts gui.layouts.horizontalGrid([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ]) ``` - A `grid` with direction fixed to horizontal. Reference: https://golemui.com/dx/widgets-reference/layout-fields/grid.md ## gui.inputs.list Call: `gui.inputs.list(path, { label, items, height?, itemHeight? })` ```ts gui.inputs.list('selection', { label: 'Pick an option', items: ['Option 1', 'Option 2', 'Option 3'], height: 200, itemHeight: 40 }) ``` - A scrolling selection list. `items` is a string array (or `{ value, label }[]`). - `height` / `itemHeight` size the scroll viewport (pixels). Reference: https://golemui.com/dx/widgets-reference/input-fields/list.md ## gui.inputs.markdown Call: `gui.inputs.markdown(path, { label })` ```ts gui.inputs.markdown('notes', { label: 'Notes (markdown)' }) ``` - A markdown *editor input* — the user types markdown and the value IS that markdown string. This is the ONLY use of markdown in GolemUI: there is no markdown-for-display. For a heading or static block, use `gui.displays.display(() => )` (your host renders it), never a markdown widget. Reference: https://golemui.com/dx/widgets-reference/input-fields/markdown.md ## gui.inputs.numberInput Call: `gui.inputs.numberInput(path, { label, defaultValue?, validator? })` ```ts gui.inputs.numberInput('age', { label: 'Age', validator: { required: true, minimum: 0, maximum: 120 } }) ``` - Number validator: `{ required, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf }`. Reference: https://golemui.com/dx/widgets-reference/input-fields/number.md ## gui.inputs.password Call: `gui.inputs.password(path, { label, validator? })` ```ts gui.inputs.password('password', { label: 'Password', validator: { required: true, minLength: 8 } }) ``` - Masked text input; loose string validator. Reference: https://golemui.com/dx/widgets-reference/input-fields/password.md ## gui.inputs.radiogroup Call: `gui.inputs.radiogroup(path, { label, options, defaultValue?, validator? })` ```ts gui.inputs.radiogroup('accountType', { label: 'Account type', defaultValue: 'personal', options: [{ value: 'personal', label: 'Personal' }, { value: 'business', label: 'Business' }] }) ``` - Radio group uses **`options`** (`{ value, label }[]`) — note the asymmetry: `dropdown` uses `items`, `radiogroup` uses `options`. - If required, use a typed validator `{ type: 'string', required: true }` (same wart as `dropdown`). Reference: https://golemui.com/dx/widgets-reference/input-fields/radiogroup.md ## gui.inputs.rangeCalendar Call: `gui.inputs.rangeCalendar(path, { label? })` ```ts gui.inputs.rangeCalendar('stayDates', { label: 'Stay dates' }) ``` - Inline calendar for a start–end date **range** (the value is a date range). For a single date use `calendar`. Reference: https://golemui.com/dx/widgets-reference/input-fields/range-calendar.md ## gui.inputs.rangeDateInput Call: `gui.inputs.rangeDateInput(path, { label? })` ```ts gui.inputs.rangeDateInput('stayDates', { label: 'Stay dates' }) ``` - Typed start–end date **range** entry (the range sibling of `dateInput`). Reference: https://golemui.com/dx/widgets-reference/input-fields/range-date-input.md ## gui.inputs.rangeDatePicker Call: `gui.inputs.rangeDatePicker(path, { label? })` ```ts gui.inputs.rangeDatePicker('stayDates', { label: 'Stay dates' }) ``` - Popover calendar for a start–end date **range** (the range sibling of `datePicker`). Reference: https://golemui.com/dx/widgets-reference/input-fields/range-date-picker.md ## gui.inputs.repeater Call: `gui.inputs.repeater(path, { label?, addLabel?, removeLabel?, limit?, template })` ```ts gui.inputs.repeater('attendees', { label: 'Attendees', addLabel: 'Add attendee', template: [ gui.inputs.textInput('attendees.items.name', { label: 'Name' }) ] }) ``` - The variable-length array field: the user adds/removes rows at runtime, each rendering the `template`. This is the idiomatic way to collect an UNKNOWN number of items — do NOT hand-roll N pre-generated copies gated by `include.when` (a common but wrong workaround). - Child field paths inside the template are **`.items.`** — a repeater on `'attendees'` holds `gui.inputs.textInput('attendees.items.name', …)`. At RUNTIME each `items` token is replaced by the row's array index (`attendees.items.name` → `attendees[0].name`, `attendees[1].name`, …); nesting a repeater inside a template adds another `.items.` segment (`teams.items.members.items.name`). - `limit` caps the number of rows; `addLabel`/`removeLabel` set the button text. Reference: https://golemui.com/dx/widgets-reference/input-fields/repeater.md ## gui.inputs.select Call: `gui.inputs.select(path, { label, options, validator? })` ```ts gui.inputs.select('plan', { label: 'Plan', options: [{ value: 'free', label: 'Free' }, { value: 'pro', label: 'Pro' }], validator: { type: 'string', required: true } }) ``` - Choice widget that uses **`options`** (like `radiogroup`) — NOT `items` (which `dropdown` uses). - Same validator wart as the other choice widgets: if required, use a typed validator `{ type: 'string', required: true }`. Reference: https://golemui.com/dx/widgets-reference/input-fields/select.md ## gui.layouts.tabs Call: `gui.layouts.tabs(sections)` ```ts gui.layouts.tabs([ { label: 'Account', children: [ gui.inputs.textInput('email', { label: 'Email' }) ] }, { label: 'Profile', children: [ gui.inputs.textInput('name', { label: 'Name' }) ] } ]) ``` - Takes `sections: { label, children, uid? }[]` — each section is a tab with its own children. Reference: https://golemui.com/dx/widgets-reference/layout-fields/tabs.md ## gui.inputs.tags Call: `gui.inputs.tags(path, { label })` ```ts gui.inputs.tags('skills', { label: 'Skills' }) ``` - Free-form multi-value tag input; the value is a string array. Reference: https://golemui.com/dx/widgets-reference/input-fields/tags.md ## gui.inputs.textInput Call: `gui.inputs.textInput(path, { label, placeholder?, defaultValue?, validator? })` ```ts gui.inputs.textInput('fullName', { label: 'Full name', validator: { required: true, minLength: 2 } }) ``` - Text fields accept a loose validator: `{ required, minLength, maxLength, pattern, format }` (no `type` needed). - Email is a text input with `validator: { required: true, format: 'email' }` — use `format`, not a regex. The full `format` enum: `email`, `url`, `uuid`, `hostname`, `ipv4`, `ipv6`, `date`, `time`, `date-time`, `duration`. Reference: https://golemui.com/dx/widgets-reference/input-fields/textinput.md ## gui.inputs.textarea Call: `gui.inputs.textarea(path, { label, placeholder?, validator? })` ```ts gui.inputs.textarea('bio', { label: 'Bio', validator: { maxLength: 500 } }) ``` - Multi-line text; same loose string validator as `textInput`. Reference: https://golemui.com/dx/widgets-reference/input-fields/textarea.md ## gui.layouts.verticalFlex Call: `gui.layouts.verticalFlex(children, props?)` ```ts gui.layouts.verticalFlex([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ]) ``` - A `flex` with direction fixed to vertical. Reference: https://golemui.com/dx/widgets-reference/layout-fields/flex.md ## gui.layouts.verticalGrid Call: `gui.layouts.verticalGrid(children, props?)` ```ts gui.layouts.verticalGrid([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ]) ``` - A `grid` with direction fixed to vertical. Reference: https://golemui.com/dx/widgets-reference/layout-fields/grid.md # Patterns Cross-cutting patterns that apply to every factory (not a factory themselves). ## Show or hide a field conditionally (hide-when) ```ts gui.inputs.textInput('promoCode', { label: 'Promo code', include: { when: '$form.hasPromoCode === true' } }) ``` - `include` and `exclude` are common config fields on EVERY `gui.*` item — pass them inside the factory's config argument (the same object as `label`): `gui.inputs.textInput('promoCode', { label, include: { when: '$form.hasPromoCode === true' } })`. `include` shows the field only while the expression is true; `exclude` hides it while true. - NEVER attach them by spreading the factory result: `{ ...gui.inputs.textInput('promoCode', { label }), include: { when } }` COMPILES (TypeScript does not flag it) but is a silent no-op — the field renders unconditionally. The `include`/`exclude` must be a key of the config object, not a sibling of the spread. - The `when` value is a `ReactiveExpression` (a plain string) that reads form state via `$form.` and uses strict equality, e.g. `'$form.hasPromoCode === true'`. When the SAME condition gates two or more fields, define a named state and use `include: { in: ['stateName'] }` / `exclude: { from: [...] }` instead of an inline `when`. Named states are declared in `formConfig.states` — see the form-level-states pattern. ## Declare named form-level states (and gate fields by them) ```ts gui.inputs.textInput('spouseName', { label: 'Spouse name', include: { in: ['familyCoverage'] } }) ``` - Named states are form-level: declare them in `formConfig.states`, a sibling of `formDef` on the `` config — NOT a wrapper around the array. `formConfig.states` maps each state name to a `ReactiveExpression` string, e.g. `{ familyCoverage: '$form.coverageType === \'family\'' }`. Then any item references it by name: `include: { in: ['familyCoverage'] }` (show while true) / `exclude: { from: ['familyCoverage'] }` (hide while true). - Full shape: ``. The `formDef` stays the bare `gui.*` array. - NEVER pass `{ states, form: [...] }` as `formDef`. It type-checks (`formDef` is `Record`) but that object is not recognized as a `gui.*` bundle, so the items are never resolved — the form renders BLANK with no console error. The `{ states, form }` shape only exists for hand-written core/JSON widgets, not the `gui.*` facade. ## Conditional & state-driven props (enable/disable, show/hide, readonly) ```ts gui.actions.button({ label: 'Submit', actionType: 'submit', disabled: { when: '$formIsInvalid' } }) ``` - ENABLE/DISABLE & READONLY: `disabled` and `readonly` are typed `boolean | { when: }`. To gate the submit button on validity: `gui.actions.button({ label, actionType: 'submit', disabled: { when: '$formIsInvalid' } })`. `$formIsInvalid` is a built-in validity flag — do not declare it as a state. - SHOW/HIDE: `include` / `exclude` are typed `{ in: ['stateName'] }` / `{ from: ['stateName'] }` (state lists) or `{ when: }`. Every state name in `in`/`from` MUST be declared in `formConfig.states`; an undeclared name leaves the widget hidden forever (the engine logs an error to the console). - These are ALL typed config keys — pass them inside the factory’s config argument. NEVER reach a prop by casting the factory result and assigning a key (`(btn as any)['disabled.formValid'] = false`) or by spreading (`{ ...gui.actions.button(...), disabled }`): keys added that way are SILENTLY ignored and the behavior never fires. If a prop is not on the typed config, you are guessing — it is not a real field.