Number field
A number input with minus and plus buttons. Keeps the value within bounds, steps with the keyboard and formats it as currency, percent or a unit.
Installation
Set up Camaleo once (Installation), then import what you need. Put it in a Field to give it a label.
import { NumberField, Field, FieldLabel } from "@camaleo/react";Anatomy
One box with three parts: the minus button, the input and the plus button.
Examples
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Up to 4 tickets per order.
Minimum and maximum
Click plus until it stops. At max the button disables itself, and the description says why before people get there.
5 × $24.00 = $120.00 per month
Live total
Change the seats. value and onValueChange drive the price below, announced with aria-live.
Up to 50%. Shift + arrow jumps 25 points.
Percent, with steps
format={{ style: "percent" }} works on fractions, so step={0.05} is 5 points; largeStep is what Shift + arrow adds.
Currency
Pass format and locale and the input shows the currency symbol and separators people expect, while the value stays a number.
With a unit
A unit format puts "kg" inside the field, so the label doesn't have to say it and nobody guesses pounds.
Required
Type a number, delete it and leave. Inside a Field, required and FieldError match="valueMissing" work like on any input.
Named step buttons
The buttons only show − and +. decrementLabel and incrementLabel give them names, so translate them with the page.
In context
These are whole pieces of UI you can copy, in the skin you picked.
Your cart
$18.00
$16.50
Subtotal$52.50
Book your stay
Lakeside cabin · Oct 31 – Nov 3
Ages 2 to 12
Guidelines
Guests, tickets, seats: a click or an arrow key gets there faster than typing.
ZIP codes, phone and card numbers aren't amounts: steppers and separators break them. Use an Input with inputMode="numeric".
"45 min" can't be misread.
Is 45 minutes, hours or people? Don't make people guess.
Props
min prop explicitly in conjunction with this prop.
Specify step="any" to always disable step validation; interactive stepping then uses a base amount of 1, while the alt and shift keys still step by smallStep and largeStep.snapOnStep is enabled.value prop instead.details.reason says which.Base UI's other root props pass through, such as smallStep, snapOnStep, allowWheelScrub and onValueCommitted.
Styling hooks
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the field itself.
Attributes
Tokens it reads
Color
Shape & size
Type & motion
This component only
/* round step buttons, only in the cart */
.cart [data-slot="number-field"] {
--cml-number-btn-radius: 999px;
--cml-number-divider: 0;
}Accessibility
- The input is a text box announced as a number field (aria-roledescription), named by the label of its Field.
- The step buttons have names from decrementLabel and incrementLabel; they default to English, so pass translated ones.
- At a bound, the matching button is disabled, so it never does nothing silently.
- It sets inputmode="numeric", so phones open the number pad; with a fractional step or format and min ≥ 0 it uses "decimal", which adds the decimal separator.