Control-only inline renderer for ONE entity field — the embeddable
primitive under FormBuilder.build_field/3's full form blocks. Hosts
that lay records out their own way (compact rows, table cells, chips)
render <.field_input> per field and keep full ownership of layout,
labels, and persistence, while every field type — including ones added
to entities later — renders correctly without host changes.
The save contract
The host wraps its inputs in a <form phx-change="..." phx-submit="...">
it owns (include hidden inputs for row identity — a record uuid — and
read _target to know which field changed). phx-submit is
load-bearing even when the change event does all the saving: a form
with phx-change but no phx-submit is treated as external by
LiveView, so Enter in a typed input native-submits and navigates away,
killing the socket. Point it anywhere harmless — a clause that no-ops
on payloads without _target works.
- Typed inputs (text, textarea, email, url, rich_text, number,
date) carry
phx-debounce="blur", so the form's change event fires on blur/enter — not per keystroke. - Discrete inputs (boolean toggle, select, radio, checkbox
group) fire immediately. Booleans pair the checkbox with a hidden
"false"input and checkbox groups a hidden"", so the change payload always carries the key even when everything is unticked. Checkbox groups submit undername[](not the barename), so the host reads a list at that key —cast_field/2normalizes it, dropping the hidden""entry. - Media references (image, video) are not form inputs at all:
they render the current file (thumbnail for images) plus
Choose/Clear buttons that push the host's
on_pick/on_clearevents withphx-value-field={field key}and anypick_params. The host opens its media picker (e.g. core'sMediaSelectorModal) and writes the chosen storage file uuid through its own save path.
Cast the change payload with PhoenixKitEntities.FormBuilder.cast_field/2
before persisting — it applies the same per-type coercion and
validation the full form pipeline uses.
Nested forms are invalid HTML: this component renders bare controls precisely so it can live inside whatever form (or none) the host has; the host is responsible for the form context.
Example
<form
id={"row-#{value.uuid}"}
phx-change="extras_changed"
phx-submit="extras_changed"
>
<input type="hidden" name="uuid" value={value.uuid} />
<.field_input
:for={field <- @entity.fields_definition}
field={field}
name={"extras[#{field["key"]}]"}
value={value.data[field["key"]]}
size="xs"
on_pick="pick_extra_media"
on_clear="clear_extra_media"
pick_params={%{"uuid" => value.uuid}}
/>
</form>
Summary
Functions
Renders the control for one field definition. See the moduledoc for the save contract per field-type family.
Functions
Renders the control for one field definition. See the moduledoc for the save contract per field-type family.
Attributes
field(:map) (required) - one fields_definition entry (string keys).name(:string) (required) - form input name, e.g. extras[price]. Checkbox groups append[](real options and the hidden""fallback both submit undername[]), so the host reads a list at that key.value(:any) - Defaults tonil.id(:string) - derived from name when absent. Defaults tonil.size(:string) - Defaults to"sm". Must be one of"xs","sm", or"md".disabled(:boolean) - Defaults tofalse.class(:string) - extra classes merged onto the styled control — for daisyUI modifiers or layout the host owns, e.g.join-itemwhen the field sits inside a joined group. Defaults tonil.form(:string) - id of the owning <form> when the control renders OUTSIDE it (the HTML form attribute) — e.g. table layouts, where a <form> inside <tr> gets foster-parented out by the HTML parser. Applied to every native control including the hidden fallbacks.Defaults to
nil.on_pick(:string) - event pushed by the image/video Choose button (required for those types). Defaults tonil.on_clear(:string) - event pushed by the image/video Clear button (hidden when absent). Defaults tonil.pick_params(:map) - extra phx-value-* params on the pick/clear buttons (row identity)."field"is reserved — the buttons always send the field key under it, so apick_paramsentry namedfieldis dropped.Defaults to
%{}.