Kinetic

A Figma-like Scrub Number Input for React: drag to scrub, click to type. Digit animation by . Owned scrub/edit engine.

Full API reference: (Features tab).

Features

  • Horizontal and vertical scrub with pointer-lock gesture
  • Click-to-edit with select-all on focus
  • Keyboard: Up/Down arrows, Alt (smallStep), Shift (largeStep), Home/End
  • Optional wheel scroll and logo-handle scrub surface
  • Bound feedback: shake or border pulse
  • Digit animation by (slots or number variant)
  • Overflow: left-aligned values; long values clip on the right, full value on hover/edit

Installation

Use the Command tab for registry install, Manual for copy-paste setup. API reference lives on the Features tab. The live demo at /demo mirrors the playground structure.

Command

Add the registry to components.json:

json
{  "registries": {    "@kinetic": "https://kinetic.itsjay.in/r/{name}.json"  }}

Then install:

bash
pnpm dlx shadcn@latest add @kinetic/scrub-number-field

Prerequisites: shadcn base-nova with input and input-group.

Manual

  1. Install dependencies
bash
pnpm add calligraph motion lucide-react
  1. Add shadcn components
bash
pnpm dlx shadcn@latest add input input-group
  1. Copy source files into your project:
  • components/ui/scrub-number-input.tsx
  • components/ui/scrub-number-input.css
  • lib/scrub-number-overflow.ts
  • hooks/use-controllable-state.tsx
  1. Update import paths to match your @/ aliases.

Quick start

tsx
import { useState } from "react"import { ScrubNumberField } from "@/components/ui/scrub-number-input"
export function PositionField() {  const [x, setX] = useState(0)
  return (    <ScrubNumberField      label="X"      aria-label="X position"      value={x}      onValueChange={setX}      onValueCommitted={(value) => console.log("committed", value)}      min={-500}      max={500}      step={1}    />  )}

Uncontrolled

tsx
<ScrubNumberField  aria-label="Opacity"  defaultValue={100}  onValueChange={setOpacity}  min={0}  max={100}/>

Custom formatting

tsx
<ScrubNumberField  aria-label="Price"  value={price}  onValueChange={setPrice}  format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }}/>

Use format (Intl.NumberFormatOptions) for display formatting via formatValue.

Display overflow

Fields stay a fixed width so digit animation does not shift layout. When a value is too long:

  • Display mode: left-aligned; overflow clips on the right.
  • Hover: native title tooltip shows the full formatted value when truncated.
  • Edit mode: click opens the full value string, not the clipped display.

Main props (ScrubNumberField)

PropTypeDescription
valuenumberControlled value
defaultValuenumberInitial value when uncontrolled
onValueChange(n: number) => voidFires on every change
onValueCommitted(n: number) => voidFires on blur after edit, or when scrub ends
defaultResetValuenumberReset target for double-click on the logo handle (logo mode)
min / maxnumberClamp bounds
stepnumberNormal increment (default 1)
smallStepnumberFine increment while Alt held (default 0.1)
largeStepnumberCoarse increment while Shift held (default 10)
allowWheelScrubbooleanWheel nudge while focused
direction"horizontal" | "vertical"Scrub axis
pixelSensitivitynumberPixels per step while scrubbing (default 2)
formatIntl.NumberFormatOptionsDisplay formatting
boundFeedback"none" | "shake" | "borderPulse"Feedback at min/max
calligraphCalligraphSettingsDigit animation variant
inputSettingsInputSettingsSelect-on-edit
logoLogoSettingsLogo handle scrub mode; double-click logo to reset
groupedbooleanBorderless control for use inside InputGroup
labelstringOptional side label
disabledbooleanDisable interaction

Gestures

ActionResult
Drag field / handleScrub by step
Up/Down arrowsNudge by step
Alt + drag / Alt + Up/Down / wheelsmallStep
Shift + drag / Shift + Up/Down / wheellargeStep
Wheel (allowWheelScrub)Step while focused
Click fieldEnter edit mode
Home / EndJump to min / max
Double-click logo handleReset to defaultResetValue (logo mode)
EnterCommit edit
EscapeRevert edit

Up/Down arrows nudge by step. Alt = fine step (smallStep), Shift = coarse step (largeStep).

Publishing

Kinetic distributes via the shadcn namespace registry at . See docs/DISTRIBUTION.md [blocked].

Install

See above. The demo at /demo copies generated props from the Usage section after you tweak settings.

Demo

Run pnpm dev and open /demo. Adjust settings in the panel, then copy the generated props snippet from the Usage section.

Build registry locally

bash
pnpm registry:buildpnpm registry:validate

Outputs public/r/scrub-number-field.json. Test against http://localhost:3000/r/{name}.json before deploy.

Development

bash
pnpm installpnpm dev              # home at http://localhost:3000, demo at /demopnpm testpnpm lintpnpm build            # registry:build + next buildpnpm registry:buildpnpm registry:validate

License

MIT