unit-field — headless numeric input for React. Drag-to-scrub, keyboard stepping, format and parse controls, committed value events. Build any numeric field.

npm install unit-field

Examples

01

Properties

Drag the labels to scrub. Arrow keys for fine control, shift for large steps.

Width
Height
Radius
Rotation
Opacity
Border
02

Padding

Grouped fields for spacing values.

24241616
03

Committed value

Live value updates on drag, committed value fires on release.

Live50%
Committed50%
04

Typography

Font size, line height, and letter spacing.

Size
Leading
Tracking

Aa Bb Cc

Usage

Install

Add unit-field to your project. Works with any React 18+ or 19+ setup.

npm install unit-field

Basic field

Compose a numeric field with a draggable label and input. Format and parse control how values are displayed and read back.

unit-field.tsx
import { UnitField } from 'unit-field';
import { useState } from 'react';

function WidthField() {
  const [width, setWidth] = useState(100);

  return (
    <UnitField.Root
      value={width}
      onValueChange={setWidth}
      min={0}
      max={1000}
      format={(v) => `${v}px`}
      parse={(v) => parseInt(v)}
    >
      <UnitField.DragArea>W</UnitField.DragArea>
      <UnitField.Input aria-label="Width" />
    </UnitField.Root>
  );
}

Drag to scrub

The drag area supports sensitivity control and Shift for large steps. Arrow keys, Home, End, Page Up/Down all work out of the box.

unit-field.tsx
<UnitField.Root
  value={rotation}
  onValueChange={setRotation}
  min={-360}
  max={360}
  step={1}
  largeStep={15}
  format={(v) => `${v}°`}
  parse={(v) => parseInt(v)}
>
  <UnitField.DragArea sensitivity={2}>
    Rotate
  </UnitField.DragArea>
  <UnitField.Input aria-label="Rotation" />
</UnitField.Root>

Committed values

Use onValueCommitted to distinguish between live scrubbing updates and final committed values — perfect for undo history or saving state.

unit-field.tsx
<UnitField.Root
  value={fontSize}
  onValueChange={setFontSize}
  onValueCommitted={(v) => {
    // Fires on Enter, blur, or drag end
    saveFontSize(v);
  }}
  min={8}
  max={120}
  format={(v) => `${v}px`}
  parse={(v) => parseInt(v)}
>
  <UnitField.DragArea>Size</UnitField.DragArea>
  <UnitField.Input aria-label="Font size" />
</UnitField.Root>