A number with no ends, dragged from the row itself: for the values that have no maximum worth naming, like a z-index, an X offset or a letter-spacing. The drag is relative rather than positional, so pressing anywhere is safe, and a soft range gives the bar something to draw against that the value is free to leave. Only the reported number is quantized, so the pointer never fights the grid.
Adds the component and everything it depends on to your project.
npx shadcn add https://ui.beste.co/component/r-base/inspector-scrubNew here? Read the installation guide.
The import and the props worth knowing about, in one place.
import { InspectorScrub } from "@/components/beste/component/inspector-scrub";
// Both bounds optional, which is the point: neither of these has an honest maximum
<InspectorScrub label="Letter spacing" defaultValue={0} step={0.01} suffix="em" sensitivity={8} />
// The bar, for a number that has no ends. `softMin`/`softMax` are the span worth
// working in, not a limit: the fill shows where the value sits in that span, pins
// once it is past it, and the number carries on. With no span there is no bar,
// since a bar over an unknown range would have to invent one.
<InspectorScrub label="Letter spacing" softMin={-0.1} softMax={0.4} step={0.01} suffix="em" />
<InspectorScrub
label="X offset"
value={x}
onValueChange={setX} // every step of the drag
onValueCommit={(next) => console.log("settled on", next)} // when the drag ends
step={1}
precision={0}
sensitivity={4} // pixels of drag per step; lower is faster
suffix="px"
name="x-offset"
tone="outline" // "muted" (default) | "outline" | "ghost"
size="sm" // "sm" | "default" | "lg"
/>
// Bounded on one side only, which no slider can express
<InspectorScrub label="Z index" min={0} step={1} precision={0} />Turn the props on the right; the snippet under them is what you would write to get what you see.
import { InspectorScrub } from "@/components/beste/component/inspector-scrub";
<InspectorScrub
label="Letter spacing"
className="w-72"
defaultValue={0}
step={0.01}
suffix="em"
sensitivity={8}
softMin={-0.1}
softMax={0.4}
/>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.
| Hover the row | Lights the whole track and shows the grip |
|---|---|
| Drag the row | Runs the number with the pointer, from wherever you pressed |
| Shift + drag | Ten times the step |
| Alt + drag | A tenth of it |
| Click the label | Puts the caret in the field to type an exact value |
| Up / Down | Step the value; Shift for ten of them |
| Escape | Throw away what was typed |
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 | number | — | Controlled value. Pair it with `onValueChange`. |
defaultValue | number | — | Initial value in uncontrolled mode. |
onValueChange | (value: number) => void | — | Fires on every change, including each step of a drag. |
onValueCommit | (value: number) => void | — | Fires when a drag ends or an edit is committed. |
min | number | — | Hard bounds, both optional, which is the point of this row. A z-index, an X offset or a letter-spacing has no honest maximum, and a slider that invents one is a slider whose whole range is a lie. What is given here the value is held to. |
max | number | — | |
softMin | number | — | The range the **bar** is drawn against, which the value is free to leave. It is how an endless number gets a picture: the fill shows where the value sits in the span you would usually be working in, pins at the end once it is past that, and the number keeps going. Give both or neither. Falls back to `min` and `max`, so a row with hard bounds already looks like a slider, and a row with none has no bar at all rather than a made-up one. |
softMax | number | — | |
step | number | 1 | What one step of the drag is worth, and what the arrows move by. |
precision | number | 2 | How many decimals the reported value keeps. |
sensitivity | number | 4 | Pixels of drag per step. Lower is faster; the default asks for four pixels a step, which is quick enough to cross a range and slow enough to land on a number. |
suffix | string | — | Printed after the number, e.g. `px`, `%`, `em`. |
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 field, so the row can take part in a form. |
id | string | — | Id of the field, for an external `<label htmlFor>`. |
className | string | — | |
aria-label | string | — | Accessible name. Falls back to `label`. |
The strip that splits a settings panel into two or three questions instead of one long scroll. It is the strip and nothing else: what the tabs switch between stays in the panel's own markup, beside the state that decides it. The pill slides between equal columns, arrow keys both move and select, and a tab can carry a count of what has changed under it.
The row that does something instead of holding something: open a sub-panel, replace an asset, regenerate a key, delete the section. It carries a description, a hint, a busy spinner and a destructive treatment, and renders as a plain anchor when given an href.
Settings row for a span rather than a value: two thumbs on the row itself, the band between them filling it, and both ends of the pair read out on the right. Ticks, tones and thumb states follow inspector-slider exactly, appearing on hover.
Settings row for a point on a surface: the row names the nine spots CSS names and shows the rest as two percentages, and opens a square pad the point is dragged around, with the thirds drawn on it and a pull towards them.