NumberField New Alpha Light DOM & Tailwind

Open in Storybook

A control for entering a numeric value, with stepper buttons, optional drag-to-scrub, and locale-aware formatting.

OverviewUsageStandalone or inside a FieldExamplesAccessibilityAPI referenceDependenciesDesign guidelines
This component is considered alpha. We're still finalising the API, so it may include a breaking change at any time — use it at your own risk.
Loading...

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:

Copy code
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:

InlineWith Field

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 whose for points at the control's id so it can be focused by clicking the label.
  • The inner control is a text input with inputmode and aria-roledescription, not a native type="number", which lets it display locale-formatted text (currency, percent, grouping) while staying fully editable. Bounds are surrounded by the Field and the min / max attributes 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 invalid and 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

PropertyAttribute Description TypeDefault
valuevalueThe 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 | nullnull
minminThe 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
maxmaxThe 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
stepstepAmount each arrow press / button click / wheel notch changes the value. 'any' disables step semantics (treated as 1 internally for stepping).number | 'any'1
largeSteplarge-stepStep used for PageUp/PageDown and Shift+Arrow.number10
smallStepsmall-stepStep used when Alt/Option is held with Arrow keys.number0.1
snapOnStepsnap-on-stepWhen true, stepping snaps the value to the nearest multiple of the step.booleanfalse
allowOutOfRangeallow-out-of-rangeWhen true, direct text entry may go outside min/max without clamping (so range validation can flag it); step interactions still clamp.booleanfalse
integerintegerRestricts 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.booleanfalse
allowWheelScruballow-wheel-scrubWhen true, scrolling the mouse wheel while focused changes the value.booleanfalse
scrubscrubWhen true, renders a drag handle so the user can click-drag to change the value.booleanfalse
localelocaleBCP-47 locale(s) for Intl formatting/parsing. Defaults to the runtime locale.string | string[] | undefined
placeholderplaceholderPlaceholder text shown when the field is empty.string | undefined
namenameForm field name. The value is submitted under this name via FormDataController.string | undefined
disableddisabledDisables the control.booleanfalse
readonlyreadonlyRead-only: the user cannot change the value, but the value is still submitted.booleanfalse
invalidinvalidApplies error styling and sets aria-invalid="true" on the inner input.booleanfalse
hideControlshide-controlsHides the increment/decrement stepper buttons (still steppable via keyboard).booleanfalse
currencycurrencyISO 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
currencyPlacementcurrency-placementWhich 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'
formformHTMLFormElement | null
sizesizeThe size of the component.'s' | 'm' | 'l''m'
requiredrequiredDetermines 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.booleanfalse

Field properties

PropertyAttribute Description TypeDefault
labellabelLabel for the control. Ignored when the control is wrapped in a <nord-field>, which provides the label via <nord-field-label>.string''
hinthintOptional hint text shown with the control. Ignored inside a <nord-field>, which provides it via <nord-field-description>.string | undefined
hintBelowhint-belowRenders the hint below the control and any error instead of below the label.booleanfalse
hideLabelhide-labelVisually hide the label, but still expose it to assistive technologies.booleanfalse
errorerrorOptional error shown with the control. Ignored inside a <nord-field>, which provides it via <nord-field-error>.string | undefined
hideRequiredhide-requiredVisually hide the required indicator, but still expose the required state to assistive technologies.booleanfalse

Slots

Slot name Description
startLeading content inside the control, before the input (e.g. a unit or currency symbol). Sits after the decrement stepper button.
endTrailing content inside the control, after the input (e.g. a unit label or %). Sits before the increment stepper button.

Methods

Method nameParameters Description
focus(options?: FocusOptions) => voidoptions: FocusOptionsFocus the inner input (mirrors combobox focus).
EventDetail TypeDescription
inputNordEventDispatched as the value changes during interaction (typing a parseable value, clearing, each step, each button tick, each wheel notch, each scrub increment).
changeNumberFieldChangeEventDispatched 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.

PropertyDescriptionDefault
--n-number-field-control-min-inline-sizeMinimum inline size (width) of the numeric input portion of the control.8ch
--n-number-field-step-inline-sizeInline size (width) of each increment/decrement stepper button.var(--n-space-xl)
--n-number-field-backgroundBackground of the control.var(--n-input-background, var(--n-color-surface))
--n-number-field-border-colorBorder color of the control.var(--n-input-border-color, var(--n-color-border-strong))
--n-number-field-border-radiusCorner 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.

AttributeDescription
data-invalidPresent while the field is invalid (mirrors the invalid property). Style hook for the whole control.
data-focusedPresent while the control has focus. Style hook for focus-within treatments.
Light DOM & Tailwind. This component renders in the light DOM, so you can style it directly with your own CSS or Tailwind utility classes — there is no shadow boundary, and its default styles are low specificity, so your utilities win.
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:

Copy code
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:

Copy code
<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:

Copy code
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:

Copy code
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 by large-step; Alt+Arrow steps by small-step. Home and End jump to min and max when 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 scrub to add a drag handle, or allow-wheel-scrub to 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, max and step to constrain the value, and format to display it in the user's locale (currency, percent, grouping).
  • Use scrub or allow-wheel-scrub when 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 for pointing at the number field’s id, 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.

Was this page helpful?

Yes No

We use this feedback to improve our documentation.