Inspector value rail

Overview

The edit sheet's value field is the source of truth: it is where a value comes from, where a suggestion lands, and what Apply commits. What it is bad at is choosing a number on a phone — the range is invisible, the page's own steps are invisible, and trying 14px instead of 16px costs four taps on a keyboard. The value rail is therefore one numeric changer for every numeric kind: a track whose ticks are the values this page already uses, a thumb you drag, a precision segment, a unit chip, and the before → after readout in its header.

The value rail in the edit sheet at 360 px

Usage

Open the edit sheet for a numeric property (tap a declared row, or a quick-add chip such as padding). The rail sits directly above the value field, under the page's own values and tokens.

  1. Drag the thumb. The value field updates live; nothing is written to the page. Release, then Apply to commit — exactly like the type switch and the suggestion chips.

  1. Tap a tick. The labelled round numbers (0 20 40 60) are buttons: one tap lands the value on that number. A violet tick is a design token, and tapping it writes the token's value (--space-3 = 12px → 12px). A green tick is a fraction of this element — ¼, ½ or 1 of its own size — and tapping it writes that number (½ of this element = 32px). Fractions are only drawn when the sheet read the element's real size: they are a statement about this element, so the rail does not invent them from its own default basis.

  2. Pick a step. The precision segment offers three steps for this value's kind — 1 px / 4 px / 8 px for a length, 0.01 / 0.05 / 0.1 for an opacity, 10 ms / 50 ms / 100 ms for a duration — plus page, which uses the step the value index derived for this property from the page's own values (steps of 4px), and which is the same step the − / + buttons in the value row use.
    The segment cannot be one ladder for everything: a 0–1 opacity range quantized to 1 can only ever write 0 or 1, and a 0–2000 ms range quantized to 1 puts the whole gesture in two pixels of travel. Each family therefore states its own, and the header says which one is in force and where it came from — page step 4px, 0.05 step, or step 10ms.

  3. Cycle the unit. The unit chip rewrites the same value in another unit (px ⇄ rem ⇄ em, ms ⇄ s, deg ⇄ turn ⇄ rad), using the page's real root font size rather than an assumed 16 px. Beside the chips the footer prints the conversion before you spend the tap: px = 0.875rem. A unit whose base size the inspector has not read is shown ticked-out, and then no equivalent is printed for it either.

  4. Read the header. It states the kind, the value the property had before this session's first edit on it (struck through), the value now, and the step in force: length 16px 44px page step 4px.

  5. No rail is stated, not hidden. A value with no numeric range (a keyword, a colour, calc(), an unparsable expression) renders No rail: this value is not a number, so there is nothing to drag. — the typed field stays the only control, which is the honest fallback rather than a disabled slider.

  6. Leaving the scale is one tap, and so is coming back. A value that is off the page's scale draws the dashed ghost ring at the nearest on-scale value; the rail's footer then offers leave scale, which keeps the value as it is, stops drawing the ghost, and switches the drag to the family's finest step. Tapping use scale puts the page's own step and the ring back. The sheet's hint is the other direction of the same decision (Snap to 16px).

  7. The value being typed is placed on the page's scale. When the field holds a value that is not one of the page's own, the rail draws the nearest on-scale value as a dashed ghost ring, and the suggestion row above it names the same value with a one-tap snap.

Every kind, and the view it gets

The rail is the right changer for a number. It is the wrong control for a colour (three numbers that only mean something together), for a four-sided shorthand (four numbers that are one declaration), for a function list (an ordered list of rail-shaped arguments) and for an enum (not a number at all). The edit sheet therefore picks a view per kind, in the ladder the mock states: a numeric kind gets the rail, a numeric shorthand fans out, a known keyword set gets segments, a list of functions gets one rail per argument, and anything else falls back to the typed field with the reason stated.

The view is chosen from the value the property holds when the sheet opens, and held for as long as the sheet is open: dragging one side with "link all sides" on turns 10px 14px 18px 14px into 12px, and the four rails must not collapse into a single slider under the user's finger.

KindViewProperties
Length, number, percent, scale(), custom property holding a numberThe railpadding: 14px, opacity: 0.5, --space-card: 14px
ColourThree rails (hue / saturation / lightness), the page's palette with per-swatch contrast badges, hex / rgb / hsl / color-mix() format chipscolor, background-color, border-color, --brand
Fan-outOne sub-rail per side plus a "link all sides" switchpadding, margin, inset, border-width, border-radius, gap, background-position
EnumSegmented keyword chips, the page's own values first and the CSS-wide keywords lastdisplay, position, overflow, text-align, flex-direction, align-items
FunctionsOne rail per argument, or one row per item for a comma listtransform, filter, box-shadow, text-shadow, transition, animation
ImageThe page's own images and gradients as candidates — no railbackground-image, mask-image, list-style-image
Time / AngleThe rail plus its unit segmenttransition-duration, animation-delay, rotate, hue-rotate
Anything elseThe typed field, with a note saying why there is no viewfont-family, content, calc(), var()

Colour

The three colour rails and the palette with contrast badges at 360 px

Three rails instead of one, because a colour is three numbers that only mean something together, and because each rail's track can then be a picture of the value: the hue track is the full gamut at the current saturation and lightness, the saturation track runs grey → colour, and the lightness track runs black → colour → white.

rgba(110, 168, 254, 0.425)  ->  color-mix(in srgb, #6ea8fe 42.5%, transparent)

In sRGB that leaves the channels untouched and takes the alpha as the weight of the first stop, so the declaration resolves to the colour that was on screen and the rails can keep dragging it. The percentage keeps four decimals, so a 0.425 alpha does not become 0.43. The parser reads the same shape back — including a first stop of rgb(110, 168, 254), whose own commas must not be mistaken for the mix's — and any other mix (in oklab, a second stop that is not transparent, malformed stops) is left to the typed field with the reason rather than approximated.

Time and angle

The rail is the changer; the unit segment is what the kind adds. Time cycles ms ⇄ s and angle cycles deg ⇄ turn ⇄ rad, each converting the current value so the write is the same duration or rotation in another unit (180ms ⇄ 0.18s, 12deg ⇄ 0.0333turn). The angle rail's range is −180…180 and its snaps are the right angles the mock lists — 0° / 45° / 90° / 180°, plus their negatives.

A time rail adds the durations a transition actually uses, one tap each:

transition-duration 0.18s        [ 0.1s ] [ 0.15s ] [ 0.2s ] [ 0.3s ]

The mock's four are 100 / 150 / 200 / 300 ms, and they earn a row of their own because a 0…2000 ms rail is roughly 10 ms per pixel on a phone: 150 ms is not a value a thumb can reliably hit. The chips are written in the rail's own unit — a value in seconds offers 0.15s, not 150ms — and a preset the range cannot hold is dropped rather than clamped. transition-timing-function and animation-timing-function get the other half of the mock's row: their easing keywords are a real choice set, so the enum view offers ease / linear / ease-in / ease-out / ease-in-out / step-start / step-end, the page's own usage first and the CSS-wide keywords last, which beats tapping cubic-bezier(0.4, 0, 0.2, 1) out on a phone.

Enum

The enum segments for <code>display</code> at 360 px

Segmented keyword chips, ranked: the values this page uses first (in the order the index reports them, so flex beats grid on a page that uses flex more), then the property's own spec set, then the CSS-wide keywords last. A value the page uses is marked, because it is the likelier choice.

The view only appears for a property with a real choice set — two or more of its own keywords. keywordsFor always appends inherit / initial / unset / revert, so a "more than one keyword" rule would make every keyword an enum and give transform: none a five-chip row of which four are the CSS-wide ones.

Functions and comma lists

One rail per argument, each typed by its own kind, with the item's name as the row label:

transform                                    [▲] [▼] [✕]
  arg 1   translateY(−4px)
  arg 2   scale(1.02)
Add  [ translateX ] [ rotate ] [ skewX ] [ skewY ] [ none ]

A property that is a list of shorthands rather than of function calls (box-shadow, transition) gets the same view with its items split on the top-level commas instead of on function names, so a shadow's four values are four rails and a two-item transition list is two rows. Splitting is bracket-depth aware, which is what keeps rgba(0, 0, 0, 0.4) and calc(100% - 2px) in one piece.

Fan-out

The padding fan-out with four sub-rails at 360 px

A four-value shorthand becomes four sub-rails, with the value line under them showing what will be written:

padding: 10px 14px 18px 14px
  top     [───●────]  10px
  right   [────●───]  14px
  bottom  [─────●──]  18px
  left    [────●───]  14px
  padding: 10px 14px 18px

Image and text

An image is not a number, so the image view offers the page's own url() and gradient values instead of a rail — the honest alternative to dragging nothing. content is not an image property: it takes a url() but it is a text value first (content: "→", counter(x), attr(data-label)), and the mock lists it under STRING, so it gets the string view; routing it to the image view gave a text value a panel with no candidates in it. Everything else (an unparsable value, a keyword with no choice set, a font stack) renders the typed field with a one-line note stating why there is no view. Nothing is ever a silently disabled control with no explanation.

A string value has no rail and no per-kind view, so what it gets is the page's own values plus the keyword forms:

font-family
On this page · 2 values            [ Inter, system-ui, sans-serif 1× ]  [ Georgia, serif 1× ]
Keywords · valid for any property  [ inherit ] [ initial ] [ unset ] [ revert ]
cursor
On this page · 2 values            [ pointer 2× ]  [ grab 1× ]

The stacks and the cursors are the page's own declarations (the group above); the keyword row is the half a string value could not reach at all, because text is the shape with no view of its own. keywordsFor supplies a property's own words first (cursor → auto, default, pointer, …) and the CSS-wide four last, the row is capped at six, the word already in the field is not offered back, and an enum-shaped property does not get the row — it renders its own chips, and two copies of the same list is worse than one.

Write-back: what the page reads back

The padding fan-out with four sub-rails at 360 px

A fan-out edit drags four sides, and what the sheet writes has to be both the shortest valid form and something the page reads back as the same four values:

10px 10px 10px 10px   ->   10px
10px 20px 10px 20px   ->   10px 20px
4px 8px 12px 8px      ->   4px 8px 12px

The CSS rules are the whole of the arithmetic (all equal → one; top == bottom and left == right → two; left == right → three; otherwise four), and there are three cases where the value must not be collapsed:

padding: 24px 14px 18px is set on element.style (expanded to padding-top and its
siblings), which wins this value for this element only.

Without that fallback the bar would claim padding has no declaration on this element seconds after the user wrote one.

Undo restores every side. One fan-out drag is one receipt entry (padding — → 24px 14px 18px), because the write is one declaration: recordChange merges same-property edits and keeps the original from value, so Undo all returns an element that had no padding of its own to exactly that state — no inline style at all — and the origin sentence goes back to naming the stylesheet rule. A value the sheet wrote is re-read after the write (inline declarations and the matched-rules entry), so the pinned preview, the Declared list, the Computed list and the origin sentence all describe the same page.

Behaviour