Components

Number Input

A numeric stepper with minus/plus buttons that clamp a value to an optional min, max and step.

php artisan blatui:add number-input

Building a cart or product quantity stepper? Use number-input with :min="1" and a compact size="sm" — see the Quantity selector example below. (A separate quantity-selector component was removed in favour of this; it was the same control with different defaults.)

<x-ui.number-input name="quantity" :value="1" class="w-40" />

Currency Mask

{{-- Cents-style entry for a till or a price form: digits enter from the right, so typing
     1 9 0 shows 1.90 and Backspace drops the last digit. Pasting "1.9" keeps it 1.90. --}}
<div class="grid w-full max-w-xs items-center gap-2">
    <x-ui.label for="amount">Amount</x-ui.label>
    <x-ui.number-input id="amount" name="amount" :value="null" :min="0" :step="0.01" :decimals="2" mask class="w-40" />
</div>

Disabled

<x-ui.number-input name="quantity" :value="3" :disabled="true" class="w-40" />

Linked Fields

{{-- Two fields derived from each other: a price without and with an 18% tax. Each one
     derives the OTHER from number-change, which only the user's own edits fire, so a value
     written into a field never comes back and nothing needs a skip flag. Under Livewire bind
     each to its property with wire:model and the same handlers write through to it. --}}
<div
    x-data="{
        rate: 0.18,
        derive(id, v, factor) {
            Alpine.$data(document.getElementById(id)).value = v === null ? null : this.$blatNumber.round(v * factor, 2);
        },
    }"
    class="grid w-full max-w-md gap-4 sm:grid-cols-2"
>
    <div class="grid gap-2">
        <x-ui.label for="price-net">Price (excl. tax)</x-ui.label>
        <x-ui.number-input
            id="price-net"
            name="price_net"
            :value="10"
            :min="0"
            :step="0.01"
            :decimals="2"
            mask
            x-on:number-change="derive('price-gross', $event.detail.value, 1 + rate)"
        />
    </div>
    <div class="grid gap-2">
        <x-ui.label for="price-gross">Price (incl. tax)</x-ui.label>
        <x-ui.number-input
            id="price-gross"
            name="price_gross"
            :value="11.8"
            :min="0"
            :step="0.01"
            :decimals="2"
            mask
            x-on:number-change="derive('price-net', $event.detail.value, 1 / (1 + rate))"
        />
    </div>
</div>

Min Max

<x-ui.number-input name="guests" :value="2" :min="1" :max="10" class="w-40" />

Quantity Selector

{{-- A cart / product quantity stepper: number-input with a floor of 1 and a
     compact footprint. Replaces the former standalone quantity-selector. --}}
<x-ui.number-input name="quantity" :value="1" :min="1" :max="10" step="1" size="sm" class="w-32" />

Sizes

<div class="flex flex-col items-start gap-4">
    <x-ui.number-input name="qty-sm" size="sm" :value="1" class="w-36" />
    <x-ui.number-input name="qty-default" size="default" :value="1" class="w-40" />
    <x-ui.number-input name="qty-lg" size="lg" :value="1" class="w-44" />
</div>

Step

<x-ui.number-input name="rate" :value="2.5" :step="0.5" :min="0" class="w-40" />

With Decimals

<x-ui.number-input name="price" :value="1.9" :step="0.1" :min="0" :decimals="2" :nullable="false" class="w-40" />

Using with Livewire

wire:model

The examples above are the frontend (Blade + Alpine) usage. Inside a Livewire component, bind wire:model for two-way state — same component, no wrappers. Full Livewire guide →

app/Livewire/Demo.php
use Livewire\Component;

class Demo extends Component
{
    public int $qty = 1;

    public function render()
    {
        return view('livewire.demo');
    }
}
resources/views/livewire/demo.blade.php
<x-ui.number-input wire:model="qty" :min="1" />

API Reference

Props, slots and exposed methods for <x-ui.number-input>.

Props

Prop Type Default Description
name string — Name of the underlying input, used for form submission and as the fallback accessible label when nothing else names the field.
value number 0 Initial numeric value. Pass null or an empty string to start the field empty.
min number — Lowest allowed value. Clamps on blur and disables the decrease button at the floor.
max number — Highest allowed value. Clamps on blur and disables the increase button at the ceiling.
step number 1 Amount each increase/decrease button adds or subtracts.
decimals int — Fixed decimal places. The field shows the value at this precision (1.90, not 1.9) and rounds what it sends. The bound value stays a number, so trailing zeroes are presentation only.
nullable bool true Whether an emptied field is committed as null on blur. Pass false for a field backed by a non-nullable property: the field then reverts to its previous value. Either way nothing is sent while the field is empty mid-edit.
size string 'default' Control height and text size; matches the input component so fields line up in a form.
sm default lg
disabled bool false Disables the field and both stepper buttons.
id string — id attribute of the underlying input, for associating an external label. With an id, a `<label for>` names the field; the fallback label (name, then "Number") is only applied when no label points at it.
placeholder string — Placeholder shown while the field is empty.
mask bool false Cents-style currency mask: digits enter from the right at `decimals` places (2 when unset), so typing 190 shows 1.90 and Backspace drops the last digit. Pasted text with a dot or comma is read as a normal number (1.9 stays 1.90). The bound value is written on every keystroke, client side, so a `$watch` on a plain `wire:model` sees each digit. A digit that would pass `max` is refused; `min`, `nullable` and the stepper buttons behave as without the mask.

Events

Events the component listens for (in) and dispatches (out). Both directions bubble, so .window works even when the component is teleported.

Event Direction Detail
number-change Out { value } The user changed the value: a keystroke that produced a number, a paste, a stepper click, or the commit on blur (value may then be null). A value written from outside, by the server or by another field, never fires it. That is what makes two fields derived from each other (a price with and without tax) safe: derive the other field in this handler and it cannot echo back.