arikchakma

Unit Field

Component 1

About

Headless, composable numeric input field with drag-to-scrub, keyboard stepping, format/parse controls, and committed-value events for building any unit field.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Unit Field to your project and fits it to your theme.

Code

How Unit Field is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import * as React from "react";
import { UnitField } from "@/components/ui/unit-field";

const INPUT =
  "w-full bg-transparent pr-2.5 text-right font-mono text-sm text-gray-700 tabular-nums outline-none placeholder:text-gray-300";

const DRAG_AREA =
  "flex aspect-square w-9 shrink-0 cursor-ew-resize items-center justify-center text-[12px] font-medium text-gray-400 select-none";

const ROOT =
  "relative flex h-9 w-full items-stretch border border-gray-200 transition-colors data-[focused=true]:border-gray-400";

function pxFormat(v: number) {
  return `${v}px`;
}

function pxParse(v: string) {
  const n = parseInt(v.replace("px", ""), 10);
  return isNaN(n) ? null : n;
}

function pctFormat(v: number) {
  return `${v}%`;
}

function pctParse(v: string) {
  const n = parseInt(v.replace("%", ""), 10);
  return isNaN(n) ? null : n;
}

function FieldRow(props: { label: string; children: React.ReactNode }) {
  return (
    <div className="grid grid-cols-[80px_1fr] items-center gap-3">
      <span className="text-[13px] text-gray-400">{props.label}</span>
      {props.children}
    </div>
  );
}

export default function UnitFieldTypographyDemo() {
  const [fontSize, setFontSize] = React.useState(24);
  const [lineHeight, setLineHeight] = React.useState(150);
  const [letterSpacing, setLetterSpacing] = React.useState(0);

  return (
    <div className="mx-auto w-full max-w-sm bg-white p-6 text-gray-700">
      <header className="mb-3">
        <div className="flex items-center gap-2">
          <span className="font-mono text-[11px] text-gray-400">04</span>
          <h3 className="text-[14px] font-medium text-black">Typography</h3>
        </div>
        <p className="mt-1 text-[13px] text-gray-500">
          Font size, line height, and letter spacing.
        </p>
      </header>

      <div className="space-y-1.5">
        <FieldRow label="Size">
          <UnitField.Root
            className={ROOT}
            format={pxFormat}
            parse={pxParse}
            value={fontSize}
            onValueChange={setFontSize}
            min={8}
            max={120}
          >
            <UnitField.DragArea className={DRAG_AREA}>Sz</UnitField.DragArea>
            <UnitField.Input className={INPUT} />
          </UnitField.Root>
        </FieldRow>
        <FieldRow label="Leading">
          <UnitField.Root
            className={ROOT}
            format={pctFormat}
            parse={pctParse}
            value={lineHeight}
            onValueChange={setLineHeight}
            min={50}
            max={300}
          >
            <UnitField.DragArea className={DRAG_AREA}>Ld</UnitField.DragArea>
            <UnitField.Input className={INPUT} />
          </UnitField.Root>
        </FieldRow>
        <FieldRow label="Tracking">
          <UnitField.Root
            className={ROOT}
            format={pxFormat}
            parse={(v) => {
              const n = parseFloat(v.replace("px", ""));
              return isNaN(n) ? null : n;
            }}
            value={letterSpacing}
            onValueChange={setLetterSpacing}
            min={-10}
            max={20}
            step={0.5}
          >
            <UnitField.DragArea className={DRAG_AREA}>Tk</UnitField.DragArea>
            <UnitField.Input className={INPUT} />
          </UnitField.Root>
        </FieldRow>
      </div>

      <div className="mt-4 overflow-hidden border border-gray-200 p-4">
        <p
          className="font-medium text-black transition-all"
          style={{
            fontSize: `${fontSize}px`,
            lineHeight: `${lineHeight}%`,
            letterSpacing: `${letterSpacing}px`,
          }}
        >
          Aa Bb Cc
        </p>
      </div>
    </div>
  );
}

More by arikchakma

View all
  • Cromia Color Picker

    Cromia Color Picker

    42
  • Cromia Compact

    Cromia Compact

    18
  • Cromia Channels

    Cromia Channels

    14
  • Cromia Swatches

    Cromia Swatches

    10