md-number-field spec card
Identity · when to use / when NOT · decision cues · behavioural contract · do/don't · anti-patterns · full API. Paste into your agent when you're implementing with this component.
A number, typed or stepped. An md-text-field
with locale-aware Intl.NumberFormat display (currency, percent, units,
grouping), arrow-key / stepper-button / wheel stepping, and native form
participation with the raw numeric value.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field label="Quantity" min="0" max="99" value="5" style="inline-size: 260px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField label="Quantity" min="0" max="99" value="5" style={{ inlineSize: '260px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field label="Quantity" min="0" max="99" value="5" style="inline-size: 260px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field label="Quantity" min="0" max="99" value="5" style="inline-size: 260px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field label="Quantity" min="0" max="99" value="5" style="inline-size: 260px;"></md-number-field>
Already installed? See the
Installation guide for one-time package setup
(core + tokens, fonts). Each tab below shows two patterns for using
md-number-field in your project: Option A registers every AWC UI
component at once (simplest), Option B imports only this component for
tree-shake-friendly bundles.
<!-- ─── Option A: global registration (all components) ─── -->
<script type="module">
import '@awc-ui/core/define';
</script>
<!-- ─── Option B: single import (tree-shake only md-number-field) ─── -->
<script type="module">
import '@awc-ui/core/components/md-number-field';
</script>
<md-number-field></md-number-field>// ─── Option A: typed React wrapper (registers all components) ───
// Importing from '@awc-ui/react' calls defineCustomElements() as a
// side effect, so every md-* element becomes available in the browser.
import { MdNumberField } from '@awc-ui/react';
export function Example() {
return <MdNumberField></MdNumberField>;
}
// ─── Option B: single import (tree-shake to only md-number-field) ───
// Skip the wrapper and use the raw custom element. Smallest bundle,
// but you lose typed props/events on JSX.
import '@awc-ui/core/components/md-number-field';
export function ExampleTreeShaken() {
return <md-number-field></md-number-field>;
}// ─── Option A: schema module (any md-* element accepted) ───
// Pair with `defineCustomElements(window)` in main.ts.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<md-number-field></md-number-field>`,
})
export class ExampleComponent {}
// ─── Option B: typed directive (tree-shake friendly) ───
// Pair with `import '@awc-ui/core/components/md-number-field'` in main.ts.
import { Component } from '@angular/core';
import { MdNumberField } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [MdNumberField],
template: `<md-number-field></md-number-field>`,
})
export class ExampleTreeShakenComponent {}<!-- ─── Option A: typed Vue wrapper (registers all components) ─── -->
<script setup lang="ts">
import { MdNumberField } from '@awc-ui/vue';
</script>
<template>
<MdNumberField></MdNumberField>
</template>
<!-- ─── Option B: single import (tree-shake to only md-number-field) ─── -->
<script setup lang="ts">
import '@awc-ui/core/components/md-number-field';
</script>
<template>
<md-number-field></md-number-field>
</template><!-- ─── Option A: global registration (done once in main entry) ─── -->
<!-- main.ts: -->
<!-- import { defineCustomElements } from '@awc-ui/svelte'; -->
<!-- defineCustomElements(window); -->
<md-number-field></md-number-field>
<!-- ─── Option B: single import (tree-shake to only md-number-field) ─── -->
<script lang="ts">
import '@awc-ui/core/components/md-number-field';
</script>
<md-number-field></md-number-field>1.234,5, 12,50 €,
50%) while the app always receives the raw number.| Situation | Use instead |
|---|---|
| Picking from a small numeric range visually | md-slider |
| A 1–5 style score | md-rating |
| Free text that merely contains digits (phone, ZIP) | md-text-field with restrict |
| Dates / times | md-date-picker / md-time-picker |
| A read-only numeric readout | md-meter or plain text |
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field variant="filled" label="Filled" value="12" style="inline-size: 220px;"></md-number-field>
<md-number-field variant="outlined" label="Outlined" value="12" style="inline-size: 220px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField variant="filled" label="Filled" value="12" style={{ inlineSize: '220px' }}></MdNumberField>
<MdNumberField variant="outlined" label="Outlined" value="12" style={{ inlineSize: '220px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field variant="filled" label="Filled" value="12" style="inline-size: 220px;"></md-number-field>
<md-number-field variant="outlined" label="Outlined" value="12" style="inline-size: 220px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field variant="filled" label="Filled" value="12" style="inline-size: 220px;"></md-number-field>
<md-number-field variant="outlined" label="Outlined" value="12" style="inline-size: 220px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field variant="filled" label="Filled" value="12" style="inline-size: 220px;"></md-number-field>
<md-number-field variant="outlined" label="Outlined" value="12" style="inline-size: 220px;"></md-number-field>steppers places the +/− buttons: inline (in the trailing slot, default),
split (tonal circles flanking the field), or none (keyboard/wheel only).
Press-and-hold auto-repeats (400ms delay, then 60ms ticks), the buttons
auto-disable at min/max, and they are pointer-only (tabindex="-1")
— the input itself is the keyboard surface.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field steppers="inline" label="Inline (default)" value="3" style="inline-size: 260px;"></md-number-field>
<md-number-field steppers="split" label="Split" value="3" style="inline-size: 280px;"></md-number-field>
<md-number-field steppers="none" label="None (keyboard only)" value="3" style="inline-size: 260px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField steppers="inline" label="Inline (default)" value="3" style={{ inlineSize: '260px' }}></MdNumberField>
<MdNumberField steppers="split" label="Split" value="3" style={{ inlineSize: '280px' }}></MdNumberField>
<MdNumberField steppers="none" label="None (keyboard only)" value="3" style={{ inlineSize: '260px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field steppers="inline" label="Inline (default)" value="3" style="inline-size: 260px;"></md-number-field>
<md-number-field steppers="split" label="Split" value="3" style="inline-size: 280px;"></md-number-field>
<md-number-field steppers="none" label="None (keyboard only)" value="3" style="inline-size: 260px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field steppers="inline" label="Inline (default)" value="3" style="inline-size: 260px;"></md-number-field>
<md-number-field steppers="split" label="Split" value="3" style="inline-size: 280px;"></md-number-field>
<md-number-field steppers="none" label="None (keyboard only)" value="3" style="inline-size: 260px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field steppers="inline" label="Inline (default)" value="3" style="inline-size: 260px;"></md-number-field>
<md-number-field steppers="split" label="Split" value="3" style="inline-size: 280px;"></md-number-field>
<md-number-field steppers="none" label="None (keyboard only)" value="3" style="inline-size: 260px;"></md-number-field>format-options takes Intl.NumberFormatOptions either way: as a property
(el.formatOptions = { style: 'currency', currency: 'EUR' }) or as a JSON
attribute (format-options='{"style":"currency","currency":"EUR"}'), so a
plain-HTML page needs no script. Malformed JSON warns once and falls back to
plain number formatting. Invalid Intl options (style: 'currency' without a
currency) are survivable too, but fall back silently — check them against
Intl.NumberFormat if a number comes out unformatted. locale picks the formatting locale (empty = the
runtime locale). Parsing accepts the locale’s group/decimal separators, signs,
symbols, and native numerals (e.g. Arabic-Indic digits under ar-SA).
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field label="Preis (EUR)" locale="de-DE" step="0.5" value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
style="inline-size: 260px;"></md-number-field>
<md-number-field id="nf-pct" label="Discount" step="0.01" min="0" max="1" value="0.5" style="inline-size: 260px;"></md-number-field>
<script type="module">
// The same options as a property — both forms are supported.
document.getElementById('nf-pct').formatOptions = { style: 'percent' };
</script>import { useEffect } from 'react';
import { MdNumberField } from '@awc-ui/react';
export function Demo() {
useEffect(() => {
// The same options as a property — both forms are supported.
document.getElementById('nf-pct').formatOptions = { style: 'percent' };
}, []);
return (
<>
<MdNumberField label="Preis (EUR)" locale="de-DE" step="0.5" value="1234.5"
formatOptions='{"style":"currency","currency":"EUR"}'
style={{ inlineSize: '260px' }}></MdNumberField>
<MdNumberField id="nf-pct" label="Discount" step="0.01" min="0" max="1" value="0.5" style={{ inlineSize: '260px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent implements AfterViewInit {
ngAfterViewInit() {
// The same options as a property — both forms are supported.
document.getElementById('nf-pct').formatOptions = { style: 'percent' };
}
}
<!-- app.component.html -->
<md-number-field label="Preis (EUR)" locale="de-DE" step="0.5" value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
style="inline-size: 260px;"></md-number-field>
<md-number-field id="nf-pct" label="Discount" step="0.01" min="0" max="1" value="0.5" style="inline-size: 260px;"></md-number-field><script setup>
import { onMounted } from 'vue';
import '@awc-ui/core/define';
onMounted(() => {
// The same options as a property — both forms are supported.
document.getElementById('nf-pct').formatOptions = { style: 'percent' };
});
</script>
<template>
<md-number-field label="Preis (EUR)" locale="de-DE" step="0.5" value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
style="inline-size: 260px;"></md-number-field>
<md-number-field id="nf-pct" label="Discount" step="0.01" min="0" max="1" value="0.5" style="inline-size: 260px;"></md-number-field>
</template><script>
import { onMount } from 'svelte';
import '@awc-ui/core/define';
onMount(() => {
// The same options as a property — both forms are supported.
document.getElementById('nf-pct').formatOptions = { style: 'percent' };
});
</script>
<md-number-field label="Preis (EUR)" locale="de-DE" step="0.5" value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
style="inline-size: 260px;"></md-number-field>
<md-number-field id="nf-pct" label="Discount" step="0.01" min="0" max="1" value="0.5" style="inline-size: 260px;"></md-number-field>| Interaction | Amount | Notes |
|---|---|---|
ArrowUp / ArrowDown | step (default 1) | Steps from the parsed visible text |
Alt + arrows | small-step (default 0.1) | Fine adjustment |
Shift + arrows | large-step (default 10) | Coarse adjustment |
Home / End | jump to min / max | Only when the bound is defined |
| Stepper press-and-hold | modifier-aware per tick | 400ms delay, 60ms interval |
Wheel (allow-wheel-scrub) | modifier-aware | Only while the input has focus |
Stepping an empty field seeds from 0, then clamps into range. Interactive
stepping always clamps — allow-out-of-range exempts only typed text from
the blur clamp. snap-on-step aligns stepped results to multiples of the step
(base = min when defined), snapping before clamping so non-aligned bounds
stay reachable.
Typed and stepped values take deliberately different paths:
| Typed text | Stepping (arrows, steppers, wheel, stepUp()) | |
|---|---|---|
| While in progress | Kept verbatim, parsed leniently | n/a — the value is generated |
| Commits | On blur or Enter | Immediately |
| Clamping | At commit, unless allow-out-of-range | Always, whatever that prop says |
| Snapping | Never | With snap-on-step, before clamping |
| Float cleanup | Never — your digits survive | toPrecision(15), so 0.1 + 0.2 commits as 0.3 |
Home / End are a third path: they jump to min / max rather than
stepping — clamped and float-cleaned, never snapped, and only when that bound is
defined.
stepUp(times?) and stepDown(times?) do the same work programmatically and
report reason: 'none'.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field label="0–10" min="0" max="10" value="10" supporting-text="Increment disables at max" style="inline-size: 260px;"></md-number-field>
<md-number-field label="Snap to 25s" step="25" snap-on-step value="30" supporting-text="ArrowUp from 30 lands on 50" style="inline-size: 260px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField label="0–10" min="0" max="10" value="10" supportingText="Increment disables at max" style={{ inlineSize: '260px' }}></MdNumberField>
<MdNumberField label="Snap to 25s" step="25" snapOnStep value="30" supportingText="ArrowUp from 30 lands on 50" style={{ inlineSize: '260px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field label="0–10" min="0" max="10" value="10" supporting-text="Increment disables at max" style="inline-size: 260px;"></md-number-field>
<md-number-field label="Snap to 25s" step="25" snap-on-step value="30" supporting-text="ArrowUp from 30 lands on 50" style="inline-size: 260px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field label="0–10" min="0" max="10" value="10" supporting-text="Increment disables at max" style="inline-size: 260px;"></md-number-field>
<md-number-field label="Snap to 25s" step="25" snap-on-step value="30" supporting-text="ArrowUp from 30 lands on 50" style="inline-size: 260px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field label="0–10" min="0" max="10" value="10" supporting-text="Increment disables at max" style="inline-size: 260px;"></md-number-field>
<md-number-field label="Snap to 25s" step="25" snap-on-step value="30" supporting-text="ArrowUp from 30 lands on 50" style="inline-size: 260px;"></md-number-field><!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field label="Enabled" value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Disabled" disabled value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Read-only" readonly value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Error" required error error-text="Please enter a quantity" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Supported" supporting-text="Between 0 and 10" reserve-supporting-space min="0" max="10" style="inline-size: 220px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField label="Enabled" value="5" style={{ inlineSize: '220px' }}></MdNumberField>
<MdNumberField label="Disabled" disabled value="5" style={{ inlineSize: '220px' }}></MdNumberField>
<MdNumberField label="Read-only" readonly value="5" style={{ inlineSize: '220px' }}></MdNumberField>
<MdNumberField label="Error" required error errorText="Please enter a quantity" style={{ inlineSize: '220px' }}></MdNumberField>
<MdNumberField label="Supported" supportingText="Between 0 and 10" reserveSupportingSpace min="0" max="10" style={{ inlineSize: '220px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field label="Enabled" value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Disabled" disabled value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Read-only" readonly value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Error" required error error-text="Please enter a quantity" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Supported" supporting-text="Between 0 and 10" reserve-supporting-space min="0" max="10" style="inline-size: 220px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field label="Enabled" value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Disabled" disabled value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Read-only" readonly value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Error" required error error-text="Please enter a quantity" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Supported" supporting-text="Between 0 and 10" reserve-supporting-space min="0" max="10" style="inline-size: 220px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field label="Enabled" value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Disabled" disabled value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Read-only" readonly value="5" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Error" required error error-text="Please enter a quantity" style="inline-size: 220px;"></md-number-field>
<md-number-field label="Supported" supporting-text="Between 0 and 10" reserve-supporting-space min="0" max="10" style="inline-size: 220px;"></md-number-field>reserve-supporting-space holds the line height so the layout doesn’t jump
when an error appears. Show supporting text or error text — never both.
| Event | Cancelable | Detail | Fires |
|---|---|---|---|
mdInput | no | { value, formattedValue, reason } | Every value change (typing included) |
mdChange | no | { value, formattedValue, reason } | Commit points — only when the value differs from the last committed one, so a blur that changed nothing is silent |
mdValidityChange | no | { valid, validationMessage, flags } | Validity changes — not composed, and silent on mount |
reason says which path produced the change:
reason | Comes from |
|---|---|
'input-change' | Typed text that parsed |
'input-clear' | The field was emptied |
'input-blur' | Blur and Enter — the same commit |
'keyboard' | Arrows, Home, End |
'increment-press' / 'decrement-press' | A stepper press, and every hold tick |
'wheel' | Wheel stepping |
'none' | The programmatic stepUp() / stepDown() |
formattedValue is the verbatim text in the box for 'input-change' and
'input-clear' — the user is mid-edit, so it is not reformatted. Every other
reason carries the reformatted display string.
mdInput as the value moves, then mdChange once it settles.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field id="qty" name="qty" label="Quantity" min="0" max="99" required></md-number-field>
<script type="module">
const el = document.getElementById('qty');
el.addEventListener('mdInput', (e) => preview(e.detail.value)); // live number | null
el.addEventListener('mdChange', (e) => save(e.detail.value)); // committed number | null
</script>import { MdNumberField } from '@awc-ui/react';
export function QuantityField() {
return (
<MdNumberField
label="Quantity"
name="qty"
min={0}
max={99}
required
onMdInput={(e) => preview(e.detail.value)}
onMdChange={(e) => save(e.detail.value)}
/>
);
}import { Component } from '@angular/core';
@Component({
selector: 'app-quantity-field',
template: `
<md-number-field
label="Quantity"
name="qty"
[min]="0"
[max]="99"
required
(mdChange)="onChange($event)"
></md-number-field>
`,
})
export class QuantityFieldComponent {
onChange(e: CustomEvent<{ value: number | null }>) { save(e.detail.value); }
}<template>
<md-number-field
label="Quantity"
name="qty"
:min="0"
:max="99"
required
@mdChange="(e) => save(e.detail.value)"
/>
</template><md-number-field
label="Quantity"
name="qty"
min="0"
max="99"
required
on:mdChange={(e) => save(e.detail.value)}
/>The component is form-associated via ElementInternals: the raw value
submits as String(value) under name, and an empty field submits no
entry. required blocks submission when empty (value-missing-label is the
bubble message; error-text wins when set). getValidity(),
checkValidity(), reportValidity() and setCustomValidity() are available
as methods, and Enter commits like blur, then requests form submission.
Enter calls form.requestSubmit() with the form’s default submit button as
the submitter, so that button’s name/value joins the entry list and
event.submitter is set — matching native implicit submission. It does not
click that button: requestSubmit() runs the submit steps directly, so put the
logic in the form’s submit listener rather than on the button’s click.
A form reset restores the value captured at load, clears any
setCustomValidity() message and resets the commit baseline. An ancestor
<fieldset disabled> (or a disabled form) disables the field without the
disabled prop.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<form id="order">
<md-number-field
id="qty"
name="qty"
label="Quantity"
required
min="1"
max="99"
value-missing-label="Enter a quantity"
></md-number-field>
<md-button variant="filled" type="submit">Order</md-button>
<md-button variant="text" type="reset">Reset</md-button>
</form>
<script type="module">
const form = document.getElementById('order');
const qty = document.getElementById('qty');
form.addEventListener('submit', (e) => {
e.preventDefault();
// The RAW number submits under name — String(value), no formatting.
console.log(Object.fromEntries(new FormData(form))); // { qty: '5' }
console.log(qty.value); // 5 (a number)
});
const { valid, validationMessage } = await qty.getValidity();
</script>import { useEffect, useRef } from 'react';
import { MdButton, MdNumberField } from '@awc-ui/react';
export function Demo() {
const formRef = useRef(null);
const qtyRef = useRef(null);
useEffect(() => {
const form = formRef.current;
const qty = qtyRef.current;
form.addEventListener('submit', (e) => {
e.preventDefault();
// The RAW number submits under name — String(value), no formatting.
console.log(Object.fromEntries(new FormData(form))); // { qty: '5' }
console.log(qty.value); // 5 (a number)
});
const { valid, validationMessage } = await qty.getValidity();
}, []);
return (
<>
<form id="order" ref={formRef}>
<MdNumberField
id="qty" ref={qtyRef}
name="qty"
label="Quantity"
required
min="1"
max="99"
valueMissingLabel="Enter a quantity"
></MdNumberField>
<MdButton variant="filled" type="submit">Order</MdButton>
<MdButton variant="text" type="reset">Reset</MdButton>
</form>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent implements AfterViewInit {
@ViewChild('form') formRef!: ElementRef;
@ViewChild('qty') qtyRef!: ElementRef;
ngAfterViewInit() {
const form = this.formRef.nativeElement;
const qty = this.qtyRef.nativeElement;
form.addEventListener('submit', (e) => {
e.preventDefault();
// The RAW number submits under name — String(value), no formatting.
console.log(Object.fromEntries(new FormData(form))); // { qty: '5' }
console.log(qty.value); // 5 (a number)
});
const { valid, validationMessage } = await qty.getValidity();
}
}
<!-- app.component.html -->
<form id="order" #form>
<md-number-field
id="qty" #qty
name="qty"
label="Quantity"
required
min="1"
max="99"
value-missing-label="Enter a quantity"
></md-number-field>
<md-button variant="filled" type="submit">Order</md-button>
<md-button variant="text" type="reset">Reset</md-button>
</form><script setup>
import { onMounted, ref } from 'vue';
import '@awc-ui/core/define';
const formRef = ref(null);
const qtyRef = ref(null);
onMounted(() => {
const form = formRef.value;
const qty = qtyRef.value;
form.addEventListener('submit', (e) => {
e.preventDefault();
// The RAW number submits under name — String(value), no formatting.
console.log(Object.fromEntries(new FormData(form))); // { qty: '5' }
console.log(qty.value); // 5 (a number)
});
const { valid, validationMessage } = await qty.getValidity();
});
</script>
<template>
<form id="order" ref="formRef">
<md-number-field
id="qty" ref="qtyRef"
name="qty"
label="Quantity"
required
min="1"
max="99"
value-missing-label="Enter a quantity"
></md-number-field>
<md-button variant="filled" type="submit">Order</md-button>
<md-button variant="text" type="reset">Reset</md-button>
</form>
</template><script>
import { onMount } from 'svelte';
import '@awc-ui/core/define';
let formRef;
let qtyRef;
onMount(() => {
const form = formRef;
const qty = qtyRef;
form.addEventListener('submit', (e) => {
e.preventDefault();
// The RAW number submits under name — String(value), no formatting.
console.log(Object.fromEntries(new FormData(form))); // { qty: '5' }
console.log(qty.value); // 5 (a number)
});
const { valid, validationMessage } = await qty.getValidity();
});
</script>
<form id="order" bind:this={formRef}>
<md-number-field
id="qty" bind:this={qtyRef}
name="qty"
label="Quantity"
required
min="1"
max="99"
value-missing-label="Enter a quantity"
></md-number-field>
<md-button variant="filled" type="submit">Order</md-button>
<md-button variant="text" type="reset">Reset</md-button>
</form>| Property | Attribute | Type | Default | Reflects |
|---|---|---|---|---|
variant | variant | 'filled' | 'outlined' | 'filled' | Yes |
label | label | string | '' | — |
placeholder | placeholder | string | '' | — |
supportingText | supporting-text | string | '' | — |
error | error | boolean | false | Yes |
errorText | error-text | string | '' | — |
disabled | disabled | boolean | false | Yes |
readOnly | readonly | boolean | false | — |
required | required | boolean | false | Yes |
name | name | string | '' | Yes |
density | density | 0 | -1 | -2 | -3 | -4 | 0 | Yes |
value | value | number | null | null | — |
locale | locale | string | '' | — |
formatOptions | format-options | Intl.NumberFormatOptions | string | — | — |
min | min | number | — | — |
max | max | number | — | — |
step | step | number | 1 | — |
smallStep | small-step | number | 0.1 | — |
largeStep | large-step | number | 10 | — |
snapOnStep | snap-on-step | boolean | false | — |
allowOutOfRange | allow-out-of-range | boolean | false | — |
allowWheelScrub | allow-wheel-scrub | boolean | false | — |
steppers | steppers | 'inline' | 'split' | 'none' | 'inline' | — |
incrementLabel | increment-label | string | 'Increment' | — |
decrementLabel | decrement-label | string | 'Decrement' | — |
valueMissingLabel | value-missing-label | string | 'Please enter a number.' | — |
reserveSupportingSpace | reserve-supporting-space | boolean | false | — |
| Method | Parameters |
|---|---|
setFocus() | none |
select() | none |
stepUp() | times: number = 1 |
stepDown() | times: number = 1 |
getValidity() | none |
checkValidity() | none |
reportValidity() | none |
setCustomValidity() | message: string |
Override on the host element for per-instance theming:
| Property | Description |
|---|---|
--md-number-field-width | Host inline-size (default 100% of container) |
--md-number-field-min-width | Minimum host inline-size |
--md-number-field-stepper-icon-size | Stepper glyph size (density-tapered 20px) |
--md-number-field-stepper-color | Stepper icon ink |
--md-number-field-split-stepper-size | Split-layout stepper circle (density-tapered 40px) |
Style internal elements through shadow DOM with ::part():
| Part | Description |
|---|---|
field | Inner md-text-field |
label names the field; the input is a plain textbox with a numeric
software keyboard (inputmode="numeric|decimal") — deliberately not
role="spinbutton" and not type="number", so the formatted value is
ordinary readable, editable text.Alt fine, Shift coarse), Home/End jump to
defined bounds, Enter commits and submits.increment-label /
decrement-label) and tabindex="-1" — pointer-only by design, because the
input already offers a superior keyboard affordance.aria-valuenow / valuemin / valuemax: those
belong to role="spinbutton", which would have assistive tech announce a bare
number fighting the locale-formatted text on screen. State the bounds in
supporting-text instead.disabled host gets pointer-events: none, so a tooltip or popover
targeting the field receives no pointer events either — wrap it in a container
when a disabled field needs a hover explanation.RTL — logical properties throughout; the split-stepper row follows the
inline direction (decrement renders on the right in RTL). Stepping is
vertical-arrow / wheel driven, so there is nothing to mirror there. Locales with
native numerals (e.g. ar-SA) format and parse them, and bidi control
characters are stripped rather than rejected. See RTL.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field steppers="split" label="Quantity" value="12"></md-number-field>
<div dir="rtl">
<md-number-field steppers="split" label="الكمية" value="12"></md-number-field>
</div>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField steppers="split" label="Quantity" value="12"></MdNumberField>
<div dir="rtl">
<MdNumberField steppers="split" label="الكمية" value="12"></MdNumberField>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field steppers="split" label="Quantity" value="12"></md-number-field>
<div dir="rtl">
<md-number-field steppers="split" label="الكمية" value="12"></md-number-field>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field steppers="split" label="Quantity" value="12"></md-number-field>
<div dir="rtl">
<md-number-field steppers="split" label="الكمية" value="12"></md-number-field>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field steppers="split" label="Quantity" value="12"></md-number-field>
<div dir="rtl">
<md-number-field steppers="split" label="الكمية" value="12"></md-number-field>
</div>Density — density="-1…-4" compacts the field and both stepper layouts.
See Density.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field label="Quantity" value="12"></md-number-field>
<md-number-field density="-1" label="Quantity" value="12"></md-number-field>
<md-number-field density="-2" label="Quantity" value="12"></md-number-field>
<md-number-field density="-3" label="Quantity" value="12"></md-number-field>
<md-number-field density="-4" label="Quantity" value="12"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField label="Quantity" value="12"></MdNumberField>
<MdNumberField density="-1" label="Quantity" value="12"></MdNumberField>
<MdNumberField density="-2" label="Quantity" value="12"></MdNumberField>
<MdNumberField density="-3" label="Quantity" value="12"></MdNumberField>
<MdNumberField density="-4" label="Quantity" value="12"></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field label="Quantity" value="12"></md-number-field>
<md-number-field density="-1" label="Quantity" value="12"></md-number-field>
<md-number-field density="-2" label="Quantity" value="12"></md-number-field>
<md-number-field density="-3" label="Quantity" value="12"></md-number-field>
<md-number-field density="-4" label="Quantity" value="12"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field label="Quantity" value="12"></md-number-field>
<md-number-field density="-1" label="Quantity" value="12"></md-number-field>
<md-number-field density="-2" label="Quantity" value="12"></md-number-field>
<md-number-field density="-3" label="Quantity" value="12"></md-number-field>
<md-number-field density="-4" label="Quantity" value="12"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field label="Quantity" value="12"></md-number-field>
<md-number-field density="-1" label="Quantity" value="12"></md-number-field>
<md-number-field density="-2" label="Quantity" value="12"></md-number-field>
<md-number-field density="-3" label="Quantity" value="12"></md-number-field>
<md-number-field density="-4" label="Quantity" value="12"></md-number-field>i18n — translate label, placeholder, supporting-text, error-text,
increment-label, decrement-label, value-missing-label. locale and
format-options are Intl configuration, not translation — locales with
native numerals (e.g. ar-SA) format and parse them.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<!-- locale + format-options are Intl configuration… -->
<md-number-field
label="Preis"
locale="de-DE"
value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
></md-number-field>
<!-- …the label props are the part you actually translate -->
<md-number-field
label="الكمية"
locale="ar-SA"
value="1234.5"
increment-label="زيادة"
decrement-label="إنقاص"
value-missing-label="الرجاء إدخال قيمة"
></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<!-- locale + format-options are Intl configuration… -->
<MdNumberField
label="Preis"
locale="de-DE"
value="1234.5"
formatOptions='{"style":"currency","currency":"EUR"}'
></MdNumberField>
<!-- …the label props are the part you actually translate -->
<MdNumberField
label="الكمية"
locale="ar-SA"
value="1234.5"
incrementLabel="زيادة"
decrementLabel="إنقاص"
valueMissingLabel="الرجاء إدخال قيمة"
></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<!-- locale + format-options are Intl configuration… -->
<md-number-field
label="Preis"
locale="de-DE"
value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
></md-number-field>
<!-- …the label props are the part you actually translate -->
<md-number-field
label="الكمية"
locale="ar-SA"
value="1234.5"
increment-label="زيادة"
decrement-label="إنقاص"
value-missing-label="الرجاء إدخال قيمة"
></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<!-- locale + format-options are Intl configuration… -->
<md-number-field
label="Preis"
locale="de-DE"
value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
></md-number-field>
<!-- …the label props are the part you actually translate -->
<md-number-field
label="الكمية"
locale="ar-SA"
value="1234.5"
increment-label="زيادة"
decrement-label="إنقاص"
value-missing-label="الرجاء إدخال قيمة"
></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<!-- locale + format-options are Intl configuration… -->
<md-number-field
label="Preis"
locale="de-DE"
value="1234.5"
format-options='{"style":"currency","currency":"EUR"}'
></md-number-field>
<!-- …the label props are the part you actually translate -->
<md-number-field
label="الكمية"
locale="ar-SA"
value="1234.5"
increment-label="زيادة"
decrement-label="إنقاص"
value-missing-label="الرجاء إدخال قيمة"
></md-number-field>| Custom property | Purpose | Default |
|---|---|---|
--md-number-field-width / --md-number-field-min-width | Host inline-size | 100% / 200px |
--md-number-field-stepper-icon-size | Stepper glyph size | 20px, density-tapered |
--md-number-field-stepper-color | Stepper icon ink | on-surface-variant |
--md-number-field-split-stepper-size | Split stepper circle | 40px, density-tapered |
--md-text-field-* | All inner-field hooks pass through | — |
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-number-field
label="Themed"
variant="outlined"
value="7"
style="inline-size: 260px; --md-number-field-stepper-color: var(--md-sys-color-primary); --md-number-field-stepper-icon-size: 24px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdNumberField
label="Themed"
variant="outlined"
value="7"
style={{ inlineSize: '260px', '--md-number-field-stepper-color': 'var(--md-sys-color-primary)', '--md-number-field-stepper-icon-size': '24px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-number-field
label="Themed"
variant="outlined"
value="7"
style="inline-size: 260px; --md-number-field-stepper-color: var(--md-sys-color-primary); --md-number-field-stepper-icon-size: 24px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-number-field
label="Themed"
variant="outlined"
value="7"
style="inline-size: 260px; --md-number-field-stepper-color: var(--md-sys-color-primary); --md-number-field-stepper-icon-size: 24px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-number-field
label="Themed"
variant="outlined"
value="7"
style="inline-size: 260px; --md-number-field-stepper-color: var(--md-sys-color-primary); --md-number-field-stepper-icon-size: 24px;"></md-number-field>CSS parts — field (the inner md-text-field), increment, decrement.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<style>
.parts-nf::part(increment) { --md-icon-button-icon-color: var(--md-sys-color-primary); }
.parts-nf::part(decrement) { --md-icon-button-icon-color: var(--md-sys-color-error); }
</style>
<md-number-field class="parts-nf" steppers="split" label="Styled steppers" value="7" style="inline-size: 280px;"></md-number-field>import { MdNumberField } from '@awc-ui/react';
export function Demo() {
return (
<>
<style>
.parts-nf::part(increment) { --md-icon-button-icon-color: var(--md-sys-color-primary); }
.parts-nf::part(decrement) { --md-icon-button-icon-color: var(--md-sys-color-error); }
</style>
<MdNumberField className="parts-nf" steppers="split" label="Styled steppers" value="7" style={{ inlineSize: '280px' }}></MdNumberField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<style>
.parts-nf::part(increment) { --md-icon-button-icon-color: var(--md-sys-color-primary); }
.parts-nf::part(decrement) { --md-icon-button-icon-color: var(--md-sys-color-error); }
</style>
<md-number-field class="parts-nf" steppers="split" label="Styled steppers" value="7" style="inline-size: 280px;"></md-number-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<style>
.parts-nf::part(increment) { --md-icon-button-icon-color: var(--md-sys-color-primary); }
.parts-nf::part(decrement) { --md-icon-button-icon-color: var(--md-sys-color-error); }
</style>
<md-number-field class="parts-nf" steppers="split" label="Styled steppers" value="7" style="inline-size: 280px;"></md-number-field>
</template><script>
import '@awc-ui/core/define';
</script>
<style>
.parts-nf::part(increment) { --md-icon-button-icon-color: var(--md-sys-color-primary); }
.parts-nf::part(decrement) { --md-icon-button-icon-color: var(--md-sys-color-error); }
</style>
<md-number-field class="parts-nf" steppers="split" label="Styled steppers" value="7" style="inline-size: 280px;"></md-number-field>md-text-field ·
md-slider ·
md-rating ·
md-select ·
md-meter
md-number-fieldTwo artefacts to give your AI agent so it generates correct UI with this component. The per-component spec answers "how do I use this exact tag?". The main-llm spec answers "which tag should I pick in the first place?".
md-number-field spec card
Identity · when to use / when NOT · decision cues · behavioural contract · do/don't · anti-patterns · full API. Paste into your agent when you're implementing with this component.
System-prompt preamble · decision matrix · token reference · page recipes · anti-patterns. Paste into the system prompt at the start of a piece of work.