## number_field (`poetry_number_field`)

A numeric input with increment and decrement steppers.

Class: Poetry::Ui::NumberField::Component - BEM block `poetry-ui-number_field`.
- `described_by:` (string) - aria-describedby wiring for Field hint/error pairing.
- `disabled:` (boolean) - default false - Disables both inputs and the steppers; the group chrome dims.
- `format:` () - 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) - default 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) - default 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) - default false - Makes the visible input read-only (steppers and typing inert).
- `required:` (boolean) - default false - Requires a value - native validation rides the hidden input.
- `small_step:` (float) - default 0.1 - The Alt-arrow step size (the fine adjustment).
- `snap:` (boolean) - default false - Snaps stepped values to step multiples counted from min:.
- `step:` (float) - default 1.0 - The arrow-key / stepper increment.
- `value:` () - Initial value - a number; nil renders empty (null semantics).
- `wheel:` (boolean) - default false - Opt-in wheel stepping while the input is focused.
- PART `number-field` - Root wrapper - the controller, disabled/invalid/filled state, and the two-input pair ride here | states: data-disabled (disabled: is set (steppers disable, the group chrome dims)); data-invalid (invalid: is set (the group wears the destructive ring via the control's aria-invalid)); data-filled (the value is non-null (the controller keeps it live))
- PART `number-field-group` - The bordered field surface - wears InputGroup's chrome (cn-input-group), focus ring keyed on the control inside
- PART `input-group-addon` - The two stepper cells - InputGroup's addon vocabulary, reused so the group paddings compose | states: data-align=inline-start|inline-end (always - inline-start holds the decrement, inline-end the increment)
- PART `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
- WIRING root: `poetry--core--number-field` registers; values min (if), max (if), step, large_step, small_step, snap (if snap), wheel (if wheel), format (if), locale (if)
- WIRING input: `poetry--core--number-field` actions keydown on keydown, input on input, focus on focus, blur on blur; targets input
- WIRING hidden: `poetry--core--number-field` actions hiddenChanged on change; targets hidden
- WIRING increment: `poetry--core--number-field` actions press on pointerdown, tap on click, leave on pointerleave; targets increment
- WIRING decrement: `poetry--core--number-field` actions press on pointerdown, tap on click, leave on pointerleave; targets decrement
- RULE: Use poetry_number_field / form.number_field - never a hand-rolled spinner or a bare input type=number.
- RULE: The server reads params[<name>] as the raw number string - display formatting (format:) never changes what submits.
- RULE: Steppers are mouse/touch affordances (tabindex -1); keyboard users step with ArrowUp/Down (Shift = large_step, Alt = small_step) on the input itself.
- RULE: Pair it with a Label/Field for the accessible name - the component ships none.
- RULE: format: takes Intl.NumberFormatOptions as a Hash ({ style: "currency", currency: "USD" }); pick locale: to pin parsing separators.
