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
With Step
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.
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; typing19,99is rejected. - A German / French / Italian locale user typing accepts BOTH
19.99AND19,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
Size Variants
Error State
Age must be between 1 and 120
With Hint
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.
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">witharia-label="Increment"/aria-label="Decrement"andtabindex="-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"andaria-describedbyset whenerrororhintare presentmin/maxconstraints 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/maxfor bounded ranges. Without them,steparrows 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 apatternattribute 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