Skip to main content
Copy for LLM

Number Input

The <x-wirekit::number-input> component wraps a native <input type="number"> with custom increment/decrement stepper buttons. Native browser spinners are hidden in favor of styled + and - buttons that match the WireKit design system.

Basic Usage

Basic Number Input

With Step

Quantity in packaging units

Decimal Input

Pass a fractional step to allow decimal entry. Common values: step="0.01" for currency / two-decimal precision, step="0.1" for measurements with one decimal, step="0.001" for engineering precision.

Currency input — two decimal places
Measurement — one decimal place

Decimal separator: dot vs comma

The native <input type="number"> always submits form values using the dot notation (19.99, never 19,99) — this is the W3C contract and is consistent across every browser. Backend code reads request('tax_rate') as a dot-formatted string regardless of the user's locale.

For typing, modern browsers respect the user's OS / browser locale:

  • An English-locale user typing into the input accepts 19.99; typing 19,99 is rejected.
  • A German / French / Italian locale user typing accepts BOTH 19.99 AND 19,99. The browser internally normalizes the comma to a dot before submitting.
  • Mobile keyboards on iOS / Android show the appropriate decimal separator on the numeric keypad based on the device locale.

If you need stricter control over the displayed decimal separator (for example, always-comma display in a German-only app even when a user's browser locale differs), wrap the value in a Livewire computed property and format with PHP's number_format($value, 2, ',', '.') before binding to a regular text input. The number-input stepper UI is locale-agnostic at the input level by design — it follows browser convention.

With Prefix & Suffix

Prefix and Suffix

Size Variants

Three Sizes

Error State

Number input with an error message

Age must be between 1 and 120

With Hint

Hint Text

Maximum 10 guests per reservation

Width

Like all WireKit form components, the number input fills its container (w-full). Control the width via the parent element — number inputs are typically narrow:

<div class="max-w-[10rem]">
    <x-wirekit::number-input label="Qty" name="qty" :value="1" :min="0" :max="99" />
</div>

See Input — Width for more layout examples (grid columns, mixed widths).

Livewire Integration

Bind the field with wire:model and it shows the bound value from the first render, follows a value your component sets on the server while the field stays on the page, and sends both typing and stepper clicks to the property:

<x-wirekit::number-input label="Budget" wire:model.live="budget" min="0" />

With a model on the field, the model owns the input and the steppers follow it. The model stays on the input itself, and bound to a property declared int or float it reads the field as a number: wire:model.live goes out as wire:model.number.live, so the typed number the server sends back is not taken for a change that overwrites what the reader entered while the request was out. A property without a type, or a string one, gets the text, and the other modifiers behave as they do on a plain <input type="number">. An Alpine x-model works the same way, and without any model the value prop seeds the field.

Optimistic UI

Pass the name of the Livewire method the field should call and the value is shown before the server answers — on a stepper click at once, and for the field when you leave it:

<x-wirekit::number-input
    name="quantity"
    label="Quantity"
    :value="$quantity"
    optimistic="saveQuantity"
/>

Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:

@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}" defer></script>

Try it

The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.

Optimistic number — accepted, refused, and a slow answer

The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo methods so the page can show a real round trip. The block under it is what you write.

A refusal keeps the value — for the steppers too, and that is deliberate. Stepping is a discrete choice, so putting it back would normally cost you nothing. But this control also has a field you can type in, and a number typed while a stepper's request is still out would be overwritten when that request rolls back. Rather than let safety depend on whether you happened to be typing, neither half undoes: the value stays and says it was not saved.

The field is not marked invalid. aria-invalid means this value is wrong, and a save that failed on the network says nothing about the value.

Props

Prop Type Default Description
label string|null null Label text above the input
hideLabel bool false Render the label visually hidden (sr-only) but keep it for assistive tech — for a compact stepper in a toolbar or a table cell
hint string|null null Help text below the input
help string|null null An explanation of the field, shown in a tooltip from a question mark beside the label and read as the field's description. Needs a label to stand beside. See Help beside the label
error string|null null Error message (also reads from $errors)
name string|null null Form field name
id string|null auto-generated Element id
size string 'md' 'sm', 'md', 'lg'
min int|float|null null Minimum allowed value
max int|float|null null Maximum allowed value
step int|float 1 Increment/decrement step
value int|float|null null Initial value
prefix string|null null Text/icon inside left
suffix string|null null Text/icon inside right
disabled bool false Disables the field and its − and + buttons. A readonly field keeps its buttons out of use as well
scope string|null null Scoped personalization key
optimistic string|null null Livewire method to call when you leave the field or use the steppers, showing the new number as saving. A refusal keeps your number and says it was not saved. See Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.
announceError bool|null null Render the error message in an aria-live="polite" region so a validation error that appears after render reaches a screen reader. Precedence: this prop, then the parent <x-wirekit::form :announce-errors>, then config('wirekit.a11y.announce_error'). Set false when the page runs its own live region.

Slots

Slot Description
labelAfter Markup after the label and its help question mark, in the same row and outside both, such as a badge that says a setting is not decided yet. See A Mark After the Label.

Accessibility

  • Uses native <input type="number"> — inherits browser validation and AT support
  • Stepper buttons are <button type="button"> with tabindex="-1" and aria-controls pointing at the field — not in the tab order, because ArrowUp / ArrowDown on the input step to the same grid points, and the native spinner these buttons replace is not a tab stop either
  • Their accessible name carries the field's label when you pass one — Decrease Quantity / Increase Quantity — so a page with several number fields does not expose a row of identically named controls to a screen reader reading it in browse mode. Without a label they take the field's aria-label, and without either they read Decrease / Increase
  • Native browser spinners are hidden via CSS to avoid double controls
  • Arrow Up/Down keys increment/decrement by step (native behavior)
  • aria-invalid="true" and aria-describedby set when error or hint are present
  • min and max hold when the field is left: a typed value outside them is set to the nearest bound, an emptied field to min (or 0), and a value finer than step is rounded to it. The field then says what it did, under itself and to a screen reader, "Set to 99, the highest value allowed.", until the next keystroke. The steppers stop at the bounds
  • Stepper icons are aria-hidden="true" — the button labels provide the accessible name

Keyboard Interaction

Key Action
Tab Move focus to the input
ArrowUp / ArrowDown Increment / decrement by step
Numeric keys Type the value directly
Home / End Move caret to start / end

Pitfalls

  • Don't omit min / max for bounded ranges. Without them, step arrows can produce invalid values that fail server-side validation — confusing for the user.
  • Don't use number-input for codes / IDs. Phone numbers, OTPs, postal codes are STRINGS. Use <x-wirekit::input> with a pattern attribute to keep leading zeros and avoid scientific notation.

Design Tokens

Token Purpose
--color-wk-bg-input Input background
--color-wk-border-strong Default border color
--color-wk-border-error Border color on error
--color-wk-text Input text color
--color-wk-text-placeholder Placeholder text color
--color-wk-text-muted Prefix/suffix text color
--color-wk-bg-active Stepper button background on hover
--color-wk-ring Focus ring color
--size-wk-sm / md / lg Input height per size variant
--radius-wk-md Border radius
--transition-wk-duration Hover/focus transition speed
--opacity-wk-disabled Dimmed state when disabled

Customization

Override defaults in config/wirekit.php:

'components' => [
    'number-input' => ['size' => 'md', 'step' => 1],
],

See Also

  • Inline Edit — edit this value in place, with an explicit confirm step

Further Reading

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store