Skip to content

BbNumberInput

The numeric field: a real number in the model, clamped to min and max.

import { BbNumberInput } from 'bitboss-ui';

On this page

Use it for

Reach for BbNumberInput whenever the value is a number somebody types: a quantity, a price, an age, a threshold, a rate limit. It cleans up what they type, clamps to min and max, and hands you a real number.

Use something else when

  • BbSlider, when a bounded value is better dragged than typed
  • BbRating, when it is a score on a fixed scale
  • BbTextInput with a mask, when the digits are an identifier: a postcode, an order number, anywhere leading zeros matter

Also see: Validated forms.

In v3, numeric text fields move from BbTextInput to BbNumberInput:

diff
- <BbTextInput v-model="quantity" type="number" label="Quantity" />
+ <BbNumberInput v-model="quantity" label="Quantity" />

The shared field props carry across; type does not.

Passthrough

Hover or tap a part to outline it. The toggles turn on loading, errors and warnings where the component has them; only parts get a row.

€EURGuidance while the control is focused.

Default

label is required, and v-model is the whole of the wiring. The model takes number | string | null and the field emits number | null. A cleared field emits null, never '' and never NaN.

Model: 8 · typeof number

label does not set name. Pass name for native form submission.

While the text is incomplete, such as - or 12., the model keeps its last parsed value.

Locale display

The field writes its number the way the app's locale writes numbers, while the user types. In English 10005.6 shows 10,005.6; in an Italian app it shows 10.005,6. Grouping follows each locale, so Italian shows 1000 and starts grouping at 10.000.

Type the digits; the field groups them.
disable-formatting: shown as typed.
Model
10005.6
Posted

Type the digits; the field groups them. Whichever of , or . the user presses starts the decimals, so nobody types a thousands separator. Delete the digits and type 1234,5: the field shows 1,234.5.

  • - typed anywhere flips the sign, and the caret stays on its digit.
  • A paste is read as a number. Written the locale's way, it is read that way: 1,234 pasted here is 1234, and 1.000 pasted in Italian is 1000. The rest is cleaned up. Symbols and words go ($1,234.56 is 1234.56, 15 m² is 15), accounting brackets are negative ((1,234.56) is -1234.56) and 1.2E+05 is 120000. Only the first of several numbers is kept ($1,234.56 - $2,000 is 1234.56).
  • The field holds 20 characters. A key, a paste or an autofill past that is refused and the field keeps its number; nothing is cut short.
  • Blur and Enter tidy the text from the value: 1.50 becomes 1.5 and 007 becomes 7. Keep trailing zeros with min-precision.

The model is always the plain number. A native post reads a hidden input that carries name and the raw value (10005.6); the visible input has no name. disable-formatting shows the number as typed, with a period decimal and no grouping, and puts name back on the visible input.

Code that reads the input's text sees the formatted one: event.target.value, a pt:input listener, a maxlength or a pattern. A test that typed into input[name=quantity] reaches the hidden input, so it targets input[role="spinbutton"] instead. Grouped text is also wider: check a narrow fixed-width field at 200% zoom.

Bounds and precision

min and max clamp the emitted value. Type past max and it settles back at once. min waits until you leave the field, because the digits of a valid number pass through smaller ones: typing 20 into a 16–28 field goes through 2. Enter applies min too, before a surrounding form submits. maxPrecision caps decimal places and defaults to 8; 0 pins the field to whole numbers. minPrecision is its twin: with 2 the field always shows two decimals (1 shows 1.00), padding with zeros the user types over, while the model stays 1.

Between 16 and 28 °C, whole degrees.
Two decimals, always shown. A third is refused.

Model: setpoint 21, tolerance 0.5

Clamping is silent, so state the range in hint.

Extra decimals are refused as they are typed, never rounded:

  • With max-precision="0" a typed , or . stays in the text until blur, and the digits after it are refused: 12.5 is 12, not 125. A pasted 12.5 is cut to 12.
  • Below 3 decimals (max-precision 0 to 2, and every currency field) a second , or . is refused once a decimal exists. 1,234 typed into the tolerance field above is 1.23. A field with 3 or more decimals keeps every digit instead, and reads the earlier mark as grouping when a later one arrives: 1.234,56 typed key by key is 1234.56.
  • Backspace walks back over the padding. At 1.00| it moves to 1|.00 and keeps the value; the next one deletes the digit.

On an iPhone, a field that can be negative (no min, or one below 0) gets a keypad with a minus key. Set min to 0 or more and it keeps the decimal keypad.

Step controls

step (default 1) is the increment applied by ArrowUp and ArrowDown, and by the increase() / decrease() functions the slots hand you. Stepping respects min, max and maxPrecision, and does nothing while the field is disabled or readonly.

Quill Whiteboard 120×90

€179.00 each · 5 in stock

Subtotal €179.00

Every content slot receives { increase, decrease }. Give icon-only step buttons an accessible name.

step affects arrows and buttons, not typed input. Validate required multiples with errors.

Coming from v2increase() / decrease() moved to the slots

The imperative handle is gone. inputRef.value.increase() no longer exists, and a stale call is undefined at runtime rather than a compile error. Move in-field steppers to the slot scope shown above, and drive an external stepper through the model (quantity += step).

Currency, units and icons

Money and measurements want a symbol in the field and a plain number in the model. currency takes an ISO 4217 code and unit a unit Intl.NumberFormat knows (kilogram, celsius, percent). Either one puts the symbol inside the text, where the locale writes it: €69.00 in English, 69,00 € in Italian, CHF-1’234.56 in Swiss German.

Submitted as {"price":69,"weight":1.4,"discount":15}

  • currency brings its decimals: 2 for EUR, 0 for JPY, 3 for KWD. min-precision and max-precision win when you set them.
  • unit="percent" holds the number you see: 15% is 15, not 0.15.
  • One or the other. With both, currency wins. unit is typed, so kilowatt or square-meter is a type error.
  • The caret stays on the number. A click, Home, End or an arrow on the symbol lands on the nearest digit, and the symbol alone cannot be deleted. Clearing the last digit empties the field, with no lone € left behind.
  • A pasted amount keeps its number. €1,234.56 and 1,234 kg are read as 1234.56 and 1234; another currency's symbol is dropped, with no conversion.
  • The unit follows the number's plural (1 day, 2 days), and at rest a screen reader hears the amount spelled out (69.00 euros).

The symbol is part of the input's text, so a maxlength counts it. prefix and suffix stay free for anything else; keep currency symbols out of them, since currency places them the locale's way. prepend:icon and append:icon put an icon in the same ring.

The end of the field shows one thing at a time, by priority: clear button → spinner (loading) → error icon (hasErrors) → your append:icon. Put the decorative icon in prepend:icon, and keep append:icon for fields that are neither clearable nor validated.

Put interactive content in prepend, append, or the outer slots.

Hints, errors and warnings

description is always visible between the label and the field. hint appears below the field on focus, and persistent-hint pins it there: the right home for the range or the unit. errors renders below the field, is announced, and sets aria-invalid. warnings is its amber counterpart for a number that is valid and still worth a second look. Type 12 for the error, then 180 for the warning; with both, only the error shows.

5 to 200 on the Team plan.

Non-empty messages imply their visual state. Errors win over warnings and set aria-invalid; warnings do not.

This is where a business rule lives. min and max can only express a range. A multiple, a value tied to another field, a limit that depends on the selected plan: you compute those and put them in errors.

Inside a BbForm

Inside a BbForm the field joins the form under its name and takes rules. Rule messages render through the errors surface above, merged with your own.

Quill Whiteboard 120×90

Sold singly. Delivered in two days.

@submit receives { quantity: 3 }, a number. Spend rules on what bounds do not cover. The usual one is that a value exists: write rules="required" (or require the key in the schema). The required prop is for a field outside a form. On a field joined to one, nothing enforces it and bitboss-ui check reports it.

min lifts a low value when the field is left or Enter is pressed in it, before the form submits, so a min_value rule is redundant. Type 0 and press Enter: @submit receives { quantity: 1 }. A native post carries the same raw number. With a schema, write z.number('Enter a quantity'), not a coercion: the field already holds number | null, and the message covers the empty case. formless and validation-label work as on BbTextInput.

Clearable, loading, disabled and readonly

loading only shows a spinner; the field stays editable. disabled and readonly are the two that stop edits.

Checked against the price list after each change.

clearable emits null. disabled blocks all interaction, while readonly keeps the value focusable and copyable.

Label modes and density

label-mode puts the label above the field (outside, the default), resting inside it like a placeholder (floating), or pinned small at the top of the field (inside).

Leave label-mode unset to use defaultInputLabelMode. Do not pair floating with a placeholder. hide-label keeps the accessible name.

variant works as on BbTextInput, including the blank none. compact reduces the control height. layout="horizontal" puts the label beside the field, so in a settings panel each limit reads as one dense row, label left and value right.

API rate limits

Applied per key, effective immediately.

layout works only with the resolved outside label mode. Embedded label modes force a vertical layout.

Coming from v2floating → label-mode

A bare floating attribute was never a prop on this family. It fell through $attrs onto the root element and did nothing. The spelling is label-mode="floating". And if your stylesheets or tests target the shared input wrapper, .bb-common-input-inner-container* was renamed to .bb-field-input*.