/ component

Unit Input

CSS-unit input with built-in deg/%/px/rem/em/vw/vh validators, pointer-locked drag scrubbing, and tiered typing tiers from casual to strict.

/ tier-casual

Basic Usage

Two UnitInputs, two units. Type a number; commit on blur or Enter. Arrow keys step ±1, Shift+Arrow ±10. Hover the suffix to scrub.

45deg, 50%
/ drag-to-scrub

Pointer-lock Scrub

Hover the px suffix → cursor turns into ↔ → drag horizontally. The cursor disappears (pointer-lock) and the value tracks your delta. Shift = coarse (×10), Alt = fine (÷10).

16px
/ tier-strict

Strict Typing

unit="deg" narrows the value/onChange types to DegString. The deg() call-site helper rejects wrong-suffix literals at compile time.

90deg
/ api

API

Public surface — component props, runtime helpers, and the type exports.

§ Component

<UnitInput<TUnit extends KnownUnit | (string & {})>
  value: UnitStringMap[TUnit] | (string & {}) | string
  onChange: (next: UnitStringMap[TUnit] | string) => void
  unit: TUnit
  min?: number
  max?: number
  step?: number
  precision?: number
  dragSensitivity?: number
  prefix?: React.ReactNode
  suffix?: React.ReactNode
  disabled?: boolean
  aria-label?: string
  className?: string
/>
Prop
Type
Description
valueUnitStringMap[TUnit] | (string & {})Current value, e.g. "45deg". Any string accepted; IntelliSense narrows by `unit`.
onChange(next) => voidCalled on commit (blur, Enter, arrow, scrub). Return type narrows by `unit`.
unitKnownUnit | (string & {})Suffix label. Known units (deg/%/px/rem/em/vw/vh) get strict typing; unknown widens to string.
minnumber?Inclusive lower bound. Soft-clamps on commit.
maxnumber?Inclusive upper bound. Soft-clamps on commit.
stepnumber (default 1)Arrow-key step. Shift=×10, Alt=÷10.
precisionnumber (default 0)Decimal places. Round via toFixed on commit.
dragSensitivitynumber (default 1)Scrub: 1px = step × sensitivity.
prefixReact.ReactNode?Left-side slot inside the shell, shares focus ring.
suffixReact.ReactNode?Override the default unit-text suffix. Scrub handle attaches here.
disabledbooleanDisables typing, arrow nudge, and scrub.
aria-labelstringRequired when no visible label is associated externally.
classNamestringApplied to the shell wrapper for sizing/spacing.

§ Runtime helpers

deg<S extends string>(value: S & DegLiteral<S>): S

Validate a deg literal at the call site.

percent<S extends string>(value: S & PercentLiteral<S>): S

Validate a % literal at the call site.

px<S extends string>(value: S & PxLiteral<S>): S

Validate a px literal.

rem<S extends string>(value: S & RemLiteral<S>): S

Validate a rem literal.

em<S extends string>(value: S & EmLiteral<S>): S

Validate an em literal.

vw<S extends string>(value: S & VwLiteral<S>): S

Validate a vw literal.

vh<S extends string>(value: S & VhLiteral<S>): S

Validate a vh literal.

§ Types

UnitStringUnion of all 7 suggestion strings (DegString | PercentString | …).
UnitStringMap{ deg: DegString, "%": PercentString, px: PxString, rem: RemString, em: EmString, vw: VwString, vh: VhString }
KnownUnitkeyof UnitStringMap — "deg" | "%" | "px" | "rem" | "em" | "vw" | "vh".
UnitLiteral<S>Union of all strict literal validators. Returns S if S is a valid unit literal in any of the known shapes, otherwise never.
/ install

Drop it in

One command via the shadcn CLI.

$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/unit-input.json