Skip to content
kerfjsGitHub

NumberField

A number you can type or step, with arrow keys and two buttons.

SourcePackage @kerf-js/veneer

Preview

tsx
import { NumberField } from '@kerf-js/veneer'export function Basic() {  return (  <NumberField    label="Retries"    defaultValue={3}    min={0}    max={10}  />  )}const min = 60_000

Installation

npm install @kerf-js/veneer

Usage

tsx
import { NumberField } from '@kerf-js/veneer'<NumberField  label="Retries"  defaultValue={3}  min={0}  max={10}/>

Sizes

size matches the other fields in a form.

tsx
import { NumberField, Stack } from '@kerf-js/veneer'export function Sizes() {  return (  <Stack gap="group">    <NumberField      label="Small"      size="sm"      defaultValue={3}      min={0}      max={10}    />    <NumberField      label="Medium"      defaultValue={3}      min={0}      max={10}    />    <NumberField      label="Large"      size="lg"      defaultValue={3}      min={0}      max={10}    />  </Stack>  )}const min = 60_000

Fractions and a unit

A fractional step moves by that much. suffix names the unit.

tsx
import { NumberField } from '@kerf-js/veneer'export function Decimal() {  return (  <NumberField    label="CPU per copy"    defaultValue={0.5}    min={0.25}    max={8}    step={0.25}    suffix="vCPU"  />  )}const min = 60_000

Money

prefix shows the currency. Page Up and Page Down move ten steps at a time.

tsx
import { NumberField } from '@kerf-js/veneer'export function Money() {  return (  <NumberField    label="Monthly budget"    defaultValue={1500}    min={0}    step={50}    prefix="$"    hint="We email you at 80%."  />  )}const min = 60_000

Hint, error, read-only, disabled

Read-only and disabled turn the steppers off too.

tsx
import { Grid, NumberField } from '@kerf-js/veneer'import { Stack, Text } from '@kerf-js/veneer'export function States() {  return (  <Grid    cols={2}    gap="group"  >    <Cell name="Empty">      <NumberField        label="Retries"        min={0}        max={10}      />    </Cell>    <Cell name="Hint">      <NumberField        label="Retries"        defaultValue={3}        min={0}        max={10}        hint="0 turns retries off."      />    </Cell>    <Cell name="Invalid">      <NumberField        label="Retries"        defaultValue={10}        min={0}        max={10}        error="Ten retries can hide a real failure."      />    </Cell>    <Cell name="Read-only">      <NumberField        label="Retries"        defaultValue={3}        readonly      />    </Cell>    <Cell name="Disabled">      <NumberField        label="Retries"        defaultValue={3}        disabled      />    </Cell>  </Grid>  )}const min = 60_000const Cell = ({  name,  children,}: {  name: string  children?: unknown}) => (  <Stack gap="tight">    <Text      role="caption"      subtle    >      {name}    </Text>    {children}  </Stack>)

Buying seats

Use min and max to limit the count, and read value to work out a total.

tsx
import { state } from '@kerf-js/core'import { NumberField, Stack, Text } from '@kerf-js/veneer'export function Seats() {  const seats = state<number | null>(8)  return (    <Stack gap="related">      <NumberField        label="Seats"        value={seats}        min={1}        max={50}        hint="$12 a seat, each month."      />      <Text subtle>        Total: ${(seats.value ?? 0) * 12} a month      </Text>    </Stack>  )}const min = 60_000

When to use

Use it for

  • An exact number, like seats or a retry count

Reach for something else

  • Slider, when a rough value is enough

Accessibility

↑↓
Steps up or down
PageUpPageDown
Steps by ten
HomeEnd
Jumps to the bound, when there is one

Theming

Restyle it app-wide from veneer() in your kerf config. Every key takes colour, spacing, size, radius, shadow, type and motion; its own keys are listed. A state nests inside: hover: { bg: 'ink' }. Text on a fill you set is contrast-checked at build. How theming works

KeyOwn keysStates
parts.fieldbg, radiushover, focus, press, active, open, disabled
parts.field.labeltexthover, focus
ts
veneer({  colors: { brand: '#e8590c', ink: '#1b1b1f' },  parts: {    field: { radius: 4, label: { focus: { text: 'brand' } } },  },})

API Reference

PropTypeDefaultDescription
labelstring—The accessible name, drawn as the floating border label (or above, classic). Required.
hintunknown—One line of help under the control.
errorunknown—Shown instead of the hint; marks the field invalid.
iconIconRef—Leading icon: it sweeps with the border.
prefixunknown—Text or an element inside the field before the value, such as $.
suffixunknown—Text or an element inside the field after the value, such as kg.
variant"floating" | "classic"—floating (default, or the app's veneer({ fields })) · classic: the label above.
appearance"line"—line: only a bottom line under the value, no box or fill. The boxed field otherwise.
size"sm" | "md" | "lg"'md'Control size. md by default.
clearablebooleanfalseA clear button (shown once there is a value).
counternumber—Maximum length, shown as a counter.
idstring—The element id. One is made for you if you leave it out.
disabledbooleanfalseTurns it off, and takes it out of the tab order.
readonlybooleanfalseShows the value and lets it be selected and copied, but not changed.
valuenumber | State<number | null> | null—The current value, when you own the state.
defaultValuenumber | null—The starting value, when the component owns the state.
minnumber—The lowest it can step to.
maxnumber—The highest it can step to.
stepnumber1How far one press moves it.
namestring—The name it posts under in a form.
requiredbooleanfalseThe form will not submit without it.
EventTypeDefaultDescription
onInput((e: InputEvent & { currentTarget: HTMLInputElement & HTMLTextAreaElemen…—Called on each keystroke.
onValueChange((v: number | null, reason: ChangeReason) => void)—Called with each new value, and whether a pointer, a key or code made the change.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="number-field"] [data-part="step"]

stepstepsinput

RelatedInputSlider