sdxc

Type to search, or start from one of these:

NumberField

A numeric field pairing a native <input type="number"> control with a decrement and an increment button, all three sharing one bordered, rounded frame.

Hold a step button to run through the range.

Steps by 0.50, as the input's own step says.

Subtotal$36 / month
View code
let seats = "3";

<div mix={[vstack({ gap: 5, align: "stretch" })]}>
	<NumberField>
		<Label htmlFor="seats">Seats</Label>
		<NumberField.Group mix={[stepper()]}>
			<NumberField.DecrementButton
				aria-label="One seat fewer"
				command={NUMBER_FIELD_STEP_DOWN_COMMAND}
				commandfor="seats"
			/>
			<NumberField.Input
				id="seats"
				name="seats"
				min={1}
				max={50}
				step={1}
				value={seats}
				aria-describedby="seats-hint"
				mix={[
					on<HTMLInputElement, "input">("input", (event) => {
						seats = event.currentTarget.value;
						void handle.update();
					}),
				]}
			/>
			<NumberField.IncrementButton
				aria-label="One seat more"
				command={NUMBER_FIELD_STEP_UP_COMMAND}
				commandfor="seats"
			/>
		</NumberField.Group>
		<Description id="seats-hint">Hold a step button to run through the range.</Description>
	</NumberField>

	<NumberField>
		<Label htmlFor="budget">Monthly budget</Label>
		<NumberField.Group mix={[stepper({ holdIntervalMs: 40 })]}>
			<NumberField.DecrementButton
				aria-label="Lower the budget"
				command={NUMBER_FIELD_STEP_DOWN_COMMAND}
				commandfor="budget"
			/>
			<NumberField.Input
				id="budget"
				name="budget"
				min={0}
				max={10000}
				step={0.5}
				defaultValue={250}
			/>
			<NumberField.IncrementButton
				aria-label="Raise the budget"
				command={NUMBER_FIELD_STEP_UP_COMMAND}
				commandfor="budget"
			/>
		</NumberField.Group>
		<Description id="budget-hint">Steps by 0.50, as the input's own step says.</Description>
	</NumberField>

	<div mix={[hstack({ gap: 2, justify: "between" })]}>
		<span mix={[text("sm"), weight("medium")]}>Subtotal</span>
		<span mix={[text("sm")]}>{subtotal}</span>
	</div>
</div>

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

npm add @sdxc/ui
import "@sdxc/ui/theme.css";

Usage

import { NumberField } from "@sdxc/ui";

Renders a plain host stacking this field's compound parts in a single column with a small gap between them: typically a caption, the NumberField.Group row, and any supporting or validation copy.

Composition

The parts the component publishes, each a static property of the host.

  • NumberField
  • NumberField.GroupRenders NumberField's frame: a role="group" <div> drawing a bordered, rounded, tinted-background box that reads as one seamless control, ringing the whole frame via :focus-within when a child gains focus.
  • NumberField.InputRenders NumberField's control: a native <input type="number"> built on Input.
  • NumberField.DecrementButtonRenders a native <button> decrementing NumberField.Input's value; its glyph is aria-hidden, so callers must supply an aria-label.
  • NumberField.IncrementButtonRenders a native <button> incrementing NumberField.Input's value; its glyph is aria-hidden, so callers must supply an aria-label.

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

Also accepts everything in TagProps<"div">.

NumberField.Group

Also accepts everything in TagProps<"div">.

NumberField.Input

Also accepts everything in Omit<Input.Props, "type" | "role" | "color">.

NumberField.DecrementButton

Also accepts everything in Omit<TagProps<"button">, "children">.

NumberField.IncrementButton

Also accepts everything in Omit<TagProps<"button">, "children">.