import { NumberInput } from '@uhg-abyss/web/ui/NumberInput';() => { const [value, setValue] = useState('5');
return ( <NumberInput label="NumberInput Sandbox" value={value} onChange={(e) => setValue(e)} /> );};useForm (recommended)
Using the useForm hook lets the DOM handle form data.
Using useForm and FormProvider simplifies form management by providing out-of-the-box validation, form state handling, and performance optimizations, leveraging the power of react-hook-form.
useState
Using the useState hook gets values from the component state.
Display properties
Label
Use the label prop to display a label above the input. To hide the input label set hideLabel to true.
Use isRequired for further customization.
Note: If using useForm, do not use isRequired. The same functionality can be achieved with required: true in validators.
Helper
Use the helper prop to display a help icon next to the label. Simply passing a string value will render the default helper, a Tooltip containing that string. The helper can be customized by passing in a node. It is recommended to use either a Tooltip or a Popover. See When should I use a Tooltip vs. a Popover? for more information on best practices regarding the two.
Subtext
Use the subText prop to display helpful information related to the input field. The prop accepts either a string or an object of the form:
{ text: string; position: 'above' | 'below';}The position property determines where the subtext will be displayed in relation to the input field. The default value is 'below'.
Where appropriate, use it for detailing the increment/step value, as shown in the example below. When doing so, avoid using abbreviations like "Inc." or "Dec." and instead use full descriptive terms like "increment" or "decrement".
Stepper only
Use the stepperOnly prop to disable the input field and only allow value changes through the increment and decrement buttons. The default is false.
Note: When using stepperOnly, the input field is still accessible through keyboard navigation.
Width
Use the width prop to set the width of the number input field. The default value is 48px. A width value should be chosen to allow the maximum value to be fully visible, but the default behavior is an ellipsis displayed to signify overflow.
Validation
Validators (useForm)
Use the validators prop to display a custom error below the number input field or do other validation when using useForm.
Note: The default error message when required is true is minimally acceptable for accessibility. It is highly recommended to customize it to be more specific to the use of the field and form.
Error message
Use the errorMessage prop to display a custom error message below the input field when using useState.
Success message
Use the successMessage prop to display a custom success message below the input field. To provide a single success message across all form input components using useForm/FormProvider, you can provide successMessage to FormProvider as shown here.
Highlighted
Use the highlighted prop to enable a distinct background color when fields are required. To supply this across all form input components using useForm/FormProvider, you can provide highlighted to FormProvider as shown here.
Min and max values
Use the minValue and maxValue props to apply min/max limits to the number input field. The default minValue is -9007199254740991 and maxValue is 9007199254740991.
When minValue and maxValue are provided, input will be validated against the min/max values upon blur. For example, if the maxValue is 5, and the user types 7, 5 will replace 7.
Step value
Use the step prop to increase and decrease the value by the given step. The default value is set to 1.
Masks
Mask config
Use the maskConfig prop to pass a masking configuration to the number input field. The default value for decimalScale is 0.
We use react-number-format internally for mask configuration. Please visit the provided link for more details on the available options.
Decimal step value
Use the decimalScale prop within maskConfig and set to a value greater than 0 to allow decimal values within the number input field. The step prop can also be used to increase/decrease by decimal values.
NumberInput Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
className | string | - | - | CSS class name to apply to each element within the component |
css | Abyss.CSSProperties | Partial<Record<`abyss-${T}`, Abyss.CSSProperties>> | - | - | Object containing CSS styling to apply; uses the classes listed in the "Integration" tab of the component's documentation |
data-testid | string | - | - | Suffix used to create a unique ID used for automated testing |
disableDefaultProviderProps | boolean | false | - | If set to true, the component will not use the DefaultPropsProvider values. If you aren’t using the DefaultPropsProvider, this prop can be ignored. |
errorMessage | string | never | - | - | Error message to display for the input. Only used when not in a FormProvider. errorMessage should not exist in useForm mode. |
helper | React.ReactNode | - | - | Helper element next to label |
hideLabel | boolean | false | - | If true, the label will be visually hidden |
highlighted | boolean | false | - | If true, the input field will be highlighted |
isDisabled | boolean | false | - | If true, the input will be disabled |
isRequired | boolean | never | - | - | Whether the input is required. Only used when not in a FormProvider. isRequired should not exist in useForm mode. |
label | string | - | The label for the input | |
maskConfig | NumericFormatProps | - | - | Mask configuration for the input field NumericFormatProps is only used when mask is 'numeric' |
maxValue | number | 'Number.MAX_SAFE_INTEGER' | - | The maximum value for the number input |
minValue | number | '-Number.MAX_SAFE_INTEGER' | - | The minimum value for the number input |
model | never | string | - | - | model should not exist in useState mode. Model name for form validation. Only used when in a FormProvider. |
onBlur | React.FocusEventHandler<HTMLInputElement> | - | - | Callback function executed when the input is blurred |
onFocus | React.FocusEventHandler<HTMLInputElement> | - | - | Callback function executed when the input is focused |
step | number | 1 | - | The step value for the number input |
stepperOnly | boolean | false | - | Whether to use the stepper buttons only or also use the input field |
subText | string | { text: string; position: SubTextPosition; } | - | - | Additional descriptive text for the input |
successMessage | string | - | - | Success message to display for the input |
validators | never | RegisterOptions | - | - | validators should not exist in useState mode. Validators for the input. Only used when in a FormProvider. |
value | ValueType | never | - | - | The value of the input. Only used when not in a FormProvider. value should not exist in useForm mode. |
Below are the link(s) to the relevant GitHub type files:
Abyss.d.tsNumberInput Classes
| Class Name | Description |
|---|---|
| .abyss-number-input-root | NumberInput root container |
| .abyss-number-input-wrapper-container | NumberInput wrapper container |
| .abyss-number-input-label | NumberInput label |
| .abyss-number-input | NumberInput input field |
| .abyss-number-input-helper | NumberInput helper text |
| .abyss-number-input-sub-text | NumberInput subtext |
| .abyss-number-input-decrement | NumberInput decrement button |
| .abyss-number-input-increment | NumberInput increment button |
| .abyss-number-input-form | NumberInput form wrapper |
| .abyss-number-input-descriptors | NumberInput descriptors |
NumberInput is a spinbutton
NumberInput is an implementation of the HTML5 <input type=”number”> and WAI-ARIA spinbutton pattern .
Spinbutton “single field” implementation
Standard spinbutton functionality (both in HTML5 and WAI-ARIA) is provided by the up and down arrows. This makes the increment and decrement buttons redundant for keyboard operation. This same approach is applied in NumberInput.
This is why in NumberInput the visible [-] and [+] do not receive keyboard focus.
If they received focus the field would:
- become a grouping of one field and two buttons -- making it a non-standard for spinbutton
- more cumbersome to use -- adding extra tab stops for unnecessary buttons
- require lengthier announcements for grouping and value
Button behavior on click: set focus to field
The buttons do remain clickable/tappable for mouse/touch operation. When selected, NumberInput sets focus to the field to provide missing context to the announced button.
This provides the expected behavior for HTML5 type=”input” when clicking the same control onHover. It also improves on the WAI-ARIA example that only announces the button and provides no context for what it controls.
A known potential issue with this implementation is that the full description of the field is announced on each click which can be lengthy. This should not be a significant issue to any sighted mouse users that also use screen readers who choose to use them since new clicks interrupt announcements of previous ones.
Screen reader operation (errors)
VoiceOver (MacOS Sonoma 14.4.1) announces "50%" when focus is (re-)set to the field. Using up/down arrows announces correct current value. Exiting and returning to field will still announce "50%" on focus.
This appears to be a bug in VoiceOver interpretation of the field's value. This fails in WAI-ARIA Date Picker Spin Button Example (for the year field). Adding aria-valuenow or aria-valuetext has no effect on the announcement. As of this writing (May 2024) There is little information found about how to address this.
Keyboard Interactions
| Key | Description |
|---|---|
| Up Arrow | Increment value |
| Down Arrow | Decrement value |
| Left Arrow | Move to the previous character in the input field |
| Right Arrow | Move to the next character in the input field |
| Page Up | Currently not supported |
| Page Down | Currently not supported |
Component Tokens
Note: Click on the token row to copy the token to your clipboard.
NumberInput Tokens
| Token Name | Value | |
|---|---|---|
| number-input.color.text.stepper.disabled | #7D7F81 | |
| number-input.sizing.height.stepper | 40px | |
| number-input.spacing.gap.vertical.container | 16px | |
| number-input.spacing.gap.horizontal.container | 12px |
Input Tokens
| Token Name | Value | |
|---|---|---|
| input.color.surface.field.default | #FFFFFF | |
| input.color.surface.field.highlighted | #E5F8FB | |
| input.color.surface.field.disabled | #F3F3F3 | |
| input.color.border.field.rest | #4B4D4F | |
| input.color.border.field.hover.default | #196ECF | |
| input.color.border.field.hover.error | #990000 | |
| input.color.border.field.hover.success | #007000 | |
| input.color.border.field.active.default | #004BA0 | |
| input.color.border.field.active.error | #990000 | |
| input.color.border.field.active.success | #007000 | |
| input.color.text.input | #4B4D4F | |
| input.color.text.hint | #4B4D4F | |
| input.color.text.required | #990000 | |
| input.color.icon.utility.rest | #4B4D4F | |
| input.color.icon.utility.hover | #323334 | |
| input.color.icon.utility.active | #000000 | |
| input.color.icon.content | #323334 | |
| input.border-radius.all.field | 4px | |
| input.border-width.all.field.default | 1px | |
| input.border-width.all.field.active | 3px | |
| input.sizing.all.icon | 20px | |
| input.spacing.gap.vertical.container | 8px | |
| input.spacing.gap.horizontal.field | 12px | |
| input.spacing.gap.horizontal.input-indicator | 2px | |
| input.spacing.gap.horizontal.prefix-input | 2px | |
| input.spacing.gap.horizontal.suffix-clear | 2px | |
| input.spacing.padding.all.focus-container | 2px | |
| input.spacing.padding.horizontal.field | 12px | |
| input.spacing.padding.left.field | 12px | |
| input.spacing.padding.right.focus-field | 44px |
Header Tokens
| Token Name | Value | |
|---|---|---|
| input-header.color.text.label | #4B4D4F | |
| input-header.color.text.hint | #4B4D4F | |
| input-header.color.icon.info.rest | #196ECF | |
| input-header.color.icon.info.hover | #004BA0 | |
| input-header.color.icon.info.active | #002677 | |
| input-header.sizing.all.icon | 24px | |
| input-header.spacing.gap.horizontal.container | 4px | |
| input-header.spacing.gap.horizontal.label | 4px | |
| input-header.spacing.gap.vertical.content | 4px | |
| input-header.spacing.padding.top.content | 2px |
Validation Tokens
| Token Name | Value | |
|---|---|---|
| input-validation.color.surface.container | #FFFFFF | |
| input-validation.color.text.error | #990000 | |
| input-validation.color.text.success | #007000 | |
| input-validation.color.icon.error | #990000 | |
| input-validation.color.icon.success | #007000 | |
| input-validation.sizing.all.icon | 20px | |
| input-validation.spacing.gap.horizontal.container | 4px |