Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

InputsReact

Number Field

A numeric input with increment and decrement buttons and a scrub area.

Rendered by React 19.3.1

Loading React…
import * as React from 'react'
import { NumberField } from '@hamur/react/number-field'

export default function NumberFieldExample() {
  const [quantity, setQuantity] = React.useState<number | null>(2)
  return (
    <div className="stack">
      <NumberField.Root
        className="stack"
        defaultValue={2}
        min={1}
        max={10}
        onValueChange={setQuantity}
      >
        <NumberField.ScrubArea className="scrub">
          <span>Quantity</span>
          <NumberField.ScrubAreaCursor className="scrub-cursor">
            ↔
          </NumberField.ScrubAreaCursor>
        </NumberField.ScrubArea>
        <NumberField.Group className="nf-group">
          <NumberField.Decrement className="button" aria-label="Decrease">
            −
          </NumberField.Decrement>
          <NumberField.Input className="input nf-input" aria-label="Quantity" />
          <NumberField.Increment className="button" aria-label="Increase">
            +
          </NumberField.Increment>
        </NumberField.Group>
      </NumberField.Root>
      <p className="muted">Quantity is {quantity ?? 'empty'}</p>
      <NumberField.Root
        defaultValue={19.99}
        step={0.5}
        locale="en-US"
        format={{ style: 'currency', currency: 'USD' }}
      >
        <NumberField.Group className="nf-group">
          <NumberField.Decrement
            className="button"
            aria-label="Lower the price"
          >
            −
          </NumberField.Decrement>
          <NumberField.Input className="input nf-input" aria-label="Price" />
          <NumberField.Increment
            className="button"
            aria-label="Raise the price"
          >
            +
          </NumberField.Increment>
        </NumberField.Group>
      </NumberField.Root>
    </div>
  )
}
1,397Tests passing in React · 0 failing
16.3 KiBSize
Not measured yetMemory
Not measured yetSpeed
productdevbook and 22 upstream authors7 commits · last

Installation

npm i @hamur/react

Not 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>.

Decrement props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
Decrement data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent 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>.

Group data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent 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>.

Increment props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
Increment data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent when the number field is focused (when wrapped in Field.Root).

InputNumberField.Input

The native input control in the number field. Renders an <input>.

Input props
PropTypeDefault
aria-roledescriptionA user-friendly description of the input's role for assistive tech.string'Number field'
Input data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent 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>.

Root props
PropTypeDefault
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,booleanfalse
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.booleanfalse
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.number0.1
largeStepThe large step value of the input element when incrementing while the shift key is held.number10
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 andbooleanfalse
formatOptions to format the input value.Intl.NumberFormatOptions—
disabledWhether the component should ignore user interaction.booleanfalse
readOnlyWhether the user should be unable to change the field value.booleanfalse
requiredWhether the user must enter a value before submitting a form.booleanfalse
inputRefA ref to access the hidden input element.React.Ref<HTMLInputElement>—
idThe id of the input element.string—
Root data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent 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>.

ScrubArea props
PropTypeDefault
directionCursor movement direction in the scrub area."horizontal" | "vertical"'horizontal'
pixelSensitivityDetermines how many pixels the cursor must move before the value changes.number2
teleportDistanceIf specified, determines the distance that the cursor may move from the centernumber—
ScrubArea data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent 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>.

ScrubAreaCursor data attributes
Data attributeDescription
data-scrubbingPresent while scrubbing.
data-disabledPresent when the number field is disabled.
data-readonlyPresent when the number field is readonly.
data-requiredPresent when the number field is required.
data-validPresent when the number field is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the number field is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the number field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the number field's value has changed (when wrapped in Field.Root).
data-filledPresent when the number field is filled (when wrapped in Field.Root).
data-focusedPresent when the number field is focused (when wrapped in Field.Root).

Shared props

Every part that renders an element takes these.

Shared props
PropType
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

Keyboard
KeysDoesPart
ArrowUpIncreases the value by stepInput
ArrowDownDecreases the value by stepInput
ShiftArrowUpShiftArrowDownSteps by largeStepInput
AltArrowUpAltArrowDownSteps by smallStepInput
HomeSets the value to minInput
EndSets the value to maxInput
Lets through only what the number format acceptsInput

Accessibility

What the parts set
PartSetsSo that
Inputaria-roledescription="Number field"a screen reader says it is a number field
Inputaria-labelledby to the Field label, aria-invalid when validation failsthe field is read with its name and error state
Incrementaria-label="Increase", aria-controls to the input, tabindex="-1"the button is named and points at the input, without an extra tab stop
Decrementaria-label="Decrease", aria-controls to the input, tabindex="-1"the same for the other button
Grouprole="group"the input and its buttons are read as one control
ScrubArearole="presentation"the drag area is not read

Focus order

  1. Input is the only tab stop; Increment and Decrement are reached with the pointer.