opsinjs
ComponentsActions and forms

Field

The wrapper that gives every input a visible label, a hint, an error message and the wiring between them. It is why no control here can ship without a name.

Preview

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

Installation

pnpm dlx shadcn@latest add @opsinjs/field

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 { Field } from "@/components/ui/field"
<Field label="Example measurement" hint="For example, 14">
  <Field.Control name="example-measurement" inputMode="decimal" autoComplete="off" />
</Field>

When to use it

Use it when

  • Every input in the system, where a Field is the label guarantee.
  • Any control needing a hint or error wired to it, not merely placed nearby.
  • As the base for purpose-built inputs, the way ReadingInput is built.

Do not use it when

  • A measurement with a unit and a plausible range.Use reading-input instead.
  • The control is an action, not an input.Use button instead.
  • Several related inputs are captured in one interaction.Use log-sheet instead.
  • You are showing a value, not collecting one.Use value instead.
  • A failed form's errors need summarising at the top.Use alert-banner instead.

Anatomy

  1. FieldThe root. Owns every id relationship and draws the invalid left rule.Controlled by validateOn
  2. Field.LabelA real label element against the generated control id.Controlled by label
  3. Field.OptionalityThe word required or optional, joining the accessible name.Controlled by optionality
  4. Field.HintGuidance before the mistake, announced on focus through aria-describedby.Controlled by hint
  5. Field.ControlThe product's control and the only real export. Takes aria-invalid and the target floor.Controlled by children
  6. Field.ErrorThe message and its CircleAlert glyph, never replacing the hint.Controlled by error
  • Field 1data-slot="field"; renders a <div>, gains data-invalid when the field is invalid
    • Field.Label 1data-slot="field-label"; a native <label for> against the control id
      • Field.Optionality 0..1data-slot="field-optionality"; nested inside the label, absent when optionality="none"
    • Field.Hint 0..1data-slot="field-hint"; renders a <p>, its id joins aria-describedby
    • Field.Error 0..1data-slot="field-error"; its id joins aria-describedby beside the hint
    • Field.Control 1data-slot="field-control"; the consumer’s element, passed as children

Examples

The error keeps the hint

error adds a sentence beside the hint rather than replacing it.

Theme
Density
Text
Status
field-error-keeps-the-hint · base base · style base-lyraOpen under the product theme

Marking the exception

One optional field among three needed ones, marked by optionality="optional". See Required and optional.

Theme
Density
Text
Status
field-marking-the-exception · base base · style base-lyraOpen under the product theme

A control opsinjs does not ship

render on Field.Control puts the whole wiring onto a textarea and a select.

Theme
Density
Text
Status
field-with-another-control · base base · style base-lyraOpen under the product theme

Validating on submit

The default validateOn fires on submit only inside Field.Form, where an empty field on Save shows the browser's own message. Pass error instead.

Theme
Density
Text
Status
field-validating-on-submit · base base · style base-lyraOpen under the product theme

Content guidelines

Labels are nouns in sentence case with no colon. Hints show the shape of a valid answer, never a number a reader could read as a result. Errors say what to fix, without blame.

Do

"Date of birth" over three labelled fields, error "Enter a date in the past".

Don’t

"DOB" with "Invalid input."* An abbreviation, an unexplained symbol, no fix.

Accessibility

Audited against WCAG 2.2 AA in a source pass and a rendered pass on /view/base/base-lyra/component/field. The audit is author-run, not an independent review, and clinical review is pending. The rendered pass was clean: no axe violations, targets met --opsin-target-minimum, focus visible, no reflow at 320px.

Fixed here. The error now carries a live region, so it is announced on submit while focus is on Save, where it was silent before.

From source. A native label against a generated id; aria-describedby survives unmount; aria-invalid sits on the control; the invalid state is a glyph and a left rule, never colour. pnpm run check:a11y guards role tokens and non-px sizes.

Limits. The required and optional word is English only and untranslatable by prop. The focus ring lives in product.css, so a shadcn add copy without it shows no ring until you add one. The invalid shadow drops under forced colours and print, where the glyph, words and rule still carry the state.

KeyActionNotes
TabMoves focus to the controlOnly the control is focusable.
Shift+TabMoves focus back out of the controlReverse order matches visual order.
EnterAsks the control to check itself, and submits an enclosing formFocus does not move and nothing is announced.
EscapeNothingNot a popup. The key reaches a dialog.
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-slotRoot, label and control always; optionality, hint and error only when that part renders; Field.Form where mounted.field, field-label, field-optionality, field-hint, field-error, field-control, field-form
data-invalidOn the root and control, when error is non-empty or a constraint fails.Present with no value when invalid; absent otherwise

API reference

Prop

Type

Generated from FieldProps in registry/bases/base/field.tsx.

label is required and there is no hideLabel. An empty label or error is refused with a development warning. Without a Field.Form ancestor the default validateOn checks on Enter alone.

Prop

Type

Generated from FieldControlProps in registry/bases/base/field.tsx.

  • ReadingInput is a Field with a unit and a plausibility contract.
  • LogSheet captures several Fields together.
  • Callout is guidance about a whole form, not one control.
  • AlertBanner is the summary at the top of a failed form.

On this page