Tessina UI
Tessina UI
GitHubIntroduction
InstallationUsageTheming
Components
ButtonIconButtonLinkSpinnerBadgeAvatarStatusChipShortcutSkeletonSurfaceProgressMeterRating
LabelFieldFieldsetCheckboxRadioSwitchSliderSelectComboboxSearchTextareaNumberFieldDate PickerTime PickerOTP InputFile UploadCalendar
AlertBannerToastTooltip
TabsAccordionCollapsibleBreadcrumbPaginationStepperSegmentedControlButtonGroupToggleButtonToggleGroupToolbarNavigation MenuMenubarBottom NavSidebar
Split ButtonFABDropdown MenuContextMenuCommandPopoverHoverCard
ContainerStackFlexGridAspectRatioSpacerCardCarouselDividerScroll Area
Table
ChatBubblePromptInputCodeBlock
ModalAlertDialogDrawerAction SheetTop Header MobileTop Header DesktopEmptyStateForm
Contributing
ComponentsForm Blocks

NumberField

Numeric form input with stacked, split, inline or no increment/decrement buttons. Five sizes, five rounded values, full intent palette, three label positions, prefix/suffix slots, locale-aware formatting via Intl.NumberFormat, and LTR/RTL support.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { NumberField } from "@tessinaui/ui";
{/* Basic uncontrolled */}
<NumberField defaultValue={0} />

{/* Controlled */}
<NumberField value={qty} onValueChange={setQty} min={0} max={99} />

{/* Quantity selector with split buttons */}
<NumberField buttonsPlacement="split" defaultValue={1} min={1} max={10} />

{/* Currency input with locale formatting */}
<NumberField
  defaultValue={1299.99}
  step={0.01}
  format={{ style: "currency", currency: "USD" }}
  locale="en-US"
/>

{/* Percent input */}
<NumberField
  defaultValue={0.245}
  step={0.001}
  format={{ style: "percent", minimumFractionDigits: 1 }}
/>

{/* With label + supporting text + intent */}
<NumberField
  label="Quantity"
  intent="error"
  supportingText="Out of stock"
  required
  defaultValue={0}
/>

{/* Prefix / suffix slots */}
<NumberField
  defaultValue={75}
  suffix={<span className="px-3 py-2 bg-secondary rounded-full">kg</span>}
/>

{/* RTL */}
<NumberField dir="rtl" label="الكمية" defaultValue={1} />

Showcase

NumberField vs Stepper

The two components solve different problems:

NumberFieldStepper
PurposeForm input — user types or steps a valueCompact counter — user picks a value with buttons
InputEditable text field with keyboard / paste supportNo input; value is read-only display
AffordancesLabel, supporting text, prefix/suffix, error statesLabel + supporting text only
Typical use"Price", "Age", "Weight" form fieldsQuantity selector in a cart row

If users can type the value, use NumberField. If they always pick from a small range with +/-, use Stepper.

API Reference

Props

PropTypeDefaultDescription
valuenumber | null—Controlled value
defaultValuenumber—Uncontrolled initial value
minnumber—Minimum value (decrement disabled at min)
maxnumber—Maximum value (increment disabled at max)
stepnumber | "any"1Amount per increment / decrement
smallStepnumber0.1Step when Meta key is held
largeStepnumber10Step when Shift key is held
snapOnStepbooleanfalseSnap to nearest step on increment / decrement
formatIntl.NumberFormatOptions—Locale-aware formatting (currency, percent, compact, decimals)
localeIntl.LocalesArgumentruntimeBCP-47 locale tag
allowWheelScrubbooleanfalseScrub the value with the mouse wheel while focused
allowOutOfRangebooleanfalseDirect text entry can exceed min / max for native validation
onValueChange(value, eventDetails) => void—Called whenever the value changes
onValueCommitted(value, eventDetails) => void—Called on blur / pointer release / keyboard commit
namestring—HTML form name
requiredbooleanfalseMark required (asterisk in label, native validation)
disabledbooleanfalseDisable all interaction
readOnlybooleanfalseValue visible but not editable
size"xs" | "sm" | "md" | "lg" | "xl""md"Field size
rounded"none" | "sm" | "md" | "lg" | "full""full"Container border radius
intent"none" | "error" | "warning" | "success" | "info""none"Border colour and supporting-text colour
labelstring—Visible label
labelPosition"outside-top" | "inside" | "outside-left""outside-top"Where the label sits
infoTextstring—Small info text rendered next to the label
supportingTextstring—Helper / validation text below the field, coloured by intent
prefixReactNode—Content rendered outside the field on the leading edge
suffixReactNode—Content rendered outside the field on the trailing edge
placeholderstring—Input placeholder
buttonsPlacement"stacked" | "split" | "inline" | "none""stacked"Layout of the increment / decrement buttons
incrementLabelstring"Increase"aria-label for the + button
decrementLabelstring"Decrease"aria-label for the − button
dir"ltr" | "rtl"inheritedText direction
idstringautoid of the underlying input
inputRefRef<HTMLInputElement>—Ref to the underlying input element
inputModestringautoMobile keyboard hint — numeric for integers, decimal otherwise
wrapperClassNamestring—Class on the outermost wrapper
containerClassNamestring—Class on the bordered container
classNamestring—Class on the <input> itself

Buttons placement

PlacementDescription
stackedTwo stacked chevrons on the trailing edge with a vertical divider. Each button is half the input height.
split− on the leading edge, + on the trailing edge. Each button is square and full-height.
inline− and + side-by-side on the trailing edge. Both square, both full-height.
noneNo visible buttons. Keyboard arrows and scroll wheel still work.

Intents

Same palette as Field — affects border, focus ring colour, and supporting-text colour.

IntentBorderFocus ringSupporting text
noneborder-borderring-ringtext-muted-foreground
errorborder-errorring-errortext-error
warningborder-warningring-warningtext-warning
successborder-successring-successtext-success
infoborder-inforing-infotext-info

Keyboard

KeyAction
↑ / ↓Increment / decrement by step
Shift + ↑ / Shift + ↓Increment / decrement by largeStep
Meta + ↑ / Meta + ↓Increment / decrement by smallStep
Page Up / Page DownIncrement / decrement by largeStep
Home / EndJump to min / max (when set)

Notes

  • Controlled vs uncontrolled: pass value for controlled, defaultValue for uncontrolled. The value can be null when the input is empty.
  • Locale formatting: passing format runs the value through Intl.NumberFormat — currencies, percentages, compact notation, and custom decimal places all work out of the box.
  • Scroll-wheel scrub: opt-in via allowWheelScrub. When enabled, scrolling over a focused field changes the value.
  • RTL: dir="rtl" is supported. Stacked / inline buttons stay on the trailing edge; split places + on the visual right and − on the visual left, matching standard RTL number-input conventions.
  • Accessibility: built on @base-ui/react/number-field. The hidden input is a real <input type="number"> so HTML form submission, native validation, and assistive technologies behave correctly.
  • Form submission: pass name to include the value in standard form submissions.

Textarea

Multi-line text input with intent variants, sizes, label positions, character counter, auto-resize, and LTR/RTL support.

Date Picker

A button-triggered date picker popover built on Calendar and Base UI Popover. Supports single, range, and multiple selection, preset shortcuts, stacked or sidebar preset layouts, footer slot, and min/max constraints.

On this page

PlaygroundInstallationUsageShowcaseNumberField vs StepperAPI ReferencePropsButtons placementIntentsKeyboardNotes