InputsReact
A numeric input with increment and decrement buttons and a scrub area.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<NumberField.Root>
<NumberField.ScrubArea>
<NumberField.ScrubAreaCursor />
</NumberField.ScrubArea>
<NumberField.Group>
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
</NumberField.Group>
</NumberField.Root>Examples
The demo at the top is the one example this component has so far.
API reference
27 props across 7 parts, in React syntax. className, style, render are listed once, under Shared props.
DecrementNumberField.Decrement
A stepper button that decreases the field value when clicked. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
GroupNumberField.Group
Groups the input with the increment and decrement buttons. Renders a <div>.
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
IncrementNumberField.Increment
A stepper button that increases the field value when clicked. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
InputNumberField.Input
The native input control in the number field. Renders an <input>.
| Prop | Type | Default |
|---|---|---|
aria-roledescriptionA user-friendly description of the input's role for assistive tech. | string | 'Number field' |
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
RootNumberField.Root
Groups all parts of the number field and manages its state. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
nameIdentifies the field when a form is submitted. | string | — |
defaultValueThe uncontrolled value of the field when it's initially rendered. | number | — |
valueThe raw numeric value of the field. | number | null | — |
onValueChangeCallback fired when the number value changes. | ((value: number | null, eventDetails: NumberFieldRootChangeEventDetails) => void) | — |
onValueCommittedCallback function that is fired when the value is committed. | ((value: number | null, eventDetails: NumberFieldRootCommitEventDetails) => void) | — |
allowOutOfRangeWhen true, direct text entry may be outside the min/max range without clamping, | boolean | false |
formIdentifies the form that owns the hidden input. | string | — |
localeThe locale of the input element. | Intl.LocalesArgument | — |
snapOnStepWhether the value should snap to the nearest step when incrementing or decrementing. | boolean | false |
stepAmount to increment and decrement with the buttons and arrow keys, or to scrub with pointer movement in the scrub area. | number | "any" | 1 |
smallStepThe small step value of the input element when incrementing while the alt key is held. | number | 0.1 |
largeStepThe large step value of the input element when incrementing while the shift key is held. | number | 10 |
minThe minimum value of the input element. | number | — |
maxThe maximum value of the input element. | number | — |
allowWheelScrubWhether to allow the user to scrub the input value with the mouse wheel while focused and | boolean | false |
formatOptions to format the input value. | Intl.NumberFormatOptions | — |
disabledWhether the component should ignore user interaction. | boolean | false |
readOnlyWhether the user should be unable to change the field value. | boolean | false |
requiredWhether the user must enter a value before submitting a form. | boolean | false |
inputRefA ref to access the hidden input element. | React.Ref<HTMLInputElement> | — |
idThe id of the input element. | string | — |
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
ScrubAreaNumberField.ScrubArea
An interactive area where the user can click and drag to change the field value. Renders a <span>.
| Prop | Type | Default |
|---|---|---|
directionCursor movement direction in the scrub area. | "horizontal" | "vertical" | 'horizontal' |
pixelSensitivityDetermines how many pixels the cursor must move before the value changes. | number | 2 |
teleportDistanceIf specified, determines the distance that the cursor may move from the center | number | — |
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
ScrubAreaCursorNumberField.ScrubAreaCursor
A custom element to display instead of the native cursor while using the scrub area. This component uses the Pointer Lock API, which may prompt the browser to display a related notification. It is disabled in Safari to avoid a layout shift that this notification causes there. Renders a <span>.
| Data attribute | Description |
|---|---|
data-scrubbing | Present while scrubbing. |
data-disabled | Present when the number field is disabled. |
data-readonly | Present when the number field is readonly. |
data-required | Present when the number field is required. |
data-valid | Present when the number field is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the number field is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the number field has been touched (when wrapped in Field.Root). |
data-dirty | Present when the number field's value has changed (when wrapped in Field.Root). |
data-filled | Present when the number field is filled (when wrapped in Field.Root). |
data-focused | Present when the number field is focused (when wrapped in Field.Root). |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| ArrowUp | Increases the value by step | Input |
| ArrowDown | Decreases the value by step | Input |
| ShiftArrowUpShiftArrowDown | Steps by largeStep | Input |
| AltArrowUpAltArrowDown | Steps by smallStep | Input |
| Home | Sets the value to min | Input |
| End | Sets the value to max | Input |
| Lets through only what the number format accepts | Input |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Input | aria-roledescription="Number field" | a screen reader says it is a number field |
| Input | aria-labelledby to the Field label, aria-invalid when validation fails | the field is read with its name and error state |
| Increment | aria-label="Increase", aria-controls to the input, tabindex="-1" | the button is named and points at the input, without an extra tab stop |
| Decrement | aria-label="Decrease", aria-controls to the input, tabindex="-1" | the same for the other button |
| Group | role="group" | the input and its buttons are read as one control |
| ScrubArea | role="presentation" | the drag area is not read |
Focus order
- Input is the only tab stop; Increment and Decrement are reached with the pointer.