Number Field
A compact numeric field with a leading Minus and trailing Plus stepper button — keyboard stepping, thousands separators and a border by default.
Installation
- $npx litefy@latest add number-field
- $pnpm dlx litefy@latest add number-field
- $yarn dlx litefy@latest add number-field
- $bun --bun litefy@latest add number-field
Usage
NumberField is the stepper-style numeric field: leading Minus / trailing Plus buttons step the value by step, clamped to min / max. Set variant="embedded" to drop the border when it lives inside another container.
"use client";import { useState } from "react";import { NumberField } from "@/ui";export default function Demo() { const [count, setCount] = useState<number | undefined>(1); return ( <div className="flex max-w-md flex-wrap items-start gap-6"> <div className="flex flex-col gap-1"> <span className="text-xs text-muted-foreground">Quantity · 1–99</span> <NumberField positiveInteger min={1} max={99} value={count} onValueChange={setCount} aria-label="Quantity" /> </div> <div className="flex flex-col gap-1"> <span className="text-xs text-muted-foreground">Rate · 0–1, step 0.1</span> <NumberField min={0} max={1} step={0.1} defaultValue={0.5} aria-label="Rate" /> </div> <div className="flex flex-col gap-1"> <span className="text-xs text-muted-foreground">Embedded · no border</span> <NumberField variant="embedded" positiveInteger min={1} max={5} defaultValue={1} aria-label="Embedded quantity" /> </div> <div className="flex flex-col gap-1"> <span className="text-xs text-muted-foreground">Disabled</span> <NumberField defaultValue={7} disabled aria-label="Disabled" /> </div> </div> );}Invalid
Set invalid to show the danger border state.
"use client";
import { NumberField } from "@/ui";
export default function NumberFieldInvalidDemo() {
return (
<div className="flex max-w-md flex-col gap-4">
<NumberField defaultValue={5} invalid aria-label="Invalid amount" />
<NumberField
defaultValue={10}
min={0}
max={20}
invalid
thousands
aria-label="Thousands amount"
/>
</div>
);
}
Controlled
Normal Mode (string value)
Value: 50 (type: string)
Positive Integer Mode (number | null value)
Value: 5 (type: number)
"use client";
import * as React from "react";
import { NumberField } from "@/ui";
export default function NumberFieldControlledDemo() {
const [stringValue, setStringValue] = React.useState<string | undefined>(
"50",
);
const [numberValue, setNumberValue] = React.useState<number | undefined>(5);
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">Normal Mode (string value)</p>
<NumberField
value={stringValue}
onValueChange={setStringValue}
min={0}
max={100}
step={0.5}
aria-label="String value"
/>
<p className="text-sm text-muted-foreground">
Value: {stringValue} (type: {typeof stringValue})
</p>
</div>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">
Positive Integer Mode (number | null value)
</p>
<NumberField
value={numberValue}
onValueChange={setNumberValue}
positiveInteger
min={0}
max={20}
aria-label="Integer value"
/>
<p className="text-sm text-muted-foreground">
Value: {numberValue} (type:{" "}
{numberValue === null ? "null" : typeof numberValue})
</p>
</div>
</div>
);
}
API Reference
NumberField
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | number | string | - | Initial value (uncontrolled mode) |
value | number | string | undefined | - | Current value (controlled mode) |
onValueChange | (value?: number) => void | (value?: string) => void | - | Value change handler. Returns number | undefined when positiveInteger=true, otherwise string |
invalid | boolean | - | Switches the border (or the text in embedded mode) to danger |
variant | "default" | "embedded" | "default" | "default" ships a border for standalone use; "embedded" drops it for fields inside other containers |
min | number | 0 (positiveInteger) or -Infinity | Minimum allowed value — the Minus button disables at it |
max | number | Infinity | Maximum allowed value — the Plus button disables at it |
step | number | 1 | Step size for the steppers and ArrowUp / ArrowDown |
positiveInteger | boolean | false | Restrict to positive integers only. Changes value type to number | undefined |
thousands | boolean | false | Group the integer part with thousands separators while the input is not focused; the committed value stays unformatted |
className | ClassNameValue | - | Custom classes, applied to the shell |
style | React.CSSProperties | - | Inline styles, applied to the shell |
classNames | { root? } | - | Custom classes for the inner input |
styles | { root? } | - | Inline styles for the inner input |
disabled | boolean | false | Disable the field and both steppers |
...props | React.ComponentProps<"input"> | - | Supports all native input props (except type), such as onBlur |
Composable Components
NumberStepper
The clickable step button — renders a Plus (direction="up") or Minus (direction="down") icon; children overrides the icon.
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "up" | "down" | "up" | Icon and accessible label (Increase / Decrease) |
className | ClassNameValue | - | Custom CSS class |
children | React.ReactNode | - | Custom icon content, replaces the default icon |
Supports all native <button> attributes (except type).
NumberRoot
The bare inner input. Supports all native <input> attributes (except className), plus className.
For the bordered variant with a non-interactive suffix cue, see NumberInput.
Multi Select
A multi-select dropdown composed of Popover and CheckboxGroup — flat or grouped options, keyboard navigation, controlled value
Number Input
A bordered numeric input with a non-interactive up/down cue in the trailing area — stepping is keyboard-only. The form-friendly sibling of NumberField.