Skip to main content
Amends 0303 (decision 7, the props vocabulary), as 0304 did for wave 2. 0303 carries a note; its body is unchanged.

Context

0303 decision 7 gave every component one props vocabulary, and 0304 extended it for wave 2. Wave 3 adds the fields (TextField, TextArea, InputGroup, SearchField, Select, ComboBox, TagGroup, Checkbox, CheckboxGroup, RadioGroup, Switch), the field parts (Label, Description, FieldError) and Form, FormField and FormSection. They need words that the table does not hold, and one of them, label, is used with a wider type than the row of 0303 allows. An Accepted ADR’s body changes only for typos and Enforcement, so the additions are recorded here.

Decision

  1. The wave 3 vocabulary. Decision 7 of 0303 stands; wave 3 adds or extends these rows: Because a field’s label is a node, a field without a visible label names itself with the runtime’s own name prop (aria-label on web, accessibilityLabel on native), which passes through.
  2. One binding for a form library. FormField hands its render function a FormFieldBinding<TValue> (name, value, onChange, onBlur, isInvalid, errorMessage, isRequired, isDisabled), built by one pure function in core/. An empty validation message reads as no message, so a field never shows an empty error node.
  3. react-hook-form is a required peer of @stackra/ui. Both runtime entries export FormField, which imports it.

Consequences

  • A field reads the same on web and native, and a form library binds to any of them with one spread.
  • label has two meanings by component kind (a visible node on a field, a string name elsewhere), which this page and the contract’s own doc comments state.
  • An app that imports @stackra/ui/react or /native installs react-hook-form even if it never draws a form.

Enforcement

  • Each component’s type test asserts that the runtime props extend the contract and refuse the raw HeroUI names this vocabulary replaces.
  • tools/scripts/spec-trace.mjs (run by pnpm validate) refuses a spec id that no test covers.
  • Until a lint rule exists, review (architecture-guardian) checks this page.

Alternatives

  • A string label and a separate labelNode. Not chosen: two props for one concept, and every adapter would have to choose.
  • react-hook-form as an optional peer with FormField on its own subpath. Not chosen: a third entry per runtime for one component, against the one-entry-per-runtime shape of 0303.

Supersedes and sources

Supersedes: none. Amends 0303 decision 7. Owner answers of 2026-10-23 on the wave 3 design (STA-364 to STA-369).