NumberField New Alpha Light DOM & Tailwind
A control for entering a numeric value, with stepper buttons, optional drag-to-scrub, and locale-aware formatting.
Usage
Number Field is a control for entering a numeric value. It provides decrement and increment stepper buttons, arrow / Page key stepping, Home / End to the bounds, press-and-hold auto-repeat, optional drag-to-scrub, clamping, and Intl.NumberFormat formatting.
Number Field renders its built-in label, hint and error on its own. Wrapped in a Field, the Field takes over with a Field Label, Field Description and Field Error, and the built-in versions step aside automatically.
Importing the component registers its custom element:
import "@nordhealth/components/lib/NumberField"
Pair it with a Field and a Field Label, connected by for:
Standalone or inside a Field
Use the control on its own with its built-in label, hint and error, or wrap it in a Field — the Field then provides the labelling. Both render the same control:
Examples
Min and max
Set min and max to bound the value. Stepping, the arrow keys and blur all clamp to the range.
Currency
Pass a format object to render the value with Intl.NumberFormat. Use style: "currency" with a currency code to format and parse an amount of money.
Percent
Use style: "percent" to display a fraction as a percentage. The stored value stays a decimal (for example 0.5 shows as 50%).
Locale
Set locale to format and parse the value for a specific locale — grouping separators, decimal marks and currency placement all follow it.
Wheel scrub
Add allow-wheel-scrub so the value changes when the focused input is scrolled.
Without stepper buttons
Use hide-controls for a plain numeric input. It is still steppable with the keyboard.
Read-only and disabled
readonly keeps the value visible but uneditable; disabled removes it from interaction entirely.
Validation and errors
Mark the Field as invalid and the control as invalid, then put the message in a Field Error.
Input group
Place a Number Field beside a Button to build a compact input group.
RTL
Number Field follows the document or container direction. Toggle the direction to see the layout mirror.
Accessibility
- Give Number Field an accessible name with its built-in
label, or — inside a Field — a Field Label whoseforpoints at the control'sidso it can be focused by clicking the label. - The inner control is a text input with
inputmodeandaria-roledescription, not a nativetype="number", which lets it display locale-formatted text (currency, percent, grouping) while staying fully editable. Bounds are surrounded by the Field and themin/maxattributes for validation. - The stepper buttons carry localized accessible labels for increment and decrement, and keyboard users can step with the arrow / Page keys and jump to the bounds with Home / End.
- For validation, mark both the Field and the control as
invalidand put the message in a Field Error so it is announced and styled consistently.
API reference
NumberField
Number Field is a control for entering a numeric value, with decrement and
increment stepper buttons, optional drag-to-scrub, and locale-aware
formatting. It ports the behaviour of Base UI's Number Field: arrow/Page
stepping, Home/End to the bounds, press-and-hold auto-repeat, optional
wheel/scrub, clamping, and Intl.NumberFormat formatting.
Number Field is a control only — it has no built-in label, hint or error.
Compose it inside a Field with a
Field Label to give it a label, hint and error,
the same way you would a native input.
FORM PARTICIPATION: the value is submitted as the raw numeric string under the
host name via FormDataController (the inner input has no name, avoiding a
double submit). Unlike Base UI there is no hidden native number input, and —
like the other new Nord controls — Number Field does NOT use
ElementInternals, so it performs no native constraint validation: required,
min, max and step are NOT enforced as form-blocking validity. min/max
govern interactive stepping and (unless allow-out-of-range is set) clamp the
value; required only surfaces as required/aria-required on the inner input
for assistive technology. Authors must validate constraints themselves and
reflect failures via invalid and a nord-field-error.
ACCESSIBILITY NOTE: the inner control is a text input (with inputmode) and
aria-roledescription="Number field", NOT role="spinbutton" and NOT a
native type="number". A text input lets us show locale-formatted text
(currency, percent, grouping) while keeping full text editing — assistive tech
reads the formatted value from the input's text. We do NOT add
aria-valuenow/valuemin/valuemax/valuetext: those are only valid on a
range role (e.g. spinbutton/slider) and would fail aria-allowed-attr
validation on a text input. This mirrors what Base UI's NumberFieldInput
actually ships (only aria-roledescription).
<nord-number-field></nord-number-field>Props
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
value | value | The raw numeric value of the field, or null when empty.
The value attribute may be set as a numeric string in HTML; it is coerced
with a custom converter (Number(v), NaN -> null). | number | null | null |
min | min | The minimum allowed value. Step interactions and blur-clamping clamp to it, and the decrement button disables at/below it. Home jumps to it only when it is finite. | number | undefined | — |
max | max | The maximum allowed value. Step interactions and blur-clamping clamp to it, and the increment button disables at/above it. End jumps to it only when it is finite. | number | undefined | — |
step | step | Amount each arrow press / button click / wheel notch changes the value.
'any' disables step semantics (treated as 1 internally for stepping). | number | 'any' | 1 |
largeStep | large-step | Step used for PageUp/PageDown and Shift+Arrow. | number | 10 |
smallStep | small-step | Step used when Alt/Option is held with Arrow keys. | number | 0.1 |
snapOnStep | snap-on-step | When true, stepping snaps the value to the nearest multiple of the step. | boolean | false |
allowOutOfRange | allow-out-of-range | When true, direct text entry may go outside min/max without clamping
(so range validation can flag it); step interactions still clamp. | boolean | false |
integer | integer | Restricts the field to whole numbers. The decimal separator can no longer be
typed, pasted or programmatic fractional values are rounded to the nearest
integer, and the built-in mask caps the fraction to zero. The soft keyboard
shows the plain numeric keypad. | boolean | false |
allowWheelScrub | allow-wheel-scrub | When true, scrolling the mouse wheel while focused changes the value. | boolean | false |
scrub | scrub | When true, renders a drag handle so the user can click-drag to change the value. | boolean | false |
locale | locale | BCP-47 locale(s) for Intl formatting/parsing. Defaults to the runtime locale. | string | string[] | undefined | — |
placeholder | placeholder | Placeholder text shown when the field is empty. | string | undefined | — |
name | name | Form field name. The value is submitted under this name via FormDataController. | string | undefined | — |
disabled | disabled | Disables the control. | boolean | false |
readonly | readonly | Read-only: the user cannot change the value, but the value is still submitted. | boolean | false |
invalid | invalid | Applies error styling and sets aria-invalid="true" on the inner input. | boolean | false |
hideControls | hide-controls | Hides the increment/decrement stepper buttons (still steppable via keyboard). | boolean | false |
currency | currency | ISO 4217 currency code (e.g. USD, EUR, SEK). Setting it enables the
currency convenience: a decorative, locale-aware symbol is rendered into the
start/end slot resolved from currencyPlacement, and the currency is
folded into the input's accessible description so assistive tech announces it.
This is layered on top of the generic start/end slots — for a fully
custom symbol, omit currency and slot your own content instead. It is
independent of format: the symbol lives beside the number rather than in
the formatted text, so it composes cleanly with mask. | string | undefined | — |
currencyPlacement | currency-placement | Which side the currency symbol is placed on. 'auto' (the default)
derives the side from the locale/currency pair via Intl (symbol-first
locales like en-US → start, symbol-last locales like de-DE → end);
'start'/'end' force the side. Has no effect unless currency is set. | 'start' | 'end' | 'auto' | 'auto' |
form | form | HTMLFormElement | null | — | |
size | size | The size of the component. | 's' | 'm' | 'l' | 'm' |
required | required | Determines whether the control is required or not.
A required control is announced as such to assistive technology and, inside
a <nord-field>, drives the required indicator on the <nord-field-label>.
When using this property you need to also set “novalidate” attribute on a form element to prevent browser from displaying its own validation errors. | boolean | false |
Field properties
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
label | label | Label for the control. Ignored when the control is wrapped in a
<nord-field>, which provides the label via <nord-field-label>. | string | '' |
hint | hint | Optional hint text shown with the control. Ignored inside a <nord-field>,
which provides it via <nord-field-description>. | string | undefined | — |
hintBelow | hint-below | Renders the hint below the control and any error instead of below the label. | boolean | false |
hideLabel | hide-label | Visually hide the label, but still expose it to assistive technologies. | boolean | false |
error | error | Optional error shown with the control. Ignored inside a <nord-field>,
which provides it via <nord-field-error>. | string | undefined | — |
hideRequired | hide-required | Visually hide the required indicator, but still expose the required state to assistive technologies. | boolean | false |
Slots
| Slot name | Description |
|---|---|
start | Leading content inside the control, before the input (e.g. a unit or currency symbol). Sits after the decrement stepper button. |
end | Trailing content inside the control, after the input (e.g. a unit label or %). Sits before the increment stepper button. |
Methods
| Method name | Parameters | Description |
|---|---|---|
focus(options?: FocusOptions) => void | options: FocusOptions | Focus the inner input (mirrors combobox focus). |
| Event | Detail Type | Description |
|---|---|---|
input | NordEvent | Dispatched as the value changes during interaction (typing a parseable value, clearing, each step, each button tick, each wheel notch, each scrub increment). |
change | NumberFieldChangeEvent | Dispatched when the value is committed (on blur, keyboard step, or pointer-up after a button hold or scrub). Carries the numeric value and a reason. |
CSS Properties
CSS Custom Properties provide more fine grain control over component presentation. We advise utilizing existing properties on the component before using these.
| Property | Description | Default |
|---|---|---|
--n-number-field-control-min-inline-size | Minimum inline size (width) of the numeric input portion of the control. | 8ch |
--n-number-field-step-inline-size | Inline size (width) of each increment/decrement stepper button. | var(--n-space-xl) |
--n-number-field-background | Background of the control. | var(--n-input-background, var(--n-color-surface)) |
--n-number-field-border-color | Border color of the control. | var(--n-input-border-color, var(--n-color-border-strong)) |
--n-number-field-border-radius | Corner radius of the control. | var(--n-input-border-radius, var(--n-border-radius-s)) |
State attributes
These data-* attributes are set by the component to reflect its current state. Use them as styling hooks — they are read-only and should not be set manually.
| Attribute | Description |
|---|---|
data-invalid | Present while the field is invalid (mirrors the invalid property). Style hook for the whole control. |
data-focused | Present while the control has focus. Style hook for focus-within treatments. |
Dependencies
This component is internally dependent on the following components:
Design guidelinesFor designers
Usage
Number Field is a control only — it has no built-in label, hint or error. Pair it with a Field to give it a label, helper text and an error message, exactly like a native input. Connect the Field Label to the number field with the for attribute so it gets an accessible name and click-to-focus.
Import the number field and the Field parts you need — each import registers its custom element:
import '@nordhealth/components/lib/NumberField'
import '@nordhealth/components/lib/Field'
import '@nordhealth/components/lib/FieldLabel'
import '@nordhealth/components/lib/FieldDescription'
import '@nordhealth/components/lib/FieldError'
Then compose the Field around the number field:
<nord-field>
<nord-field-label for="amount">Amount</nord-field-label>
<nord-number-field id="amount"></nord-number-field>
<nord-field-description>Enter an amount between 1 and 100.</nord-field-description>
<!-- show a Field Error and mark the Field invalid instead, when validation fails -->
</nord-field>
Read and write the value with the value property — it is a number (or null when empty), not a string:
const field = document.querySelector('nord-number-field')
field.value = 42
field.addEventListener('input', () => console.log(field.value))
Constrain and format the field with the min, max, step and format options. format takes Intl.NumberFormat options and is set as a property:
field.min = 0
field.max = 1000
field.format = { style: 'currency', currency: 'EUR' }
Number Field renders in the light DOM, so you can style it directly with your own CSS or Tailwind utilities, and size it by setting a width on the field (or on its Field).
Keyboard, buttons and scrubbing
- Arrow keys step by
step; Shift+Arrow and PageUp/PageDown step bylarge-step; Alt+Arrow steps bysmall-step. Home and End jump tominandmaxwhen those are set. - The stepper buttons support press-and-hold to auto-repeat. They are
tabindex="-1", so keyboard users step via the input while touch and assistive-technology users can still tap the buttons. - Enable
scrubto add a drag handle, orallow-wheel-scrubto change the value with the mouse wheel while the input is focused.
Accessibility
The inner control is a text input with inputmode, carrying aria-roledescription="Number field" — not role="spinbutton" and not a native type="number". This lets it display locale-formatted text (currency, percent, grouping) while keeping full text editing; assistive technology reads the formatted value straight from the input's text. The form value is submitted as the raw numeric string under the host name; the inner input has no name, so there is no double submit.
Do
- Use for entering a single numeric value, such as a quantity, price, percentage or duration.
- Pair it with a Field and a Field Label connected with
for, so it has an accessible name, helper text and an error message. - Set
min,maxandstepto constrain the value, andformatto display it in the user's locale (currency, percent, grouping). - Use
scruborallow-wheel-scrubwhen fine-grained, fast adjustment is helpful, such as in design tools.
Don’t
- Don’t use for non-numeric input, see Input instead.
- Don’t use for choosing from a fixed list of values, see Select or Combobox instead.
- Don’t add a Field Label without a
forpointing at the number field’sid, or the control will have no accessible name. - Don’t rely on the number field for validation messaging — mark the surrounding Field invalid and show a Field Error.