Forms.field
Server-rendered
Field wrapper for label, control, description, and errors.
field/1 remains the simplest composition helper. It automatically wraps the
control passed to its inner block with field_control/1, so most usages
should pass the form control directly and use the :label, :description,
:message, and :error slots for supporting content.
Prefer the shorthand :label slot with for for ordinary field labels.
Reach for raw :label slot content, field_label/1, field_control/1,
field_description/1, field_message/1, and field_error/1 when you need
richer markup or want to compose the pieces outside field/1.
Profile field
Shown in your profile.
<.field>
<:label for="name">Name</:label>
<.input id="name" name="name" />
<:description>Shown in your profile.</:description>
</.field>
Validation state
We'll send deployment alerts here.
Please use your company domain.
<.field>
<:label for="email">Work email</:label>
<.input id="email" name="email" type="email" />
<:description>We'll send deployment alerts here.</:description>
<:error>Please use your company domain.</:error>
</.field>
Custom Label Markup
Slug has already been taken.
<.field invalid={true}>
<:label>
<.field_label>
<.label for="workspace-slug">Workspace slug</.label>
<span class="text-muted-foreground text-xs">Used in your public workspace URL.</span>
</.field_label>
</:label>
<.input id="workspace-slug" name="workspace[slug]" value="cinder-ui" />
<:error>Slug has already been taken.</:error>
</.field>
Phoenix validation flow
<.form for={%{}} phx-change="validate" phx-submit="save" class="space-y-6">
<.field invalid={true}>
<:label for="owner">Owner</:label>
<.autocomplete
id="owner"
name="owner"
value="levi"
aria-label="Owner"
>
<:option value="levi" label="Levi Buzolic" description="Engineering" />
<:option value="mira" label="Mira Chen" description="Design" />
<:empty>No matching teammates.</:empty>
</.autocomplete>
<:description>Choose the teammate responsible for this workspace.</:description>
<:error>Please choose a teammate.</:error>
</.field>
</.form>
Date range fields
Use the first local day to include in the report.
End date must be on or after the start date.
<div class="grid gap-4 sm:grid-cols-2">
<.field>
<:label for="report_start_date">Start date</:label>
<.input
id="report_start_date"
name="report[start_date]"
type="date"
value="2026-06-01"
/>
<:description>Use the first local day to include in the report.</:description>
</.field>
<.field invalid={true}>
<:label for="report_end_date">End date</:label>
<.input
id="report_end_date"
name="report[end_date]"
type="date"
value="2026-05-31"
min="2026-06-01"
aria-invalid="true"
/>
<:error>End date must be on or after the start date.</:error>
</.field>
</div>
LiveView date validation
Phoenix shim
<.form for={@form} phx-change="validate" phx-submit="save" class="grid gap-6">
<.field>
<:label for={@form[:start_date].id}>Start date</:label>
<.input field={@form[:start_date]} type="date" required />
<:description>Changes are validated by the LiveView on phx-change.</:description>
</.field>
<.field invalid={true}>
<:label for={@form[:end_date].id}>End date</:label>
<.input
field={@form[:end_date]}
type="date"
min="2026-06-01"
aria-invalid="true"
/>
<:error>End date must be on or after the start date.</:error>
</.field>
</.form>
Attributes
| Name | Type | Default | Values | Global Includes |
|---|---|---|---|---|
class
|
:string |
— | — | — |
invalid
|
:boolean |
false
|
— | — |
rest
|
:global |
— | — | — |
Slots
| Slot | Slot Attributes |
|---|---|
description
|
— |
error
|
— |
inner_block
Required
|
— |
label
|
for
(:string)
class
(:string)
|
message
|
— |