# Choosing between element types that both validate

*Open when more than one of the 58 types could carry what the user asked for, or before authoring `date`, `code`, `buttonList`, `chips`, `comments`, `history`, `approvals`, `checklist`, or a decomposed workspace.*

**`textarea` / `richText` / `html`** — plain multi-line notes → `textarea`; formatted text the *user* edits → `richText` (value is an HTML string, bindable only to a string field); markup *you* author → `html`. `html` interpolates `{{path}}` tokens but is never a home for record data — bind an element instead. `richText.collapseAfterLines` is inert once the element has a definite height (`heightMode` `fixed`/`fill`, or any pinned height): pin the height or collapse, never both.

**`date` / `datePicker`** — `date` is a field row; `datePicker` is the standalone always-open picker card and the only one with `range: true`. `options.minDate`/`options.maxDate` are the only way to author a real window, and both are **inert on `datePicker`** (so are `readTemplate`, `readIcon`, `readColor`). A `datePicker`'s bounds come from `disablePast`/`disableFuture` alone, which mean "anything up to now" and "every future date forever" — not a window. "Only the next 30 days" is a `date` with `maxDate`. On `date`, an unparseable bound yields *no* bound rather than an error, and inverted bounds (min > max, easy when both come from `{{…}}`) drop both. With `range: true`, set `startFieldPath` **and** `endFieldPath` or neither — start-only saves the start and discards the end — and never give `datePicker` a `dataSource.fieldPath`.

**`select` / `radio` / `buttonList` / `chips`** — bounded list → `select`; a few always-visible choices → `radio`; freeform tags → `chips`. `buttonList` is a segmented **selection field** whose value is the picked id(s); a row of actions is `button`s in a `flexContainer`. `chips` binds only to a LIST-valued relation (many-to-many or one-to-many backref), only `dataSource.kind: 'entity'` is live, and `allowPickExisting` defaults **off** at runtime — without `allowPickExisting: true` users can create tags but never pick one. The option list has a different key per type: `dataSource.options` (select), `options` (radio), `items` (buttonList). `color` on a radio option or buttonList item is accepted and rendered by nothing; `select` is the one choice type that paints per-option colour.

**`image` / `files` / `avatar`** — `image` renders an `<img>` and offers the end user **no control to fill it**; a picture the user supplies is `files` or `avatar`. On a bound `files`, state `multiple` explicitly to match the column: an absent `multiple` reads as `false`, wrong for a multi-file column, and the FE locks that checkbox, so it cannot be fixed without re-picking the field. `avatar` and `signature` attach their upload by the element's own `name`, not by `dataSource.fieldPath` — bound to `Users.photo` the element must be *named* `photo`, or the attach is rejected with a bare "upload failed".

**`entityList` / `table` / `entityToolbar`+`entityLayouts`** — a record list with view switching → `entityList`; per-column config, inline editing or grouping → `table`. The decomposed pair is for a composed workspace only: two strips over a separate `table`/`kanban`/`cards`. They hold no value, read the entity from base `dataSource.entity` falling back to the form's own, and talk to the data widget through **ports, never a prop** — never invent a wiring key. Wire them in the draft's top-level `connections`, element NAMES on both ends (port names are in each type's prop table):

```json
"connections": [{ "id": "c1",
  "from": { "element": "ordersToolbar", "output": "search" },
  "to":   { "element": "ordersTable",   "input":  "search" } }]
```

Unwired, the search box filters nothing and a view tab switches nothing. Omitting the key leaves existing wires alone; `[]` deletes them all.

**`code`** — a Monaco **field** holding a bound value (a JSON/config editor on the form), not form scripting. `language` defaults to `'json'` and a json-contract value is the *parsed* structure: bound to a TEXT/varchar column you must add `valueFormat: 'text'` (or a non-parsable `language` like `plaintext`/`markdown`/`sql`), or existing prose renders as a quoted JSON literal and the first non-JSON edit raises a field error that blocks the whole form from saving. `defaultValue` obeys the same contract.

## Four types that need an entity option on

`comments`, `history`, `approvals`, `checklist` read backend-generated per-entity tables (`<Entity>Comments`, `<Entity>Approves`, `<Entity>Checklists`) that exist only when the entity's `comments` / `trackChangeHistory` / `approvals` / `checklists` option is on — **all four default to off**. Off: `comments` shows a permanently empty feed, its failed request swallowed; `history` and `checklist` are empty; `approvals` fires a red "request failed" toast on *every* open of the record. The builder canvas shows a live-looking preview in all four cases. Creating the entity in this plan → set the flag in its `options`; existing entity → say plainly it must be enabled in the Entity Builder first. All four also need a *saved* record: on a create form they are inert, not merely empty.

## The automatic submit key

On a form with no bound entity the platform mints `<jsonRoot>.<elementName>` for exactly 14 types: `input`, `textarea`, `richText`, `checkbox`, `toggle`, `radio`, `select`, `chips`, `date`, `datePicker`, `files`, `signature`, `icon`, `avatar`.

Four types hold a value and get **no** automatic key: `table` (its edited rows), `buttonList` (the selected ids), `code` (its bound value), `groupedSelect` (writes the variables its `sources` name). Each needs its own `dataSource.fieldPath` rooted at the form's default json variable — `'submitData.config'` — or its value reaches no payload: the submission arrives without that key, silently. `isSubmitData: true` with no path collects nothing either.

## A type you have not met

`suppa form-kb --mode components` lists all 58 with their purpose; `--mode api --types <type> --types <type>` gives the prop tables (a list flag repeats — it takes no JSON array), `--mode note --topic <type>` the behaviour note. Never assemble something out of `input` + `html` because no type came to mind.
