Components · Forms

Advanced fields

Three composite patterns the plain field primitives can't express on their own — all built by wiring Field / Input / Select / Button and the DS Radio together: a repeatable field array (add & remove rows), a conditional field that mounts a revealed block off a watched value, and an inline-editable field that swaps between a read-only view and a draft editor.

field array · repeatable rows · append / removeAt
Partner
Vet on file
2 of 5 contacts · at least 1 required

Foster onboarding — Marisol Vega. Each row is one array item keyed by a stable id; the trash control calls removeAt(i), Add another contact appends a blank row. The last row's Mobile sits focused. The array enforces its own min (1) / max (5) — the add button withdraws at the cap.

conditional field · revealed vs collapsed (mounts on the watched value)
watched = "yes" → block revealed
Can you foster at home?
revealed
Sizes you can take

Choosing Yes mounts the dependent block (a bordered accent-ruled group). Its fields only validate & submit while it's mounted.

watched = "no" → block unmounted
Can you foster at home?
// reveal unmounted
foster-capacity fields are removed from the DOM and dropped from the form state — nothing hidden-but-submitted.

Switching back to No tears the block down and clears its values, so a stale “2 dogs” never rides along on submit.

inline-editable field · view ⇄ edit · draft + Save / Cancel
view state — read-only + Edit
Display name
Marisol Vega
Pronouns
Not set

At rest the field shows its value and a subtle Edit ghost trigger; an unset value reads Not set and the trigger becomes Add.

edit state — draft input + Save / Cancel
editing — display name

Clicking Edit swaps the value for a focused draft input (its own local state). Save commits the draft to the field; Cancel discards it and returns to the view state unchanged.

field array — value
A T[] of row values, each keyed by a stable id (not the index). append(blank) adds a row; removeAt(i) drops one; row inputs bind to items[i].field.
min / max rows
min keeps at least N rows (the last remove control disables at the floor); max caps the count — the Add button is withdrawn once the array is full. A live n/max count is announced.
conditional — watch
A dependent block is gated on a watched value (here the Radio answer). It is mounted/unmounted, not hidden — so hidden fields never validate or submit, and unmounting clears their state.
reveal default
On mount the revealed fields take their configured defaults (e.g. count = 1); tearing the block down and re-choosing Yes starts fresh rather than resurrecting the prior draft.
inline edit — draft
The editor holds a local draft seeded from the committed value. Save writes the draft back (and returns to view); Cancel / Esc discards it. Enter saves. Focus moves into the input on open and back to the trigger on close.
a11y
Groups are a <fieldset> + <legend>; radios/checkboxes ride Base UI roles; each row's remove control names its item; the inline trigger names its field (Edit display name).

Source · composed from ui/Field.tsx · Input.tsx · Select.tsx · Button.tsx + @phauna/ds/overlays Radio/Checkbox (catalog/inputs.css .choice). Field-array, conditional-reveal and inline-edit are app-level patterns built on those DS primitives — no bespoke control.