Skip to content

Sensible defaults

The DX layer applies a small set of sensible defaults to keep your form definitions concise:

DefaultBehavior
Auto-labelIf you don’t set label, the engine derives one from path (e.g. firstNameFirst Name).
Auto-placeholderIf you don’t set placeholder, the engine uses path as a placeholder.
Auto-stackIf your formDef has multiple top-level items and no top-level layout, the engine wraps them in a vertical flex.

Every input shortcut that takes a path (gui.inputs.textInput, numberInput, booleanInput, password, currency, textarea, markdown, checkbox, radiogroup, select, dropdown, list, calendar, dateInput, datePicker, rangeCalendar, rangeDateInput, rangeDatePicker, custom) runs its path through a single transform when no label is set:

  1. Split before each uppercase letter — firstNamefirst Name.
  2. Replace runs of _ or - with a space — first_name, first-namefirst name.
  3. Collapse and trim whitespace.
  4. Title-case each word.

The transform doesn’t touch dots in the path, so a nested path keeps its dot in the derived label:

pathDerived label
nameName
firstNameFirst Name
first_nameFirst Name
first-nameFirst Name
user.firstNameUser.First Name
a.b.cA.B.C

If you want only the leaf segment as the label, set it explicitly: gui.inputs.textInput('user.firstName', { label: 'First name' }).

For each input, the engine derives a label only when all of these are true:

  • label was not passed (it’s undefined or null).
  • suppressAutomaticLabels is not on for that input (via selector).
  • path is non-empty.

Anything else — including label: '' — is kept as-is.

Pass label on the call site. It always wins over the derivation:

gui.inputs.textInput('firstName', { label: 'Given name' }); // → 'Given name'

You can also use a Localizable for i18n: label: { key: 'user.firstName.label', default: 'First name' }.

Pass an empty-string label. The check looks for label != null, so '' counts as “you set it”:

gui.inputs.textInput('firstName', { label: '' }); // → '' (no label rendered)

Use a selector. Scope it as broadly or narrowly as you need:

const formSelectors = [
// Turn off auto-labels on every input.
gui.selectors.inputs({ suppressAutomaticLabels: true }),
// Re-enable only for inputs tagged 'identity'.
gui.selectors.tag('identity').inputs({ suppressAutomaticLabels: false }),
];

Same precedence rules as every other selector apply — see Precedence.

Auto-label is a DX-layer feature. Raw JSON form definitions go through a different path: the label you set in JSON is the label rendered, and an absent label stays absent.

Same shape as auto-label, but the value used is the raw path (no transform). The same path != null rules apply, with suppressAutomaticPlaceholders to disable.

If your formDef has multiple top-level items and no wrapping layout, the engine wraps them in a vertical flex layout so the form renders as a single column. Wrap them yourself (e.g. gui.layouts.flex(..., { direction: 'row' })) to opt out, or set suppressAutomaticStack: true in formConfig for the whole form.

const formDef = [
gui.layouts.flex(
[
gui.inputs.textInput('username', undefined, ['identity']),
gui.inputs.textInput('firstName'),
gui.inputs.textInput('lastName'),
],
{ direction: 'column', gap: 12 },
),
];
const formSelectors = [
// suppress auto-labels and auto-placeholders on every input
gui.selectors.inputs({
suppressAutomaticLabels: true,
suppressAutomaticPlaceholders: true,
}),
// override that suppression for inputs tagged 'identity'
gui.selectors.tag('identity').inputs({
suppressAutomaticPlaceholders: false,
}),
];

What you should see in the demo:

  • No labels on any of the three inputs — suppressAutomaticLabels is on for every input.
  • No placeholder on firstName or lastNamesuppressAutomaticPlaceholders is on for every input.
  • A username placeholder on the first field — the tag-scoped selector re-enables auto-placeholders for inputs tagged 'identity', beating the broader rule by precedence.