Input

Single-line text field with sizes, native input types, affix slots, and grouped controls.

Basic

.heex
<.input name="email" label="Email" placeholder="you@example.com" />

Sizes

Set size to one of xs, sm, md, lg, or xl to match the surrounding density.

.heex
<.input size="xs" name="size_xs" placeholder="Extra small" />
<.input size="sm" name="size_sm" placeholder="Small" />
<.input size="md" name="size_md" placeholder="Medium (default)" />
<.input size="lg" name="size_lg" placeholder="Large" />
<.input size="xl" name="size_xl" placeholder="Extra large" />

Label, description, and help text

Combine label, sublabel, description, and help_text to build the label area without extra wrappers.

(required)

Used to authenticate requests against your account.

Stored encrypted at rest. Rotate keys from your account settings.
.heex
<.input
  name="api_key"
  label="API key"
  sublabel="(required)"
  description="Used to authenticate requests against your account."
  help_text="Stored encrypted at rest. Rotate keys from your account settings."
  placeholder="sk_live_..."
/>

Input types

type maps to the underlying <input> type, so the browser provides the matching keyboard, validation, and native picker.

.heex
<.input type="email" name="email_type" label="Email" placeholder="you@example.com" />
<.input type="password" name="password_type" label="Password" value="hunter2" />
<.input type="number" name="quantity_type" label="Quantity" min="1" max="10" value="3" />
<.input type="date" name="appointment_type" label="Appointment" />
<.input type="time" name="start_at_type" label="Start time" />

Inner prefix

:inner_prefix renders inside the field border, before the input. Fits inline icons and short static text.

.heex
<.input name="card_number" placeholder="0000 0000 0000 0000">
  <:inner_prefix>
    <.icon name="hero-credit-card" class="icon" />
  </:inner_prefix>
</.input>

<.input name="subdomain" placeholder="yourcompany">
  <:inner_prefix class="pointer-events-none text-foreground-softer">https://www.</:inner_prefix>
</.input>

Inner suffix

:inner_suffix renders inside the field border, after the input. Drop in password reveal toggles, inline loading, or error indicators.

.heex
<.input name="password_suffix" type="password" value="supersecret">
  <:inner_suffix>
    <.button variant="ghost" size="icon-sm" type="button" title="Show password">
      <.icon name="hero-eye" class="icon" />
    </.button>
  </:inner_suffix>
</.input>

<.input name="search_suffix" placeholder="Searching...">
  <:inner_suffix>
    <.loading variant="ring-bg" />
  </:inner_suffix>
</.input>

<.input name="email_suffix" value="invalid@" errors={["Must be a valid email address."]}>
  <:inner_suffix>
    <.icon name="hero-exclamation-circle" class="size-5 text-danger" />
  </:inner_suffix>
</.input>

Outer prefix

:outer_prefix attaches to the field's leading edge, outside the border. Borders and corners join automatically.

https://
.heex
<.input name="amount" type="number" placeholder="0.00">
  <:inner_prefix class="text-foreground-softer">$</:inner_prefix>
  <:outer_prefix>
    <select class="h-full rounded-l-base border-none bg-emphasis px-3 text-sm focus:ring-0">
      <option value="USD">USD</option>
      <option value="EUR">EUR</option>
      <option value="GBP">GBP</option>
    </select>
  </:outer_prefix>
</.input>

<.input name="domain" placeholder="mysite">
  <:outer_prefix class="bg-emphasis px-3 text-foreground-softer">https://</:outer_prefix>
</.input>

Outer suffix

:outer_suffix attaches to the field's trailing edge. Match the button's size to the input's so the joint stays flush.

kg
.heex
<.input name="refund_amount" type="number" placeholder="0.00">
  <:inner_prefix class="text-foreground-softer">$</:inner_prefix>
  <:outer_suffix>
    <.button>Refund</.button>
  </:outer_suffix>
</.input>

<.input name="weight" type="number" placeholder="50">
  <:outer_suffix class="bg-emphasis px-3 text-foreground-softer">kg</:outer_suffix>
</.input>

Common header pattern: a leading magnifier icon and a trailing submit button joined to the field.

Invite by email

Combine an :inner_prefix icon with an :outer_suffix action button. The trailing button absorbs the field's right border for one connected control.

.heex
<.input
  type="email"
  name="invite_email"
  placeholder="teammate@company.com"
>
  <:inner_prefix>
    <.icon name="hero-at-symbol" class="icon" />
  </:inner_prefix>
  <:outer_suffix>
    <.button color="primary">
      <.icon name="hero-paper-airplane" class="icon" /> Invite
    </.button>
  </:outer_suffix>
</.input>

Money amount

Two affixes share one field: an :outer_prefix currency picker on the leading edge and an :inner_prefix dollar sign inside the field.

Charged in the selected currency.
.heex
<.input
  type="number"
  name="money_amount"
  label="Amount"
  help_text="Charged in the selected currency."
  min="0"
  step="0.01"
  placeholder="0.00"
>
  <:inner_prefix class="text-foreground-softer">$</:inner_prefix>
  <:outer_prefix>
    <select class="h-full rounded-l-base border-none bg-emphasis px-3 text-sm focus:ring-0">
      <option value="USD">USD</option>
      <option value="EUR">EUR</option>
      <option value="GBP">GBP</option>
    </select>
  </:outer_prefix>
</.input>

Disabled

Set disabled to block focus and edits. Disabling a parent <fieldset> cascades through native HTML semantics.

.heex
<.input name="locked" label="Account ID" value="acct_2BvKj1..." disabled />

Readonly

Set readonly to keep focus and copy/select behavior, but block edits to the value.

.heex
<.input name="reference" label="Reference" value="REF-2025-0042" readonly />

Validation errors

Bind field to a Phoenix.HTML.FormField and the input renders translated changeset errors automatically once the field has been touched.

Format: (555) 555-5555
.heex
<.form :let={f} for={@form} phx-change="validate" phx-submit="save">
  <.input field={f[:first_name]} label="First name" />
  <.input field={f[:last_name]} label="Last name" />
  <.input field={f[:email]} type="email" label="Email" />
  <.input
    field={f[:phone]}
    label="Phone"
    help_text="Format: (555) 555-5555"
  />
</.form>
.ex
def handle_event("validate", %{"user" => _params}, socket) do
  # Re-run validation and reassign the form
  {:noreply, socket}
end

def handle_event("save", %{"user" => _params}, socket) do
  # Persist the user
  {:noreply, socket}
end

Group: full name

Wrap inputs in <.input_group> to join them into one connected control with a shared label and a single border.

.heex
<.input_group label="Full name">
  <.input name="first_name" placeholder="First name" />
  <.input name="last_name" placeholder="Last name" />
</.input_group>

Group: card details

Three inputs read as one control. Use the same size on every child so heights stay flush.

.heex
<.input_group label="Card details">
  <.input name="group_card_number" placeholder="Card number">
    <:inner_prefix>
      <.icon name="hero-credit-card" class="icon" />
    </:inner_prefix>
  </.input>
  <.input name="group_card_expiry" placeholder="MM / YY" class="w-24" />
  <.input name="group_card_cvc" placeholder="CVC" class="w-20" />
</.input_group>

Group: date and time range

Native date and time pickers slot directly into <.input_group>. The leading and trailing corners round automatically.

Both date and time use the visitor's local timezone.
.heex
<.input_group label="When" help_text="Both date and time use the visitor's local timezone.">
  <.input type="date" name="starts_on" class="w-44" />
  <.input type="time" name="starts_at" class="w-32" />
</.input_group>