Components Forms (v1)

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Forms (v1)

This page is for users using the old form structure (pre Phoenix 1.7).

v1 way (pre Phoenix 1.7):

<.form :let={f} as={:user} for={@changeset} phx-submit="on_submit">
  <.form_field type="text_input" form={f} field={:your_field} />

  <.form_field
    form={f}
    field={:a_select_field}
    type="select"
    options={[{"Option 1", "1"}, {"Option 2", "2"}]}
  />

  <.button type="submit" label="Submit" />
</.form>

v2 way (Phoenix 1.7):

<.form for={@form} phx-submit="on_submit">
  <.field field={@form[:email]} />

  <.field
    field={@form[:email]}
    type="select"
    options={[{"Option 1", "1"}, {"Option 2", "2"}]}
  />

  <.button type="submit" label="Submit" />
</.form>

Go here for the v2 docs.

There are two ways of creating form fields
  1. All inclusive option: Use `.form_field', which includes the input label and any errors on the changeset. Use this if you just want a good looking form that works with errors out of the box.
  2. Explicit option: Individually write out the label, input and errors. Do this when you want to be more explicit option on where the label or errors go.
The following code shows both methods for each form element.
Form field
`.form_field` is the easiest way to create input fields. We support every field Phoenix supports plus more.
Gender
heex
Copy
<.form :let={f} as={:user} for={@changeset}>
  <.form_field
    type="text_input"
    form={f}
    field={:your_field}
    placeholder="Placeholder"
  />
  <.form_field
    type="select"
    options={[Admin: "admin", User: "user"]}
    form={f}
    field={:select}
  />
  <.form_field type="password_input" form={f} field={:password} />
  <.form_field
    type="radio_group"
    options={[Male: "male", Female: "female", Other: "other"]}
    form={f}
    field={:gender}
  />
  <div class="flex justify-end gap-3">
    <.button type="button" label="Cancel" color="white" />
    <.button type="submit" label="Submit" />
  </div>
</.form>
Form Field properties
elixir
Copy
            # <.form_field>
  attr(:form, :any, doc: "the form object", required: true)
  attr(:field, :atom, doc: "field in changeset / form", required: true)
  attr(:label, :string, doc: "labels your field")
  attr(:label_class, :string, default: nil, doc: "extra CSS for your label")
  attr(:help_text, :string, default: nil, doc: "context/help for your field")

  attr(:type, :string, default: "text_input", values: [
    "text_input",
    "email_input",
    "number_input",
    "password_input",
    "search_input",
    "telephone_input",
    "url_input",
    "time_input",
    "time_select",
    "date_input",
    "date_select",
    "datetime_local_input",
    "datetime_select",
    "color_input",
    "file_input",
    "range_input",
    "textarea",
    "select",
    "checkbox",
    "checkbox_group",
    "radio_group",
    "switch",
    "hidden_input"],
  doc: "The type of input")

attr(:wrapper_classes, :string, default: "pc-form-field-wrapper", doc: "CSS class for wrapper")
attr :rest, :global, include: @form_attrs

        
Text input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.text_input form={f} field={:your_field} placeholder="eg. John" />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field
  type="text_input"
  form={f}
  field={:your_field}
  placeholder="Placeholder"
/>

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Email input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.email_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="email_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Number input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.number_input form={f} field={:your_field} placeholder="eg. 1" />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="number_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Password input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.password_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="password_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Search input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.search_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="search_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Telephone input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.telephone_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="telephone_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
URL input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.url_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="url_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Hidden input
heex
Copy
          <!-- Explicit option  -->
<.hidden_input form={f} field={:your_field} />

<!-- All inclusive option  -->
<.form_field type="hidden_input" form={f} field={:your_field} />

        

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Time input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.time_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="time_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Time select
Time select
:
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.time_select form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="time_select" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#time_select/3 as opts.
Date input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.date_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="date_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#text_input/3 as opts.
Date select
Date select
/ /
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.date_select form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="date_select" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#time_select/3 as opts.
Datetime local input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.datetime_local_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="datetime_local_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#datetime_local_input/3 as opts.
Datetime select
Datetime select
/ / :
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.datetime_select form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="datetime_select" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#datetime_select/3 as opts.
Color input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.search_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="color_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#color_input/3 as opts.
File input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.file_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="file_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#file_input/3 as opts.
Range input
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.range_input form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="range_input" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#range_input/3 as opts.
Textarea
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.textarea form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="textarea" form={f} field={:your_field} />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#textarea/3 as opts.
Select
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />
<.select
  options={["Admin": "admin", "User": "user"]}
  form={f}
  field={:your_field}
/>
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field
  type="select"
  options={["Admin": "admin", "User": "user"]}
  form={f}
  field={:your_field}
/>

Properties

All properties are forwarded to Phoenix.HTML.Form.html#select/4 as opts.
Multiple select

We are yet to implement the HTML5 multiple select due to it being hard to use for end users. Instead we recommend using our checkbox group component.

Switch
heex
Copy
<!-- Explicit option  -->
<.switch form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="switch" form={f} field={:your_field} label="I agree" />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#checkbox/3 as opts.
Checkbox
heex
Copy
<!-- Explicit option  -->
<.checkbox form={f} field={:your_field} />
<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field type="checkbox" form={f} field={:your_field} label="I accept" />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#checkbox/3 as opts.
Checkbox group

Use this instead of a multiple select (when the field data type is a list).

Column layout
Row layout
heex
Copy
<.form_field
  type="checkbox_group"
  form={f}
  field={:your_field}
  label="Column layout"
  options={[
    {"Option 1", "option_1"},
    {"Option 2", "option_2"},
    {"Option 3", "option_3"},
    {"Option 4", "option_4"}
  ]}
/>

<.form_field
  type="checkbox_group"
  form={f}
  layout={:row}
  field={:your_field}
  label="Row layout"
  options={[
    {"Option 1", "option_1"},
    {"Option 2", "option_2"},
    {"Option 3", "option_3"},
    {"Option 4", "option_4"}
  ]}
/>
Properties
elixir
Copy
            attr(:form, :any, default: nil, doc: "")
  attr(:field, :atom, default: nil, doc: "")
  attr(:label, :string, default: nil, doc: "labels your field")
  attr(:class, :string, default: "", doc: "extra classes for the text input")
  attr(:options, :list, default: [], doc: "options for the select")
  attr(:layout, :atom, default: :col, values: [:row, :col], doc: "layout for the checkboxes")
  attr(:checked, :list, doc: "a list of checked values")
  attr(:rest, :global, include: @form_attrs)

        
Radio
Column layout
Row layout
heex
Copy
<!-- Explicit option  -->
<.form_label form={f} field={:your_field} />

<div class="flex flex-col gap-1">
  <label class="inline-flex items-center gap-3 text-sm text-gray-900 dark:text-gray-200">
    <.radio form={f} field={:your_field} value="green" />
    <div>Green</div>
  </label>

  <label class="inline-flex items-center gap-3 text-sm text-gray-900 dark:text-gray-200">
    <.radio form={f} field={:your_field} value="blue" />
    <div>Blue</div>
  </label>

  <label class="inline-flex items-center gap-3 text-sm text-gray-900 dark:text-gray-200">
    <.radio form={f} field={:your_field} value="gray" />
    <div>Gray</div>
  </label>
</div>

<.form_field_error form={f} field={:your_field} class="mt-1" />

<!-- All inclusive option  -->
<.form_field
  type="radio_group"
  form={f}
  field={:radio}
  options={["Option 1": "option_1", "Option 2": "option_2", "Option 3": "option_3"]}
  label="Column layout"
/>

<.form_field
  type="radio_group"
  form={f}
  field={:radio_group}
  layout={:row}
  label="Row layout"
  options={[
    "Option 1": "option_1",
    "Option 2": "option_2",
    "Option 3": "option_3"
  ]}
/>

Properties

All properties are forwarded to Phoenix.HTML.Form.html#radio_button/4 as opts.
Form label

Usually paired with inputs.

heex
Copy
<.form_label form={f} field={:email} />
<.form_label form={f} field={:email} label="Custom label" />

Properties

All properties are forwarded to Phoenix.HTML.Form.html#label/3 as opts.
Form field error

Will display any errors in your changeset for a particular field.

heex
Copy
          <.form_field_error form={f} field={:your_field} />

        
Help text

You can add additional instructions at the bottom of any input.

Type your email above
heex
Copy
<.form_label form={f} field={:email} />
<.text_input form={f} field={:email} />
<.form_help_text class="mt-2">Type your email above</.form_help_text>

OR

<.form_field
  type="text_input"
  form={f}
  field={:email}
  help_text="Type your email above"
/>