opsinjs
ComponentsHealth input

ReadingInput

A field for typing in a measurement, with the unit shown and switchable beside the number, and room for an advisory the product wrote.

Preview

Theme
Density
Text
Status
reading-input · base base · style base-lyraOpen under the product theme

Installation

pnpm dlx shadcn@latest add @opsinjs/reading-input

The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.

Usage

import { ReadingInput } from "@/components/ui/reading-input"
<ReadingInput
  label="Example measurement"
  unit={unit}
  units={["kg", "lb", "st"]}
  precision={1}
  value={reading}
  onChange={(next) => {
    setReading(next.value)
    setUnit(next.unit)
  }}
/>

When to use it

Use it when

  • Anywhere a person types a measurement of themselves: weight, blood pressure, glucose, temperature.
  • Entry where the unit is part of the value.
  • Entry where the product flags an unlikely number without stopping anybody.

Do not use it when

  • A quantity, a count or a code, not a measurement of a person.Use number-field instead.
  • The value is best chosen from a small set rather than typed.Use segmented-control instead.
  • The reader estimates the value, such as a pain score, not measures it.Use scale-input instead.
  • You are displaying a reading, not collecting one.Use value instead.
  • Several related measurements captured in one interaction.Use log-sheet instead.

Clinical meaning

Asserts. That the reader typed this number, in this unit, as a measurement of themselves. It is always self-reported.

Never read as. A validated measurement, a device reading or an assessed value.

Colour axis. Neither; the advisory is ordinary foreground.

Thresholds. None. The component holds no bound. The product supplies the finished warning, which never sets aria-invalid, moves focus or blocks submission. Staleness belongs to Uncertainty, staleness and missing data.

Unit switches. Only definitional conversions from tokens/units.json: kg, lb, st, °C and °F. mmol/L to mg/dL is refused, because the factor is the substance's molar mass, so that switch clears the entry and says so. See Unit systems and Numbers, units and precision.

Anatomy

  1. ReadingInputA div for a single reading, a fieldset for a compound one.
  2. FieldEmbedded whole. Owns the label, descriptions, invalid state and 44px floor.Controlled by label
  3. ReadingInput.LegendCompound only: the measurement's name, as a legend.Controlled by label
  4. ReadingInput.HintCompound only: the group's guidance, in every box's description.Controlled by hint
  5. ReadingInput.ControlThe row of boxes and the unit. It wraps at 200% text.
  6. ReadingInput.SegmentOne labelled box carrying data-opsinjs-value, one per part.Controlled by segments
  7. ReadingInput.UnitA native select at the 44px floor, or plain text for one unit.Controlled by units
  8. ReadingInput.WarningThe product's advisory, as a description rather than an error.Controlled by warning
  9. ReadingInput.EffectWhat a switch did: converted, cleared or restored. Gone once a box is touched.

Examples

Two numbers that are one measurement

segments turns the field into a <fieldset> with a <legend>, one box per part.

Theme
Density
Text
Status
reading-input-a-compound-reading · base base · style base-lyraOpen under the product theme

Switching the unit, and the sentence that follows it

precision sets the decimal places a conversion rounds to.

Theme
Density
Text
Status
reading-input-switching-the-unit · base base · style base-lyraOpen under the product theme

A sentence the product wrote

warning shows the product's sentence without comparing anything.

Theme
Density
Text
Status
reading-input-a-warning-the-product-owns · base base · style base-lyraOpen under the product theme

Content guidelines

Label the measurement in the reader's words and put the unit beside the field, not in the label. Hints show the shape of an answer, never a sample reading and never a bound. Advisories ask a question and offer the likely fix.

Do

"That is a long way above most readings. Did you mean to type a decimal point?"

Don’t

"Invalid value. That reading is outside the accepted range." blames the reader.

Accessibility

Audited against WCAG 2.2 AA in a source pass and a rendered pass, and pnpm run check:a11y runs on every commit. The author ran the audit, so it is not independent, and clinical review is pending.

Fixed in this pass. autoComplete is now a prop reaching Field.Control, defaulting to "off", because a self-reported reading has no WCAG autofill token and the component invents none (1.3.5).

From the source. Field renders a real <label for>, and a compound reading is a <fieldset> of one Field per part. The unit's spoken form joins the box's description, each option carries its plural on aria-label, and the box is type="text" with inputMode defaulting to decimal. The advisory and the effect line are descriptions: no aria-invalid and no focus move.

Left to the product. The effect line reporting a switch sits in no live region, so a product must wire effectText to a polite announcer. Without that, a non-sighted reader hears the new unit but not that the number changed.

Not gated. No hit area is measured, and the boundary inherits --border without a gated pair.

KeyActionNotes
TabMoves to the box, then the unit switchThree stops on a compound reading: two boxes and the switch.
Shift+TabMoves backwards through the same stopsReverse order matches the visual order.
Any digit, . or ,Types into the focused boxNothing is rejected, clamped or reformatted while typing.
Up / DownChanges the unit when the switch has focusNative select. The entry converts or clears, and the line says which.
EnterWhatever the surrounding form doesNothing is swallowed. Set enterKeyHint to name the return key's action.
EscapeNothing hereNo overlay to dismiss, no entry to revert.
PairThemeAPCA LcWCAG 2.2Floor
body text on the pagelight101.617.18:1Pass
body text on the pagedark-100.518.00:1Pass
body text on a cardlight104.717.96:1Pass
body text on a carddark-99.616.32:1Pass
body text on the muted groundlight98.116.32:1Pass
body text on the muted grounddark-97.513.77:1Pass
secondary text on the pagelight83.97.42:1Pass
secondary text on the pagedark-80.713.44:1Pass
secondary text on a cardlight87.07.76:1Pass
secondary text on a carddark-79.812.19:1Pass
secondary text on the muted groundlight80.47.05:1Pass
secondary text on the muted grounddark-77.810.28:1Pass
a hairline boundary on the pagelight19.31.41:1Below floor
a hairline boundary on the pagedark-8.21.90:1Below floor
a hairline boundary on a cardlight22.41.47:1Below floor
a hairline boundary on a carddark-7.31.72:1Below floor
a hairline boundary on the muted groundlight15.81.34:1Below floor
a hairline boundary on the muted grounddark0.01.45:1Below floor
a placeholder boundary on the pagelight69.84.52:1Pass
a placeholder boundary on the pagedark-51.07.62:1Pass
a placeholder boundary on a cardlight72.84.72:1Pass
a placeholder boundary on a carddark-50.16.91:1Pass
a placeholder boundary on the muted groundlight66.24.29:1Pass
a placeholder boundary on the muted grounddark-48.15.83:1Pass
a placeholder fill on the pagelight8.31.18:1Below floor
a placeholder fill on the pagedark-8.21.90:1Below floor
a placeholder fill on a cardlight11.41.23:1Below floor
a placeholder fill on a carddark-7.31.72:1Below floor
a placeholder fill on the muted groundlight0.01.12:1Below floor
a placeholder fill on the muted grounddark0.01.45:1Below floor
a placeholder boundary on its own filllight58.93.84:1Pass
a placeholder boundary on its own filldark-41.54.01:1Below floor
the card hairline on the pagelight69.84.52:1Pass
the card hairline on the pagedark-28.74.19:1Below floor
the card hairline on a cardlight72.84.72:1Pass
the card hairline on a carddark-27.93.80:1Below floor

These are the measured token pairs this component draws colour from, not a measurement of the component itself.

Data attributes

AttributeConditionValue
data-slotOn the root, the control row, every segment and the unit, always.reading-input, reading-input-control, reading-input-segment, reading-input-unit
data-slotLegend and hint on a compound reading, warning when supplied, effect after a switch.reading-input-legend, reading-input-hint, reading-input-warning, reading-input-effect
data-opsinjs-valueOn every ReadingInput.Segment, alwaysThe number in that box, read off the box not the prop
data-status, data-categoryNever, at any value of any propNever set

API reference

Prop

Type

Generated from ReadingInputProps in registry/bases/base/reading-input.tsx.

unit is required and value is in it. Without precision a conversion is not rounded. There is no error prop: an error belongs to Field.

  • Field is what this is built on, without a unit or an advisory.
  • NumberField is a plain numeric input with no clinical semantics.
  • Value is the display counterpart, and both must agree on precision and units.
  • ResultCard is where a compound reading goes, in the same segments shape.
  • ScaleInput is for estimated scores such as pain, which are not measurements.

On this page