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:
| Component | Best 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} />| Size | Height | Text | Use Case |
|---|---|---|---|
xs | 28px | xs | Dense layouts: filter bars, table cells, inline editors. |
sm | 32px | sm | Toolbars, secondary forms, compact admin UIs. |
md | 36px | sm | Default for most forms. |
lg | 40px | base | Prominent fields: hero forms, settings panels. |
xl | 44px | lg | High-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
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'sid. When omitted, the component falls back to the value ofname(or tofield.idwhenfieldis set) so the rendered<label>can target the input viafor.Defaults to
nil.label(:string) - Primary label text rendered above the field. When omitted, no<label>is rendered. Defaults tonil.sublabel(:string) - Short inline hint rendered alongside the mainlabel. 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 tonil.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) - Whentrue, the field and stepper buttons cannot be interacted with and render with reduced contrast.Defaults to
false.field(Phoenix.HTML.FormField) - APhoenix.HTML.FormFieldstruct, typically obtained from a form binding such asf[:price]. When provided, the input derives itsid,name,value, anderrorsfrom 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 whenfieldis not set. Whenfieldis set, this attribute is ignored in favor offield.value.name(:any) - The submitted name for the underlying form input. Required whenfieldis not set. Whenfieldis set, this attribute is ignored in favor offield.name.errors(:list) - A list of error message strings rendered below the field. Whenfieldis set, errors are pulled fromfield.errorsand translated automatically.Defaults to
[].min(:any) - Minimum allowed value. Pass a number to clamp the value on stepping and (whenallow_out_of_rangeisfalse) 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 (whenallow_out_of_rangeisfalse) on blur. When omitted, there is no upper bound.Defaults to
nil.step(:any) - Amount added or subtracted on a singleArrowUp/ArrowDownpress 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 of1).Defaults to
1.Defaults to
1.small_step(:any) - Step amount used when the user holdsAltwhile pressing an arrow key or stepper button. Defaults tostep / 10when omitted.Defaults to
nil.large_step(:any) - Step amount used when the user holdsShiftwhile pressing an arrow key or stepper button. Also used forPageUpandPageDown. Defaults tostep * 10when omitted.Defaults to
nil.snap_on_step(:boolean) - Whentrue, increment and decrement snap the result to the nearest multiple ofstep, even when the current value is off-grid. Has no effect whenstepis"any".Defaults to
false.allow_out_of_range(:boolean) - Whentrue, 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) - Whentrue, scrolling the mouse wheel over a focused field withAltheld 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'sIntl.NumberFormatfor 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'sIntl.NumberFormat. Keys are converted fromsnake_casetocamelCasebefore being passed in. Common options:style:"decimal"(default),"currency", or"percent".currency: the ISO 4217 currency code, used withstyle: "currency".currency_display:"symbol","narrowSymbol","code", or"name".minimum_fraction_digitsandmaximum_fraction_digits: precision bounds.use_grouping:falseto drop the thousands separator.
With
style: "percent", the underlying value is the decimal form:0.42renders as"42%". Stepping operates on the underlying value, so astepof0.01advances 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 asphx-change,phx-debounce, andphx-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.