Fluxon.Components.Input (Fluxon v3.0.0)

Provides <.input> and <.input_group> components for text-based form fields.

The <.input> component renders a labeled <input> element together with optional description, helper text, and error messages. It composes with prefix and suffix slots, both inside and outside the field, so icons, static text, dropdowns, and action buttons can be attached without breaking border alignment. The component binds directly to Phoenix.HTML.FormField structs for changeset-driven forms or accepts a plain name/value for standalone use.

The <.input_group> component visually joins multiple inputs or related controls (selects, buttons) into a single connected control with shared borders and rounding.

Input vs Textarea vs Autocomplete vs Select

Fluxon offers several form controls for text-shaped values. Pick the one that matches the kind of value the user enters:

ComponentValue KindUse Case
<.input>A single line of free-form text or numberNames, emails, passwords, search, dates
<.textarea>Multi-line free-form textComments, descriptions, message bodies
<.autocomplete>One value chosen from a typed searchPicking from a long list while filtering
<.select>One value chosen from a fixed listStatus, category, country picker

Usage

Render an input by providing a name. A label is recommended so the field has a visible association for the user:

<.input name="username" label="Username" placeholder="Enter username..." />

Most fields combine a label with a placeholder, helper text, and a specific input type:

<.input
  name="email"
  type="email"
  label="Email"
  description="We'll send a confirmation link here."
  help_text="We never share your email."
  placeholder="you@example.com"
/>

Sizes

The size attribute scales the field height, horizontal padding, and font size. The default is "md":

<.input name="input_xs" size="xs" placeholder="Extra Small" />
<.input name="input_sm" size="sm" placeholder="Small" />
<.input name="input_md" size="md" placeholder="Medium (Default)" />
<.input name="input_lg" size="lg" placeholder="Large" />
<.input name="input_xl" size="xl" placeholder="Extra Large" />
SizeHeightTextUse Case
xs28pxxsDense layouts: filter bars, inline editors, table cells
sm32pxsmToolbars, secondary forms, compact admin UIs
md36pxsmDefault for most forms
lg40pxbaseProminent fields: hero search, sign-up forms
xl44pxlgHigh-emphasis single-field surfaces (landing pages)

Input Types

The type attribute maps directly to the underlying <input>'s HTML type. Pick the type that matches the value being collected so the browser provides the right on-screen keyboard, validation hints, and native picker UI:

<.input type="email" name="email" label="Email" />
<.input type="password" name="password" label="Password" />
<.input type="search" name="site_search" placeholder="Search site..." />
<.input type="number" name="count" label="Quantity" min="1" max="10" />
<.input type="date" name="appointment" label="Appointment Date" />
<.input type="time" name="start_at" label="Start Time" />

Setting type="hidden" renders only a bare <input type="hidden"> with no surrounding label, affixes, or error UI. This is useful for carrying values that must round-trip with a form but should not be visible to the user.

Reach for Number Input for Rich Numeric Entry

type="number" renders a plain numeric field with the browser's native spinner. For counters, quantities, prices, or percentages that benefit from stepper buttons, press-and-hold repeat, clamping to min/max, and locale-aware display formatting, use Fluxon.Components.NumberInput instead. It shares this component's label, affix, and form-binding surface.

Labels, Description, and Helper Text

The label area can carry up to three distinct strings:

  • label: the primary label rendered above the field.
  • sublabel: short inline text rendered next to the label, typically a state hint such as "(required)" or "(optional)".
  • description: longer guidance rendered between the label and the field.

Helper text rendered below the field uses the help_text attribute:

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

Form Integration

The component supports two binding modes: a Phoenix.HTML.FormField struct via field (changeset-driven forms) or a plain name (standalone inputs).

Bind to a form field

When field is set, the component derives id, name, value, and errors from the form field struct. Errors are translated and rendered automatically when the field has been touched (Phoenix.Component.used_input?/1):

<.form :let={f} for={@form}>
  <.input field={f[:email]} type="email" label="Email" />
  <.input field={f[:password]} type="password" label="Password" />
</.form>

This mode handles nested input names (user[email]) and validation errors with no extra wiring.

Standalone input

When you don't have a changeset, pass name, optionally value, and any error list directly:

<.input name="q" placeholder="Search..." value={@query} />
<.input name="email" type="email" value={@email} errors={@errors} />

Picking the Binding Mode

Use field for changeset-backed forms (CRUD, validation, nested data). Use name for one-off inputs without a changeset (top-bar search boxes, ad-hoc filters, controls whose value is held in socket assigns).

Input States

Disabled

Disabled inputs cannot be focused or edited and are styled with reduced contrast. Disabling a parent <fieldset> cascades to descendant inputs through HTML's native :disabled semantics:

<.input name="locked" value="Cannot change" disabled />

<fieldset disabled>
  <.input name="email" value="user@example.com" />
  <.input name="phone" value="+1 555 123 4567" />
</fieldset>

Readonly

A readonly input still receives focus and can be selected/copied, but its value cannot be edited. Pass readonly through the global rest attribute:

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

Errors

When the errors list is non-empty (or the bound form field has errors), the field renders with the danger color and each message is shown below the input:

<.input
  name="email"
  value="invalid@"
  errors={["Must be a valid email address."]}
/>

When using field, errors are pulled from field.errors and translated through the configured Gettext backend.

Inner Affixes

The :inner_prefix and :inner_suffix slots inject content inside the field border, sharing the input's focus ring. Use them for inline icons, currency symbols, password reveal toggles, and inline indicators:

<!-- Email field with leading icon -->
<.input name="email" placeholder="user@example.com">
  <:inner_prefix>
    <.icon name="hero-at-symbol" class="icon" />
  </:inner_prefix>
</.input>

<!-- Password with reveal toggle -->
<.input name="password" type="password" value="secretpassword">
  <:inner_suffix>
    <.button variant="tertiary" size="icon-sm" title="Show password">
      <.icon name="hero-eye" class="icon" />
    </.button>
  </:inner_suffix>
</.input>

<!-- Static text prefix -->
<.input name="subdomain" placeholder="yourdomain">
  <:inner_prefix class="pointer-events-none text-tertiary">https://www.</:inner_prefix>
</.input>

<!-- Inline loading indicator while a search runs -->
<.input name="q" placeholder="Searching..." disabled>
  <:inner_suffix>
    <.loading variant="ring-bg" />
  </:inner_suffix>
</.input>

Both inner slots accept multiple entries and an optional class for per-slot overrides.

Outer Affixes

The :outer_prefix and :outer_suffix slots attach content outside the field's border. The component automatically rounds the leading/trailing corners of the affix and overlaps borders for a single seamless control. Use them for action buttons, select dropdowns, or static unit labels that are conceptually paired with the input but distinct from its value:

<!-- Invite form with a trailing send button -->
<.input name="invite_email" placeholder="user@example.com">
  <:inner_prefix>
    <.icon name="hero-at-symbol" class="icon" />
  </:inner_prefix>
  <:outer_suffix>
    <.button variant="solid" color="primary">
      <.icon name="hero-paper-airplane" class="icon" /> Invite
    </.button>
  </:outer_suffix>
</.input>

<!-- Currency picker on the leading edge, action on the trailing edge -->
<.input name="amount" placeholder="100.00">
  <:inner_prefix>$</:inner_prefix>
  <:outer_prefix>
    <select class="h-full w-full rounded-l-lg border-none focus:ring-0 px-2">
      <option value="USD">USD</option>
      <option value="CAD">CAD</option>
    </select>
  </:outer_prefix>
  <:outer_suffix>
    <.button variant="soft" color="primary">Check Balance</.button>
  </:outer_suffix>
</.input>

<!-- Domain field framed by static text -->
<.input name="site" placeholder="mysite">
  <:outer_prefix class="px-2 text-tertiary">https://</:outer_prefix>
  <:outer_suffix class="px-2 text-tertiary">.example.com</:outer_suffix>
</.input>

<!-- Numeric input with a unit -->
<.input name="weight" type="number" placeholder="50">
  <:outer_suffix class="px-2 text-tertiary">kg</:outer_suffix>
</.input>

Match Button Size to Input Size

When placing a <.button> inside :outer_prefix or :outer_suffix, set the button's size to the same value as the input. Mismatched sizes leave the button shorter or taller than the field and visibly break the joint border.

Input Groups

<.input_group> joins multiple inputs (and adjacent controls like buttons or selects) into one connected control. The group strips internal rounding and collapses adjacent borders so the children read as a single unit:

<!-- First and last name -->
<.input_group label="Full Name">
  <.input name="first_name" placeholder="First Name" />
  <.input name="last_name" placeholder="Last Name" />
</.input_group>

<!-- Two inputs separated by a static label -->
<.input_group label="Price Range">
  <.input name="min_price" placeholder="Min price">
    <:inner_prefix>$</:inner_prefix>
  </.input>
  <div class="shrink-0 bg-emphasis border-y border-base shadow-xs self-stretch flex items-center justify-center px-2 text-foreground-softest">
    to
  </div>
  <.input name="max_price" placeholder="Max price">
    <:inner_prefix>$</:inner_prefix>
  </.input>
</.input_group>

<!-- Card number, expiry, and CVC sharing one connected control -->
<.input_group>
  <.input name="card_number" placeholder="Card Number">
    <:inner_prefix><.icon name="hero-credit-card" class="icon" /></:inner_prefix>
  </.input>
  <.input name="expiry" placeholder="MM / YY" class="w-24" />
  <.input name="cvc" placeholder="CVC" class="w-20" />
</.input_group>

Use One Size Per Group

All children of <.input_group> should share the same size value. Mixing sizes leaves children at different heights and breaks the connected border.

Constraining Field Width

By default the field fills the width of its container. To narrow only the field while keeping the label, description, and helper text at their natural width, apply a width utility to any ancestor that targets [data-part=field-root]:

<div class="**:data-[part=field-root]:max-w-xs">
  <.input label="Postal code" description="Used for billing" name="zip" />
</div>

This pattern works uniformly across all Fluxon input-shaped form components.

Examples

Login form with changeset binding

<.form :let={f} for={@form} phx-submit="sign_in" phx-change="validate">
  <.input field={f[:email]} type="email" label="Email" autocomplete="email" required />
  <.input field={f[:password]} type="password" label="Password" autocomplete="current-password" required>
    <:inner_suffix>
      <.button type="button" variant="tertiary" size="icon-sm" phx-click={JS.dispatch("toggle-password")}>
        <.icon name="hero-eye" class="icon" />
      </.button>
    </:inner_suffix>
  </.input>

  <.button type="submit" color="primary">Sign in</.button>
</.form>

Top-bar search with submit button

<.input name="q" type="search" placeholder="Search articles..." value={@query} phx-debounce="200">
  <:inner_prefix>
    <.icon name="hero-magnifying-glass" class="icon" />
  </:inner_prefix>
  <:outer_suffix>
    <.button color="primary" phx-click="search">Search</.button>
  </:outer_suffix>
</.input>

Invite teammates by email

<.input
  field={@form[:email]}
  type="email"
  label="Invite teammates"
  placeholder="teammate@company.com"
  description="They'll receive a sign-up link valid for 7 days."
>
  <:inner_prefix>
    <.icon name="hero-at-symbol" class="icon" />
  </:inner_prefix>
  <:outer_suffix>
    <.button color="primary" phx-disable-with="Sending...">
      Send invite
    </.button>
  </:outer_suffix>
</.input>

Money amount with a currency picker

<.input field={@form[:amount]} type="number" label="Amount" min="0" step="0.01">
  <:outer_prefix>
    <select name="currency" class="h-full w-full rounded-l-base border-none focus:ring-0 px-2">
      <option value="USD">USD</option>
      <option value="EUR">EUR</option>
      <option value="GBP">GBP</option>
    </select>
  </:outer_prefix>
</.input>

Address group: postal code + city

<.input_group label="Address" help_text="We use this to estimate shipping.">
  <.input field={@form[:postal_code]} placeholder="Postal code" class="w-32" />
  <.input field={@form[:city]} placeholder="City" />
</.input_group>

Date and time range

<.input_group label="When">
  <.input field={@form[:starts_on]} type="date" class="w-44" />
  <.input field={@form[:starts_at]} type="time" class="w-32" />
</.input_group>

Summary

Components

Renders a labeled text input with support for sizes, input types, affix slots, and form integration.

Renders a row of inputs (and adjacent controls) joined into a single connected control.

Components

input(assigns)

Renders a labeled text input with support for sizes, input types, affix slots, and form integration.

Use this for any single-line text-shaped value: names, emails, passwords, search, numbers, and native date/time pickers. The component renders the <input> element plus its surrounding label, description, helper text, error messages, and the inner/outer prefix/suffix slots.

Pass a Phoenix.HTML.FormField via field to bind the input to a changeset-backed form, or pass name, value, and optionally errors directly for standalone use.

Attributes

  • id (:any) - Sets the input's id. When omitted, the component falls back to the value of name (or to field.id when field is set) so the rendered <label> can target the input via for.

    Defaults to nil.

  • label (:string) - Primary label text rendered above the field. Associated with the input through the for attribute, using the input's id. When omitted, no <label> is rendered.

    Defaults to nil.

  • sublabel (:string) - Short inline hint rendered alongside the main label. Use for state cues such as "(required)", "(optional)", or units that belong with the label rather than the field.

    Defaults to nil.

  • description (:string) - Longer guidance text rendered between the label and the field. Use for sentences that explain the field's purpose or set expectations about the value.

    Defaults to nil.

  • help_text (:string) - Helper text rendered below the field. Use for tips, formatting hints, or follow-up instructions that are easier to read after the user has seen the input.

    Defaults to nil.

  • class (:any) - Extra CSS classes applied to the inner <label data-part="field"> element that wraps the input and any inner affixes. Use this to adjust the field's width, padding, or background without affecting the surrounding label and helper text.

    Defaults to nil.

  • size (:string) - Controls the field height, horizontal padding, and font size.

    • xs: 28px tall. Best for dense layouts like filter bars and table-cell editors.
    • sm: 32px tall. Useful for toolbars and compact admin forms.
    • md: 36px tall. Default size for most forms.
    • lg: 40px tall. Good for prominent fields such as hero search bars or sign-up forms.
    • xl: 44px tall. For high-emphasis single-field surfaces such as landing-page email capture.

    Defaults to "md".

    Defaults to "md". Must be one of "xs", "sm", "md", "lg", or "xl".

  • disabled (:boolean) - When true, the field cannot be focused or edited and renders with reduced contrast. Disabling a parent <fieldset> cascades through HTML's native :disabled semantics without setting this attribute on each input.

    Defaults to false.

  • field (Phoenix.HTML.FormField) - A Phoenix.HTML.FormField struct, typically obtained from a form binding such as f[:email]. When provided, the input derives its id, name, value, and errors from the struct, and runs error messages through the project's translation helper. Errors are only shown after the field has been touched, as reported by Phoenix.Component.used_input?/1.

  • value (:any) - The current value of the input. Required when not using field. When field is set, this attribute is ignored in favor of field.value.

  • name (:any) - The submitted name for the input. Required when not using field. When field is set, this attribute is ignored in favor of field.name.

  • errors (:list) - A list of error message strings rendered below the field. When field is set, errors are pulled from field.errors and translated automatically; use this attribute to surface errors on standalone inputs that are not bound to a form field.

    Defaults to [].

  • type (:string) - The HTML type attribute on the underlying <input>. Choose the type that matches the value being collected so the browser provides the right keyboard, validation hints, and native picker.

    • text (default): general-purpose text entry.
    • email: email addresses; engages email-aware mobile keyboards and built-in validation.
    • password: masked entry for credentials.
    • search: search inputs; some browsers add a clear button.
    • tel: phone numbers; mobile keyboards switch to a numeric layout.
    • url: URL entry with built-in validation hints.
    • number: numeric values; pair with min, max, and step from the global rest attributes. For stepper buttons and locale-aware formatting, reach for Fluxon.Components.NumberInput.
    • date, time, datetime-local, month, week: native date/time pickers.
    • color: native color picker.
    • hidden: renders only <input type="hidden"> with no surrounding label, affixes, or errors. Use for values that must round-trip with the form but should not be visible to the user.

    Defaults to "text". Must be one of "color", "date", "datetime-local", "email", "month", "number", "password", "search", "tel", "text", "time", "url", "week", or "hidden".

  • Global attributes are accepted. Any additional HTML attributes supported by <input>, forwarded as-is to the rendered element. Common values include placeholder, autocomplete, required, readonly, min/max/step for numeric and date types, and Phoenix bindings such as phx-change, phx-debounce, and phx-blur. Supports all globals plus: ["accept", "autocomplete", "capture", "cols", "form", "list", "max", "maxlength", "min", "minlength", "pattern", "placeholder", "readonly", "required", "rows", "size", "step"].

Slots

  • inner_prefix - Content rendered inside the field border, before the input. Use for inline icons or short static text such as a currency symbol or URL prefix. Multiple slot entries stack in declaration order.Accepts attributes:
    • class (:any) - Extra CSS classes for the inner prefix wrapper.
  • outer_prefix - Content rendered outside the field, attached to its leading edge. Use for buttons, dropdowns, or static text labels conceptually paired with the input. The component rounds the leading corners and overlaps borders so the affix joins the field as a single control. Multiple slot entries stack in declaration order.Accepts attributes:
    • class (:any) - Extra CSS classes for the outer prefix wrapper.
  • inner_suffix - Content rendered inside the field border, after the input. Use for inline icons, clear buttons, password reveal toggles, or inline loading indicators. Multiple slot entries stack in declaration order.Accepts attributes:
    • class (:any) - Extra CSS classes for the inner suffix wrapper.
  • outer_suffix - Content rendered outside the field, attached to its trailing edge. Use for action buttons or trailing dropdowns. The component rounds the trailing corners and overlaps borders so the affix joins the field as a single control. Multiple slot entries stack in declaration order.Accepts attributes:
    • class (:any) - Extra CSS classes for the outer suffix wrapper.

input_group(assigns)

Renders a row of inputs (and adjacent controls) joined into a single connected control.

Use this when several values are conceptually one input (first/last name, postal code/city, card number/expiry/CVC, date/time range). The group strips internal rounding from its children and overlaps adjacent borders so the row reads as one control. A shared label, description, and helper text apply to the whole group.

Direct children are typically <.input>, <.select>, <.button>, or any element with a data-part attribute that the group recognizes for border alignment.

Use One Size Per Group

All inputs and buttons inside the group should share the same size. Mixing sizes leaves children at different heights and breaks the connected border.

Examples

<.input_group label="Login Credentials">
  <.input name="email" placeholder="Email" size="lg" />
  <.input name="password" type="password" placeholder="Password" size="lg" />
</.input_group>

Attributes

  • class (:any) - Extra CSS classes applied to the inner <div data-part="input-group"> that wraps the grouped controls. Use for layout adjustments specific to the row of inputs without affecting the surrounding label and helper text.

    Defaults to nil.

  • label (:string) - Primary label rendered above the entire group. The label is not associated with any specific child input via for; pair individual inputs with their own labels when each field needs an independent association.

    Defaults to nil.

  • sublabel (:string) - Short inline hint rendered alongside the group's main label, such as "(required)". Defaults to nil.

  • description (:string) - Longer guidance text rendered between the group label and the grouped inputs. Use for sentences that explain how the children combine into a single value.

    Defaults to nil.

  • help_text (:string) - Helper text rendered below the grouped inputs. Use for tips or formatting hints that apply to the group as a whole.

    Defaults to nil.

Slots

  • inner_block (required) - The grouped controls. Typically one or more <.input>, <.select>, or <.button> components, plus optional static elements (such as a separator <div>) for visual structure. Border rounding and joining is handled automatically based on the position of each child.