Skip to main content
WireKit
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

<x-wirekit::number-input> keeps its displayed value in internal state seeded from the value prop, so when binding with Livewire pass the bound property as :value alongside wire:model to seed the initial display:

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

Without :value the field shows min (or 0) until the user interacts; wire:model keeps it in sync afterward. This is the framework-agnostic seeding pattern WireKit's stateful controls share — it works in plain Blade forms too.

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') }}"></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
hint string|null null Help text below the input
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 Disabled state
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.

Accessibility

  • Uses native <input type="number"> — inherits browser validation and AT support
  • Stepper buttons are <button type="button"> with aria-label="Increment" / aria-label="Decrement" and tabindex="-1" (not in tab order — the input itself handles keyboard stepping)
  • 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/max constraints enforced both by native validation and stepper button disabling
  • 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-accent Stepper button hover accent
--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

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store