Number field
The text-field box around a native number input with right-aligned tabular digits, an optional unit affix and two square stepper buttons that replace the host spinner.
stable · light DOM · 4.0.0. Visible native controls retain FormData, labels, constraints, disabled fieldsets and reset. No duplicate form-value mechanism is installed.
Live variants
default
Between 1 and 20.
<j3w1-number-field id="example-number-field-default"><div class="number-field">
<label class="number-field-label" for="example-number-field-default-nf-workspaces">Workspaces</label>
<div class="number-field-root">
<input class="number-field-input" id="example-number-field-default-nf-workspaces" type="number" value="10" min="1" max="20" step="1" aria-describedby="example-number-field-default-nf-workspaces-help example-number-field-default-nf-workspaces-message" name="example-number-field-default-nf-workspaces">
<span class="number-field-stepper">
<button class="number-field-action" type="button" aria-label="Increase workspaces">+</button>
<button class="number-field-action" type="button" aria-label="Decrease workspaces">−</button>
</span>
</div>
<p class="number-field-help" id="example-number-field-default-nf-workspaces-help">Between 1 and 20.</p>
<p class="number-field-message" id="example-number-field-default-nf-workspaces-message">✕ Between 1 and 20.</p>
</div></j3w1-number-field>with-unit
Between 0 and 64 pixels; Up and Down step by one.
<j3w1-number-field id="example-number-field-with-unit"><div class="number-field">
<label class="number-field-label" for="example-number-field-with-unit-nf-gap">Inner gap (px)</label>
<div class="number-field-root">
<input class="number-field-input" id="example-number-field-with-unit-nf-gap" type="number" value="14" min="0" max="64" step="1" aria-describedby="example-number-field-with-unit-nf-gap-help example-number-field-with-unit-nf-gap-message" name="example-number-field-with-unit-nf-gap">
<span class="number-field-unit" aria-hidden="true">px</span>
<span class="number-field-stepper">
<button class="number-field-action" type="button" aria-label="Increase inner gap">+</button>
<button class="number-field-action" type="button" aria-label="Decrease inner gap">−</button>
</span>
</div>
<p class="number-field-help" id="example-number-field-with-unit-nf-gap-help">Between 0 and 64 pixels; Up and Down step by one.</p>
<p class="number-field-message" id="example-number-field-with-unit-nf-gap-message">✕ Between 0 and 64.</p>
</div></j3w1-number-field>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/number-field.css';
import '@j3w1/ui/register/number-field';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy number-field --out ./vendor/j3w1/number-fieldThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
value | CustomEvent | Forwards value to the primary native control. |
required | CustomEvent | Forwards required to the primary native control. |
readonly | CustomEvent | Forwards readonly to the primary native control. |
checked | CustomEvent | Forwards checked to the primary native control. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
value | string | Primary native value; assignment does not fabricate user events. |
checked | boolean | Native checkbox state. |
required | boolean | Native required constraint. |
readOnly | boolean | Native readonly property. |
form | HTMLFormElement | null | Owning native form. Read only. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
checkValidity | (): boolean | Check owned native constraints. |
reportValidity | (): boolean | Report the first invalid owned control. |
clear | (): void | Clear an editable primary input and emit native input/change. |
stepBy | (amount: number): void | Step a number input by the supplied number of native steps. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | Native change from an owned control, alongside the original native event. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Number field number-field
The text-field box around a native number input with right-aligned tabular digits, an optional unit affix and two square stepper buttons that replace the host spinner.
Report an issue with this component
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
root.bg | color.surface.inputapproveduse | #000000 | Form-control fill. |
root.border | color.border.controlapproveduse | #c81a1a | D-041 owner correction: red control and floating-frame borders, distinct from rose text. |
root.bg-hover | color.interaction.hover.bgapproveduse | #1c1010 | D-041 interaction.hover.bg: color.primitive.modern.hover. |
root.border-focus | color.border.activeobserveduse | #e53935 | Active or focused control border. |
root.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
root.ring-invalid | color.interaction.focus.ring-containerobserveduse | #ffa2a7 | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry. |
root.border-invalid | color.status.danger.borderobserveduse | #e53935 | Danger border, 2px on invalid inputs. |
root.border-disabled | color.border.disabledapproveduse | #482b2b | D-041 border.disabled: color.primitive.modern.divider. |
root.bg-disabled | color.interaction.disabled.bgapproveduse | #0d0707 | D-041 interaction.disabled.bg: color.primitive.modern.panel. |
root.border-readonly | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
root.bg-readonly | color.surface.canvasapproveduse | #000000 | Page and terminal background. |
input.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.text-disabled | color.text.disabledapproveduse | #a3676b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.caret | color.code.caretapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
unit.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
unit.border | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
action.text | color.action.tertiary.textapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
action.bg-hover | color.action.tertiary.hover-bgobserveduse | #1c0a09 | Ghost and icon button hover. |
action.border | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
label.text | color.text.brightapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
help.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
message.text | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
State matrix
Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).
| State | Default | With unit |
|---|---|---|
default | Between 1 and 20. | Between 0 and 64 pixels; Up and Down step by one. |
hover | ||
focus-visible | ||
invalid | ||
invalid+focus-visible | ||
disabled | ||
read-only |
Purpose
Entering a bounded number: a gap in pixels, a timeout, a count. The host
spinner, tiny and inconsistent, gives way to two square buttons that are
real tab stops. Free-form numeric text (a version, an identifier) is a
text-field with inputmode.
Anatomy
Parts as listed in the anatomy; each stepper button is at least 24px wide.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | 1px color.border.control on color.surface.input; digits color.text.default; unit color.text.muted; stepper glyphs color.action.tertiary.text | — |
| hover | background → color.interaction.hover.bg; a hovered stepper button → color.action.tertiary.hover-bg | cursor: text on the input, pointer on the buttons |
| focus-visible | border → color.border.active; 1px dashed color.interaction.focus.ring at −2px on the box or the focused stepper button | the ring |
| invalid | 2px color.status.danger.border; message with the ✕ glyph | border 1 → 2px; glyph; aria-invalid |
| invalid+focus-visible | the 2px danger border and the ring in color.interaction.focus.ring-container at −4px | double boundary |
| disabled | text, unit and glyphs color.text.disabled; border color.border.disabled; background color.interaction.disabled.bg; buttons disabled | disabled; cursor: not-allowed |
| read-only | no box: 1px dotted color.border.divider bottom edge on color.surface.canvas; stepper hidden | readonly; dotted edge; no buttons |
Precedence: disabled > invalid > hover; focus-visible is always drawn.
Keyboard
A value outside the range may be typed; it is reported invalid on blur or submit, never clamped silently.
Accessibility
A programmatic label is required. The aria-hidden unit is repeated in the
label or help (“Inner gap (px)”). Help states min, max and step before the
user types. aria-invalid only after interaction or submit. Text meets
4.5:1; borders 3:1.
Portability
The text-field box plus two buttons; the host spinner is hidden with the
engine’s pseudo-elements and appearance: textfield. A host whose spin
buttons cannot be replaced styles them square in the same colours and
records it.
Non-examples
The host’s default spinner. Round or overlapping stepper buttons. A value clamped silently. Left-aligned digits. A unit typed into the value. A number field for phone numbers, postcodes or identifiers. Invalid by colour alone. Disabling by opacity.
Contrast report
22 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| number-field default text | default | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| number-field default border | default | ui | color.border.control #c81a1a | color.surface.input #000000 | 3.62 | 3 | pass |
| number-field hover text | hover | text | color.text.default #e99499 | color.interaction.hover.bg #1c1010 | 8.10 | 4.5 | pass |
| number-field hover border | hover | ui | color.border.control #c81a1a | color.interaction.hover.bg #1c1010 | 3.20 | 3 | pass |
| number-field focus-visible text | focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| number-field focus-visible border | focus-visible | ui | color.border.active #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| number-field focus-visible outline | focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| number-field invalid text | invalid | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| number-field invalid border | invalid | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| number-field invalid+focus-visible text | invalid+focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| number-field invalid+focus-visible border | invalid+focus-visible | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| number-field invalid+focus-visible outline | invalid+focus-visible | ui | color.interaction.focus.ring-container #ffa2a7 | color.surface.input #000000 | 10.98 | 3 | pass |
| number-field read-only text | read-only | text | color.text.default #e99499 | color.surface.canvas #000000 | 9.16 | 4.5 | pass |
| unit affix on the input surface | — | text | color.text.muted #d4868b | color.surface.input #000000 | 7.58 | 4.5 | pass |
| stepper glyphs on the input surface | — | text | color.action.tertiary.text #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| stepper glyphs on their hover fill | hover | text | color.action.tertiary.text #e99499 | color.action.tertiary.hover-bg #1c0a09 | 8.35 | 4.5 | pass |
| validation message on the panel surface | — | text | color.status.danger.text #f07178 | color.surface.default #0d0707 | 6.98 | 4.5 | pass |
| help text on the panel surface | — | text | color.text.muted #d4868b | color.surface.default #0d0707 | 7.22 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | color.text.disabled #a3676b | color.interaction.disabled.bg #0d0707 | 4.48 | 3 | pass |
| disabled border (exempt) | disabled | ui | color.border.disabled #482b2b | color.interaction.disabled.bg #0d0707 | 1.58 | 1 | pass |
| affix and stepper dividers (decorative) | — | ui | color.border.divider #482b2b | color.surface.input #000000 | 1.66 | 1 | pass |
| read-only bottom edge (decorative; the field has no box) | read-only | ui | color.border.divider #482b2b | color.surface.canvas #000000 | 1.66 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab / Shift+Tab | Input, then the increase button, then the decrease button. |
| Up / Down | Steps by the step attribute (native); Shift+Up / Shift+Down step by ten where the host binds it. |
| Page Up / Page Down | Steps by ten times the step where the host binds it. |
| Home / End | Jumps to min or max where the host binds it. |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: text-field field range search-field
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-TOUCH FX-DENSITY FX-STATE-MATRIX