NumberField

NumberField is the stepper-style numeric control: a Surface container with a Button on the left, the current value in the middle, and a + Button on the right. Each press fires onChange with the new value, already clamped to [min, max] and offset by step. It's the right pick for quantities in a cart, a zoom level in an inspector, a tip percentage — anywhere the value moves in discrete increments and the user mostly nudges rather than types.

100
<NumberField value={qty} onChange={setQty} min={1} max={99} />
<NumberField
  value={tip}
  onChange={setTip}
  min={0}
  max={100}
  step={5}
  color="brand"
  size="lg"
/>
<NumberField
  value={zoom}
  onChange={setZoom}
  input={false}
  min={25}
  max={400}
  step={25}
  rounded={false}
/>

Usage

import { NumberField } from '@cladd-ui/react';
 
<NumberField value={qty} onChange={setQty} min={1} max={99} />;

NumberField is controlled — pass value and an onChange handler. Increments happen via the + / buttons; the value is clamped to [min, max] (defaults 0 and 1_000_000) and offset by step (default 1) before onChange fires. The buttons auto-disable when value is already at min or max, so you don't need to gate them yourself.

The middle slot renders the value in an Input by default — focusable and selectable so the user can copy the value out — but the stepper buttons own the writes. Set input={false} when you want a read-only chip instead.

Examples

Sizes

size accepts sm, md (default), lg, xl, 2xl — the same scale as Input and Textarea, forwarded to both the + / Buttons and the value display so the whole row scales as one unit. (NumberField doesn't go down to 2xs / xs — those are reserved for sub-controls like the inline clear button on Input.)

<NumberField size="md" value={value} onChange={setValue} />

Container variant

variant is the Surface variant token applied to the containergradient (default), solid, transparent, solid-fill, gradient-fill. The *-fill variants flood the container with the accent color and invert the text, useful when the field is the loud primary control on a settings card.

<NumberField variant="gradient" value={value} onChange={setValue} />

Button variant

buttonVariant controls the surface variant on just the + / buttons — transparent (default) keeps them as quiet glyphs inside the container, gradient-fill / solid-fill paints them in the accent color for a punchier stepper. Pair with buttonOutline to add an outline ring around each button independently of the container.

<NumberField
  value={value}
  onChange={setValue}
  buttonVariant="gradient-fill"
  buttonOutline={false}
/>

Input mode

input toggles how the middle slot is rendered. input={true} (default) renders the value in an Input — same recessed SurfaceCut chrome as a regular text field, focusable so the user can select and copy the value. input={false} swaps it for a read-only SurfaceCut chip — flatter, narrower, no caret. Reach for the chip variant when you don't want a focus ring competing with the buttons (toolbar steppers, sidebar zoom controls, anywhere the value is purely informational between two clicks).

150
<NumberField value={editable} onChange={setEditable} />
<NumberField value={chip} onChange={setChip} input={false} step={25} />

Min, max, step

min (default 0) and max (default 1_000_000) clamp the value at both ends. The button disables itself once value <= min and the + button once value >= max, so the bounds are visually obvious without extra wiring. step (default 1) is the offset applied on each press — pick 5 for a tip percentage, 10 for a price, 25 for a zoom level.

<NumberField value={qty} onChange={setQty} min={1} max={5} />
<NumberField
  value={price}
  onChange={setPrice}
  min={0}
  max={100}
  step={10}
  color="green"
/>

Rounded

rounded (default true) pill-shapes the container and the + / buttons; turn it off for a more rectangular, app-shell look. valueRounded (default false) pill-shapes just the middle value display — useful when you want a pill chip nested inside a squared container.

<NumberField
  rounded
  valueRounded={false}
  value={value}
  onChange={setValue}
/>

Disabled and read-only

disabled dims the field to 50% and disables both buttons entirely. readOnly is the subtler one: the buttons stay at full opacity but stop responding to clicks, so the field reads as live but won't change — handy when a parallel action is in flight or the value is locked by a higher-level state.

<NumberField
  value={value}
  onChange={setValue}
  readOnly
  disabled={false}
/>

Playground

size, color, variant, rounded, and input are designed to compose. Try a 2xl gradient-fill field in brand for a hero quantity selector, or a compact sm transparent field with input={false} as an inline sidebar zoom control.

<NumberField
  size="md"
  color="brand"
  variant="gradient"
  rounded
  input
  value={value}
  onChange={setValue}
  min={0}
  max={100}
/>

API Reference

Inherits from SurfaceProps
buttonOutline: booleanfalseOutline ring on the +/− buttons. Default false.
buttonVariant: SurfaceVariant'transparent'Surface variant applied to the +/− buttons. Default 'transparent'.
children: ReactNodeCustom content rendered inside the number field container (rare - most usage is value-only).
className: stringExtra classes for the number field container.
color: ColorAccent color token. Sets the container's cladd-color-{name} class - cascades to the +/− buttons.
contentClassName: stringExtra classes for the inner SurfaceContent wrapper (where the −/value/+ row is laid out).
disabled: booleanVisually dim the number field and disable both buttons.
input: booleanWhen true (default), the value is rendered in an editable Input.

When false, the value is rendered in a read-only SurfaceCut chip - useful when keyboard entry is not desired.
inputClassName: stringExtra classes for the value Input (or SurfaceCut).
max: number1_000_000Default 1_000_000.
min: number0Default 0.
onChange: (value: number) => voidFires after a +/− button press, with the new value (already clamped to [min, max]).
outline: booleantrueOutline ring on the number field container. Default true.
readOnly: booleanBlock changes without the disabled visual treatment.
rounded: booleantruePill-shape the container and the +/− buttons. Default true.
size: NumberFieldSize'md'Size token. Drives container/button height, padding, and font size. Default 'md'.
step: number1Increment per +/− press. Default 1.
surfaceLevel: number | stringForwarded to the underlying Surface as level - see SurfaceProps.level.
value: number0Default 0.
valueRounded: booleanfalsePill-shape the value display. Default false.
variant: SurfaceVariant'gradient'Surface variant for the number field container. Default 'gradient'.