numeric-input-usage

star 2

Use after component-usage-ux when an app needs @techsio/ui-kit NumericInput for accessible number entry with Zag.js spinbutton behavior, compound parts, numeric public values, locale formatting, min/max/step, and token-first styling.

TechsioCZ By TechsioCZ schedule Updated 6/6/2026

name: numeric-input-usage description: > Use after component-usage-ux when an app needs @techsio/ui-kit NumericInput for accessible number entry with Zag.js spinbutton behavior, compound parts, numeric public values, locale formatting, min/max/step, and token-first styling. type: core library: "@techsio/ui-kit" library_version: "0.3.2" requires: - component-usage-ux - zag-compound-components - app-token-overrides sources: - "libs/ui/src/atoms/numeric-input.tsx" - "libs/ui/src/tokens/components/atoms/_numeric-input.css" - "libs/ui/stories/atoms/numeric-input.stories.tsx" - "libs/ui/src/atoms/figma/numeric-input.figma.tsx" - "https://zagjs.com/components/react/number-input"

@techsio/ui-kit NumericInput Usage

Use NumericInput for quantities, percentages, currency-like values, or bounded numbers where keyboard, wheel, increment/decrement, and validation behavior matter.

Setup

import { NumericInput } from "@techsio/ui-kit/atoms/numeric-input"

<NumericInput id="quantity" name="quantity" defaultValue={1} min={1} step={1}>
  <NumericInput.Control>
    <NumericInput.Input />
    <NumericInput.TriggerContainer>
      <NumericInput.IncrementTrigger />
      <NumericInput.DecrementTrigger />
    </NumericInput.TriggerContainer>
  </NumericInput.Control>
</NumericInput>

Public wrapper props use numbers:

value/defaultValue: number
onChange: (value: number) => void
size: sm | md | lg
locale: string, default cs-CZ
precision, min, max, step, name, disabled, required, invalid
allowMouseWheel, allowOverflow, clampValueOnBlur, spinOnPress, formatOptions

Core Patterns

Keep the compound anatomy intact

<NumericInput defaultValue={50} min={0} max={100}>
  <NumericInput.Control>
    <NumericInput.Input />
    <NumericInput.TriggerContainer>
      <NumericInput.IncrementTrigger />
      <NumericInput.DecrementTrigger />
    </NumericInput.TriggerContainer>
  </NumericInput.Control>
</NumericInput>

Do not replace trigger parts with native buttons. The subcomponents spread Zag part props and use Button/Input tokens.

Respect wrapper value types

Zag number-input documents string values internally, but this UI-kit wrapper converts public number values to the Zag string format and calls onChange with valueAsNumber.

const [quantity, setQuantity] = useState(1)

<NumericInput value={quantity} onChange={setQuantity} min={1} />

Use locale and precision deliberately

<NumericInput
  defaultValue={12.5}
  locale="cs-CZ"
  precision={1}
  step={0.1}
/>

Use formatOptions for currency/percent formatting only after checking that the output is still a usable input value.

Use describedBy for external help/error text

<NumericInput id="stock" describedBy="stock-help" invalid>
  <NumericInput.Control>
    <NumericInput.Input />
  </NumericInput.Control>
</NumericInput>

describedBy is merged into the input aria-describedby.

Common Mistakes

HIGH Passing Zag string values to the wrapper

Wrong:

<NumericInput value="10" onValueChange={setValue} />

Correct:

<NumericInput value={10} onChange={setValue} />

Source: libs/ui/src/atoms/numeric-input.tsx

HIGH Custom steppers

Wrong:

<NumericInput defaultValue={1}>
  <Input />
  <button>+</button>
</NumericInput>

Correct:

<NumericInput defaultValue={1}>
  <NumericInput.Control>
    <NumericInput.Input />
    <NumericInput.TriggerContainer>
      <NumericInput.IncrementTrigger />
      <NumericInput.DecrementTrigger />
    </NumericInput.TriggerContainer>
  </NumericInput.Control>
</NumericInput>

Source: https://zagjs.com/components/react/number-input

HIGH Inline sizing/color classes

Wrong:

<NumericInput className="w-24 text-sm">
  <NumericInput.Control className="border-red-500 px-2">
    <NumericInput.Input />
  </NumericInput.Control>
</NumericInput>

Correct:

<NumericInput size="sm" invalid>
  <NumericInput.Control>
    <NumericInput.Input />
  </NumericInput.Control>
</NumericInput>

Source: libs/ui/src/tokens/components/atoms/_numeric-input.css

MEDIUM Missing min/max semantics

Wrong:

<NumericInput defaultValue={1} />

Correct for a quantity:

<NumericInput defaultValue={1} min={1} step={1} />

Use min/max/step to express domain constraints, not custom blur handlers.

Validation Commands

rg -n "<input[^>]*type=\"number\"|<NumericInput\\b(?!\\.)[^>]*(onValueChange|value=\")" apps
rg -U -n "<NumericInput\\b(?!\\.)[\\s\\S]{0,400}<button|<NumericInput\\b(?!\\.)[\\s\\S]{0,400}<Input" apps
rg -n "<NumericInput\\b(?!\\.)[^>]*className=.*(bg-|text-|border-|px-|py-)" apps
rg -U -P -n "<NumericInput\\b(?!\\.)(?![\\s\\S]{0,600}<NumericInput\\.Input)" apps
Install via CLI
npx skills add https://github.com/TechsioCZ/new-engine --skill numeric-input-usage
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator