Docs

Cinder UI

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

Used in your public workspace URL.

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

Choose the teammate responsible for this workspace.

Please choose a teammate.

  <.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

Changes are validated by the LiveView on phx-change.

End date must be on or after the start date.

  <.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