Number field

A whole number, such as a count; empty while nothing is typed.

Installation

npx shadcn@latest add @formdef/number-field

Register it under widgets in lib/formdef/index.tsx, as widgets: { number: NumberField }.

Usage

The widget serves number | undefined: an empty input is undefined, not 0. It finds a field whose schema input is number, optional or not.

export const  = createForm({
  : .({ : .().().(1).(8).() }),
  : { :  },
  fields: {
    guests: { : 'number', : { : 'Guests', : 1, : 8, : 1 } },
  },
})

min, max and step go to the <input type="number">, where the arrow keys and the spin buttons respect them. The schema still decides what is valid.

Not for amounts

The number field is for counts and whole numbers. An amount or any other decimal belongs in a text field with inputMode: 'decimal': an <input type="number"> turns a typed comma into a dot or drops it, so a German amount cannot be entered.

Props

Prop

Type

Source

import { type FieldOf, fieldParts } from '@formdef/core'
import type { ComponentProps } from 'react'

import { FieldFrame } from '@/components/formdef/field-frame'
import { Input } from '@/components/ui/input'
import type { FieldLabels } from '@/lib/formdef/field-labels'

type Attrs = Pick<
  ComponentProps<'input'>,
  | 'className'
  | 'placeholder'
  | 'autoComplete'
  | 'autoFocus'
  | 'disabled'
  | 'readOnly'
  | 'required'
  | 'min'
  | 'max'
  | 'step'
>

type NumberFieldProps = FieldOf<number | undefined> & FieldLabels & Attrs

export const NumberField = ({
  field,
  label,
  labelAside,
  description,
  ...attrs
}: NumberFieldProps) => {
  const parts = fieldParts(field, { description })
  const value = field.state.value ?? ''

  return (
    <FieldFrame
      parts={parts}
      label={label}
      labelAside={labelAside}
      description={description}
      disabled={attrs.disabled}
    >
      <Input
        {...attrs}
        {...parts.control}
        type="number"
        value={value}
        onChange={(event) =>
          field.handleChange(event.target.value === '' ? undefined : event.target.valueAsNumber)
        }
      />
    </FieldFrame>
  )
}