Number Field
A numeric input with increment and decrement steppers.
Installation
Included in poetry-ui — available as
poetry_number_field
the moment you've installed Poetry,
with no per-component step. To own the source and edit it, copy it into your app:
bin/rails g poetry:add number-field
Default
<%= poetry_number_field(name: "quantity", label: "Quantity", value: 5, min: 0, max: 100, class: "w-56") %>
Currency
<%= poetry_number_field(name: "price", label: "Price", value: 1234.5, min: 0, step: 0.5,
format: { style: "currency", currency: "USD" }, locale: "en-US", class: "w-56") %>
Decimal steps
<%= poetry_number_field(name: "opacity", label: "Opacity", value: 0.5, min: 0, max: 1, step: 0.1, class: "w-56") %>
API
Poetry::Ui::NumberField::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| described_by: | String | aria-describedby wiring for Field hint/error pairing. | |
| disabled: | Boolean | defaults to false |
Disables both inputs and the steppers; the group chrome dims. |
| format: | Object | Intl.NumberFormatOptions for the DISPLAY (submission stays raw). | |
| id: | String | The visible input's dom id - the seam a Label's for_id: points at. | |
| invalid: | Boolean | defaults to false |
Marks the field invalid (aria-invalid on the visible input; the group wears the destructive ring). |
| label: | String | Standalone accessible name -> aria-label on the visible input. Inside a form, the Field label wires ids instead - pass neither and pair with poetry_label/form. | |
| large_step: | Float | defaults to 10.0 |
The Shift-arrow step size (the coarse jump). |
| locale: | String | Locale tag pinning the display and parsing separators; the page locale otherwise. | |
| max: | Float | The upper clamp for stepping and native validation. | |
| min: | Float | The lower clamp for stepping and native validation. | |
| name: | String | required | The submitted field name - rides the hidden number input. |
| placeholder: | String | Placeholder text for the empty input. | |
| readonly: | Boolean | defaults to false |
Makes the visible input read-only (steppers and typing inert). |
| required: | Boolean | defaults to false |
Requires a value - native validation rides the hidden input. |
| small_step: | Float | defaults to 0.1 |
The Alt-arrow step size (the fine adjustment). |
| snap: | Boolean | defaults to false |
Snaps stepped values to step multiples counted from min:. |
| step: | Float | defaults to 1.0 |
The arrow-key / stepper increment. |
| value: | Object | Initial value - a number; nil renders empty (null semantics). | |
| wheel: | Boolean | defaults to false |
Opt-in wheel stepping while the input is focused. |
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=number-field] | Root wrapper - the controller, disabled/invalid/filled state, and the two-input pair ride here |
| [data-slot=number-field-group] | The bordered field surface - wears InputGroup's chrome (cn-input-group), focus ring keyed on the control inside |
| [data-slot=input-group-addon] | The two stepper cells - InputGroup's addon vocabulary, reused so the group paddings compose |
| [data-slot=input-group-control] | The visible formatted <input type=text> - InputGroup's control slot (the themes' focus-ring hook); aria-roledescription "Number field", never a spinbutton |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| number-field | data-disabled | disabled: is set (steppers disable, the group chrome dims) | — |
| number-field | data-invalid | invalid: is set (the group wears the destructive ring via the control's aria-invalid) | — |
| number-field | data-filled | the value is non-null (the controller keeps it live) | — |
| input-group-addon | data-align | always - inline-start holds the decrement, inline-end the increment | inline-start · inline-end |
Wiring
The Stimulus surface each element carries — declared in the component, verified against rendered DOM in CI. Bare actions fire on the element's default event.
| Element | Controller | Wiring |
|---|---|---|
| root | poetry--core--number-field | registers · value min (if) · value max (if) · value step · value large_step · value small_step · value snap (if snap) · value wheel (if wheel) · value format (if) · value locale (if) |
| input | poetry--core--number-field | keydown on keydown · input on input · focus on focus · blur on blur · target input |
| hidden | poetry--core--number-field | hiddenChanged on change · target hidden |
| increment | poetry--core--number-field | press on pointerdown · tap on click · leave on pointerleave · target increment |
| decrement | poetry--core--number-field | press on pointerdown · tap on click · leave on pointerleave · target decrement |