A number and the unit that gives it meaning, in one row: px, rem, a percentage, or a keyword like auto that is the whole value on its own. Each unit may bring its own range, so a percentage stops at 100 while pixels do not.
The import and the props worth knowing about, in one place.
import { InspectorUnit } from "@/components/beste/component/inspector-unit";
// Uncontrolled
<InspectorUnit label="Width" defaultValue={{ value: 100, unit: "%" }} />
// Controlled, with a separate commit for expensive work
<InspectorUnit
label="Max width"
value={width}
onValueChange={setWidth}
onValueCommit={(value) => save(value)}
/>
// A unit that is the whole value: the number field steps aside rather than sitting
// there holding a figure that means nothing
<InspectorUnit
label="Height"
units={["px", "%", "vh", { value: "auto", valueless: true }]}
value={height}
onValueChange={setHeight}
/>
// A range per unit, since 0 to 100 reads as a percentage and 0 to 9999 does not
<InspectorUnit
label="Offset"
units={[
{ value: "px", min: -400, max: 400 },
{ value: "%", min: -100, max: 100 },
{ value: "rem", min: -24, max: 24, step: 0.25 },
]}
value={offset}
onValueChange={setOffset}
/>
<InspectorUnit
label="Letter spacing"
icon={TypeIcon} // optional leading icon
tone="outline" // "muted" (default) | "outline" | "ghost"
size="sm" // "sm" | "default" | "lg"
units={["em", "px"]}
step={0.01}
precision={3} // decimals kept on commit
name="tracking" // submits as tracking and tracking-unit
value={tracking}
onValueChange={setTracking}
/>
// What the value turns into
const css = value.unit === "auto" ? "auto" : `${value.value}${value.unit}`;
// The arrows work the field the way they work a stepper, and Shift moves ten steps.Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| Arrow Up / Down | One step on the number, the way a stepper moves: a length is a number first and a string second. |
|---|---|
| Shift + Arrow | Ten steps. |
| Enter | Accept what was typed. |
| Escape | Throw the draft away and put the committed value back. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | Label rendered on the left, inside the row. |
icon | LucideIcon | — | Optional leading icon shown before the label. |
value | UnitValue | — | Controlled value. Pair it with `onValueChange`. |
defaultValue | UnitValue | — | Initial value in uncontrolled mode. |
onValueChange | (value: UnitValue) => void | — | Fires on every accepted edit and on every unit change. |
onValueCommit | (value: UnitValue) => void | — | Fires once an edit is finished — the field left or Enter pressed, a unit chosen. Use it for work too expensive to run per keystroke. |
units | (string | InspectorUnitOption)[] | ["px", "%", "rem"] | The units on offer. Plain strings are enough when the unit is its own label. |
min | number | 0 to 9999, in steps of 1 | Range the number answers to, for units that do not name their own. |
max | number | — | |
step | number | — | |
precision | number | 2 | How many decimals a value keeps. Anything typed past this is rounded on commit, so what the field shows is what the caller was handed. |
disabled | boolean | — | Block interaction and dim the row. |
tone | "muted" | "outline" | "ghost" | "muted" | Surface treatment: filled (default), hairline outline, or bare until hover. |
size | "sm" | "default" | "lg" | "default" | Row height preset. |
name | string | — | Name of the number field, so the row can take part in a form. |
id | string | — | Id of the number field, for an external `<label htmlFor>`. |
className | string | — | |
aria-label | string | — | Accessible name. Falls back to `label`. |