Number Field
The Number Field component provides users with a field for number values, with stepper buttons and a scrub area to increment or decrement the value.
Introduction
A number field is a UI element that accepts numeric values from the user. NumberField
is a customizable replacement for the native HTML <input type="number">
that solves some usability and visual issues while enhancing its functionality.
Component
import { NumberField } from '@base_ui/react/NumberField';
Anatomy
The NumberField
component is composed of a root component and a group component which contains an input, and optionally, an increment button, decrement button, and a scrub area with a virtual cursor:
<NumberField>
<NumberField.Group>
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
<NumberField.ScrubArea>
<NumberField.ScrubAreaCursor />
</NumberField.ScrubArea>
</NumberField.Group>
</NumberField>
Custom structure
Use the render
prop to override the rendered elements with your own components:
<NumberField render={(props) => <MyNumberField {...props} />}>
{/* Subcomponents */}
</NumberField>
All subcomponents accept the render
prop.
To ensure behavior works as expected:
- Forward all props: Your component should spread all props to the underlying element.
- Forward the
ref
: Your component should useforwardRef
to ensure the Number Field components can access the element via a ref.
A custom component that adheres to these two principles looks like this:
const MyNumberField = React.forwardRef(function MyNumberField(props, ref) {
return <div ref={ref} {...props} />;
});
Value
The value
prop holds the number value, and onChange
is called when it updates:
function App() {
const [value, setValue] = useState(0);
return (
<NumberField value={value} onChange={setValue}>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField>
);
}
This is the controlled way of handling the number field.
Default value
When the number field is uncontrolled, the defaultValue
prop sets the initial value of the input:
<NumberField defaultValue={10}>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField>
Min and max values
To prevent the value from going below or above a certain amount, the min
and max
props can be used:
<NumberField min={0} max={100}>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField>
Step
The step
prop snaps values of the input to ones that are multiples of the given number, affecting how the stepper buttons change the value:
<NumberField step={5} min={2}>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField>
In the above example, the numbers are snapped to multiples of step
starting from the min
value: 2
, 7
, 12
, 17
and so on.
The largeStep
and smallStep
props can be specified to change the step when a modifier key is held:
largeStep
is used when shift is held, incrementing and snapping to multiples of10
.smallStep
is used when alt is held, incrementing and snapping to multiples of0.1
.
<NumberField step={5} largeStep={50} smallStep={0.5}>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField>
Format
The format
prop accepts Intl.NumberFormat
options to customize the formatting of the input value:
Scrubbing
The ScrubArea
subcomponent lets users scrub the value with their pointer as a faster alternative to the stepper buttons. This is useful in high-density UIs, such as an image editor that changes the width, height, or location of a layer:
The pointer is locked while scrubbing, allowing the user to scrub infinitely without hitting the window boundary. Since this hides the cursor, you can add a virtual cursor asset using the NumberField.ScrubAreaCursor
subcomponent, which automatically loops around the boundary:
<NumberField.ScrubArea direction="horizontal" style={{ cursor: 'ew-resize' }}>
<label htmlFor={id} style={{ cursor: 'unset' }}>
Scrub
</label>
<NumberField.ScrubAreaCursor>
<span style={{ filter: 'drop-shadow(2px 0 2px rgb(0 0 0 / 0.3))' }}>
<svg
width="26"
height="14"
viewBox="0 0 24 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
shapeRendering="crispEdges"
>
<path
d="M19.3382 3.00223V5.40757L13.0684 5.40757L13.0683 5.40757L6.59302 5.40964V3V1.81225L5.74356 2.64241L1.65053 6.64241L1.28462 7L1.65053 7.35759L5.74356 11.3576L6.59302 12.1878V11L6.59302 8.61585L13.0684 8.61585H19.3382V11V12.1741L20.1847 11.3605L24.3465 7.36049L24.7217 6.9999L24.3464 6.63941L20.1846 2.64164L19.3382 1.82862V3.00223Z"
fill="black"
stroke="white"
/>
</svg>
</span>
)}
</NumberField.ScrubAreaCursor>
</NumberField.ScrubArea>
In your CSS, ensure any label
elements inside the ScrubArea
specify cursor: unset
. You can rotate the above macOS-style cursor 90 degrees using a transform
style.
Teleport distance
To teleport the virtual cursor closer to the input rather than the entire viewport, use the teleportDistance
prop:
<NumberField.ScrubArea teleportDistance={200}>
<NumberField.ScrubAreaCursor />
</NumberField.ScrubArea>
This specifies in pixels the distance the cursor can travel around the center of the scrub area element before it loops back around.
Wheel scrubbing
To allow the input to be scrubbed using the mouse wheel, add the allowWheelScrub
prop:
Hook
import { useNumberField } from '@mui/base/useNumberField';
The useNumberField
hook lets you apply the functionality of a Number Field to a fully custom component.
It returns props to be placed on the custom component, along with fields representing the component's internal state.
Accessibility
Ensure the number field has an accessible name via a label
element.