Fluxon.Components.NumberInput (Fluxon v3.0.0)

Renders a labeled numeric field with leading and trailing stepper buttons, locale-aware display formatting, and tight form integration.

The component shares the visible surface of Fluxon.Components.Input.input/1 (label, sublabel, description, helper text, error messages, sizes, inner and outer affix slots, changeset binding) and layers numeric semantics on top: increment and decrement buttons with press-and-hold repeat, modifier-augmented keyboard stepping, clamping at min and max, optional snapping to multiples of step, optional Alt-gated wheel scrubbing, and locale-aware formatting through Intl.NumberFormat.

Form submission always sends the canonical numeric string (decimal point, no grouping, no currency or percent symbol), so changesets and server-side handlers receive a clean number regardless of the locale or display format the user sees.

Pick a Numeric Input

Fluxon offers a few ways to collect a number. Choose the control that matches the task:

ComponentBest for
<.number_input>Counters, quantities, prices, percentages, ratings, configuration knobs.
<.input type="number">Plain numeric entry with no formatting, stepper buttons, or modifiers.
<.input type="text">Free-form values that happen to be numeric (phone numbers, codes).

Usage

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

<.number_input name="quantity" label="Quantity" value={1} />

Most fields combine a label with helper text and explicit numeric bounds:

<.number_input
  name="rating"
  label="Rating"
  description="Tell us how this product performed."
  help_text="From 1 (worst) to 5 (best)."
  value={3}
  min={1}
  max={5}
  step={1}
/>

Sizes

The size attribute scales the field height, horizontal padding, font size, and stepper button widths together. The default is "md".

<.number_input name="n_xs" size="xs" value={0} />
<.number_input name="n_sm" size="sm" value={0} />
<.number_input name="n_md" size="md" value={0} />
<.number_input name="n_lg" size="lg" value={0} />
<.number_input name="n_xl" size="xl" value={0} />
SizeHeightTextUse Case
xs28pxxsDense layouts: filter bars, table cells, inline editors.
sm32pxsmToolbars, secondary forms, compact admin UIs.
md36pxsmDefault for most forms.
lg40pxbaseProminent fields: hero forms, settings panels.
xl44pxlgHigh-emphasis single-field surfaces (landing pages, onboarding).

Bounds, Step, and Snapping

min and max clamp the value; step controls the amount that each stepper-button click or ArrowUp / ArrowDown press adds or subtracts:

<.number_input name="quantity" value={1} min={0} max={99} step={1} />
<.number_input name="amount" value={0.0} min={0} step={0.25} />

Modifier keys swap in a different amount. Shift switches to large_step, defaulting to ten times step. Alt switches to small_step, defaulting to one tenth of step. Both modifiers apply to keyboard arrows and to the stepper buttons:

<.number_input
  name="zoom"
  value={1.0}
  min={0.1}
  max={5}
  step={0.1}
  small_step={0.01}
  large_step={1}
/>

snap_on_step rounds increment and decrement results to the nearest multiple of step, even when the current value is off-grid. Useful when stepping is meant to align values to a grid rather than simply add a constant:

<.number_input name="grid_size" value={11} step={5} snap_on_step />

By default, the value is clamped to [min, max] after both stepping and typed entry. Set allow_out_of_range to let typed values fall outside the range (stepper buttons and keyboard stepping still clamp); this is the pattern that lets the browser's native range validation flag overflow / underflow on form submit.

The visible input is the single tab stop, and keyboard stepping runs through it. ArrowUp and ArrowDown step by step (or by large_step and small_step with the Shift and Alt modifiers described above), while PageUp and PageDown always step by large_step. Home and End jump to min and max when those bounds are defined, and Enter commits the typed value without moving focus.

Wheel Scrubbing

Enable allow_wheel_scrub to let the user adjust the value by scrolling the mouse wheel while holding Alt:

<.number_input name="zoom" value={1.0} step={0.1} allow_wheel_scrub />

Wheel scrubbing requires three conditions at once: the field must be focused, the Alt key must be held, and allow_wheel_scrub must be enabled. Without any of these, the wheel is ignored, so accidental scrolling never changes the value.

Locale and Format

Pass an IETF language tag through locale to control the thousands grouping character, the decimal separator, and the placement of any currency or unit symbol. The format map accepts the most common Intl.NumberFormat options:

<!-- Locale-driven grouping and decimal -->
<.number_input name="balance_en" value={1234.5} locale="en-US" />
<.number_input name="balance_de" value={1234.5} locale="de-DE" />

<!-- Currency formatting -->
<.number_input
  name="usd"
  value={1234.5}
  locale="en-US"
  format={%{style: "currency", currency: "USD"}}
/>

<!-- Percent: the underlying value is the decimal -->
<.number_input
  name="progress"
  value={0.42}
  format={%{style: "percent", maximum_fraction_digits: 1}}
/>

<!-- Disable grouping, control precision -->
<.number_input
  name="weight"
  value={2.5}
  format={%{maximum_fraction_digits: 2, use_grouping: false}}
/>

Percent Style Behavior

In percent mode the underlying value is the decimal: 0.42 displays as "42%". When the user focuses the field for typing, the editable form switches to the percent number ("42"); on blur, that value is divided by 100 again so the canonical value stays decimal. Stepping is in underlying units, so a step of 0.01 means one percentage point per click.

Typed input is parsed locale-aware. The parser accepts the locale's group and decimal separators, Swiss-style apostrophe grouping, non-breaking-space groupings, leading or trailing signs, the full set of Unicode minus and plus glyphs, currency and unit symbols, percent and permille marks, and a mixed-locale fallback that keeps the last "." as the decimal when the input crosses styles.

Stepper Buttons

The controls attribute decides whether the stepper buttons render. The default, "flanking", places minus on the left and plus on the right of the field. Pass "none" to drop the buttons while keeping keyboard stepping and wheel scrubbing intact:

<!-- Default flanking layout -->
<.number_input name="qty" value={1} />

<!-- No stepper buttons -->
<.number_input name="freeform" value={0} controls="none" />

The stepper buttons take pointer input only and stay out of the tab order. Pressing and holding either button fires an immediate step and then repeats at an accelerating cadence until the pointer is released. Dragging more than a few pixels away from the button cancels the hold, so a touch-and-drag is treated as a scroll gesture rather than a press.

Form Integration

Two binding modes are supported: a Phoenix.HTML.FormField via field for changeset-backed forms, or a plain name for standalone inputs.

Bind to a form field

<.form :let={f} for={@form} phx-change="validate" phx-submit="save">
  <.number_input field={f[:price]} label="Price" min={0} step={0.01} />
</.form>

Errors are pulled from field.errors, translated through the configured Gettext backend, and rendered after the field has been touched (Phoenix.Component.used_input?/1).

Standalone input

<.number_input name="count" label="Count" value={@count} />
<.number_input name="age" value={@age} 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 filters, ad-hoc counters, controls whose value is held in socket assigns).

The component renders a visible text input that shows the formatted value and a sibling hidden input that carries the canonical numeric string for form submission. Server-side handlers always see the canonical form, so changeset casts (:integer, :float, Decimal) and phx-change validators behave the same regardless of the locale the user sees.

Affixes

Four slots compose content around the field. Inner affixes share the input's focus ring; outer affixes have their corners rounded so they join the field as a single control:

<!-- Inner: currency symbol, unit hint -->
<.number_input name="amount" value={0}>
  <:inner_prefix class="pointer-events-none text-tertiary">$</:inner_prefix>
</.number_input>

<.number_input name="weight" value={50}>
  <:inner_suffix class="px-2 text-tertiary">kg</:inner_suffix>
</.number_input>

<!-- Outer: static labels, trailing action -->
<.number_input name="rpm" value={1800} step={50}>
  <:outer_suffix>
    <.button variant="ghost">Reset</.button>
  </:outer_suffix>
</.number_input>

States

Disabled

<.number_input name="locked" value={42} disabled />

disabled cascades to the visible input, the hidden input, and both stepper buttons. Disabling a parent <fieldset> cascades through HTML's native :disabled semantics without needing to set the attribute on each field.

Readonly

<.number_input name="reference" value={2026} readonly />

A readonly input still receives focus and can be copied; its value cannot be edited by typing or by the stepper buttons.

Errors

<.number_input name="age" value={-5} errors={["Age must be 0 or greater."]} />

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

Examples

Cart line item with quantity stepper

<.form :let={f} for={@form} phx-change="update_cart" phx-submit="checkout">
  <.number_input
    field={f[:quantity]}
    label="Quantity"
    min={1}
    max={99}
    step={1}
    size="sm"
  />
</.form>

Currency field with a trailing apply action

<.number_input
  field={@form[:price]}
  label="Price"
  min={0}
  step={0.01}
  locale="en-US"
  format={%{style: "currency", currency: "USD", minimum_fraction_digits: 2}}
>
  <:outer_suffix>
    <.button color="primary" phx-click="apply_price">Apply</.button>
  </:outer_suffix>
</.number_input>

Locale-aware percent with snapping

<.number_input
  field={@form[:tax_rate]}
  label="Tax rate"
  value={0.18}
  min={0}
  max={1}
  step={0.005}
  snap_on_step
  format={%{style: "percent", maximum_fraction_digits: 2}}
/>

Zoom slider with wheel scrub

<.number_input
  name="zoom"
  label="Zoom"
  value={1.0}
  min={0.25}
  max={4}
  step={0.25}
  small_step={0.05}
  large_step={0.5}
  allow_wheel_scrub
  format={%{maximum_fraction_digits: 2}}
>
  <:outer_suffix class="px-2 text-tertiary">x</:outer_suffix>
</.number_input>

Summary

Components

Renders a labeled numeric input with stepper buttons, locale-aware formatting, and form integration.

Components

number_input(assigns)

Renders a labeled numeric input with stepper buttons, locale-aware formatting, and form integration.

Use this for any single-line numeric value where the user benefits from stepping, bounds, or locale-aware display: counters, quantities, prices, percentages, ratings, and configuration knobs. The component renders the visible field, a hidden canonical-value input for form submission, leading and trailing stepper buttons, and the surrounding label, description, helper text, and error messages.

Pass a Phoenix.HTML.FormField via field to bind the input to a changeset-backed form, or pass name and value 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. 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)" or "(optional)".

    Defaults to nil.

  • description (:string) - Longer guidance text rendered between the label and the field. Defaults to nil.

  • help_text (:string) - Helper text rendered below the field. Use for tips, formatting hints, or follow-up instructions.

    Defaults to nil.

  • class (:any) - Extra CSS classes applied to the inner <label data-part="field"> element that wraps the display input and inner affixes.

    Defaults to nil.

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

    • 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 forms and settings panels.
    • xl: 44px tall. For high-emphasis single-field surfaces such as landing-page onboarding.

    Defaults to "md".

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

  • disabled (:boolean) - When true, the field and stepper buttons cannot be interacted with and render with reduced contrast.

    Defaults to false.

  • field (Phoenix.HTML.FormField) - A Phoenix.HTML.FormField struct, typically obtained from a form binding such as f[:price]. 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.

  • value (:any) - The current numeric value. Pass an integer, a float, or a string that parses as a number. Required when field is not set. When field is set, this attribute is ignored in favor of field.value.

  • name (:any) - The submitted name for the underlying form input. Required when field is not set. 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.

    Defaults to [].

  • min (:any) - Minimum allowed value. Pass a number to clamp the value on stepping and (when allow_out_of_range is false) on blur. When omitted, there is no lower bound.

    Defaults to nil.

  • max (:any) - Maximum allowed value. Pass a number to clamp the value on stepping and (when allow_out_of_range is false) on blur. When omitted, there is no upper bound.

    Defaults to nil.

  • step (:any) - Amount added or subtracted on a single ArrowUp / ArrowDown press or stepper button click. Pass a positive number, or the string "any" to disable snap-on-step behavior entirely (keyboard arrows still default to a step of 1).

    Defaults to 1.

    Defaults to 1.

  • small_step (:any) - Step amount used when the user holds Alt while pressing an arrow key or stepper button. Defaults to step / 10 when omitted.

    Defaults to nil.

  • large_step (:any) - Step amount used when the user holds Shift while pressing an arrow key or stepper button. Also used for PageUp and PageDown. Defaults to step * 10 when omitted.

    Defaults to nil.

  • snap_on_step (:boolean) - When true, increment and decrement snap the result to the nearest multiple of step, even when the current value is off-grid. Has no effect when step is "any".

    Defaults to false.

  • allow_out_of_range (:boolean) - When true, typed values outside [min, max] are kept as-is on blur instead of being clamped. Stepper buttons and keyboard stepping always clamp.

    Defaults to false.

  • allow_wheel_scrub (:boolean) - When true, scrolling the mouse wheel over a focused field with Alt held adjusts the value. Requires both focus and the modifier to avoid accidental changes from a scroll.

    Defaults to false.

  • controls (:string) - Controls the rendering of the stepper buttons.

    • flanking (default): renders the minus button on the left of the field and the plus button on the right. Best for everyday numeric entry where users expect pointer-driven stepping.
    • none: omits the buttons. Keyboard stepping, paste, and wheel scrubbing still work. Use when the field is large and stepper buttons would feel out of proportion, or when the page provides its own controls.

    Defaults to "flanking". Must be one of "flanking", or "none".

  • locale (:string) - IETF language tag used by the browser's Intl.NumberFormat for display formatting and for thousands/decimal separator parsing. Examples: "en", "en-US", "de-DE", "pt-BR", "fr", "ja".

    Defaults to "en".

  • format (:any) - A map of options forwarded to the browser's Intl.NumberFormat. Keys are converted from snake_case to camelCase before being passed in. Common options:

    • style: "decimal" (default), "currency", or "percent".
    • currency: the ISO 4217 currency code, used with style: "currency".
    • currency_display: "symbol", "narrowSymbol", "code", or "name".
    • minimum_fraction_digits and maximum_fraction_digits: precision bounds.
    • use_grouping: false to drop the thousands separator.

    With style: "percent", the underlying value is the decimal form: 0.42 renders as "42%". Stepping operates on the underlying value, so a step of 0.01 advances by one percentage point.

    Defaults to %{}.

  • Global attributes are accepted. Any additional HTML attributes forwarded to the visible input element. Common values include placeholder, autocomplete, required, readonly, and Phoenix bindings such as phx-change, phx-debounce, and phx-blur. Supports all globals plus: ["autocomplete", "form", "placeholder", "readonly", "required"].

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 unit hint.Accepts attributes:
    • class (:any) - Extra CSS classes for the inner prefix wrapper.
  • outer_prefix - Content rendered outside the field, between the leading stepper button and the field. Use for static text labels or selectors. The leading corners are rounded so the affix joins as a single control with the field.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 or inline indicators.Accepts attributes:
    • class (:any) - Extra CSS classes for the inner suffix wrapper.
  • outer_suffix - Content rendered outside the field, between the field and the trailing stepper button. Use for static text labels or trailing controls. The trailing corners are rounded so the affix joins as a single control with the field.Accepts attributes:
    • class (:any) - Extra CSS classes for the outer suffix wrapper.