Skip to main content

FieldRow

Forms

Label/description/error/aria-describedby wiring for a single field OUTSIDE a react-hook-form context.

import { FieldRow } from "@elabs-ai/components-ui";
Install
npm install @elabs-ai/components-tokens @elabs-ai/components-ui

Skipping the @source line is the most common first failure: Tailwind does not scan node_modules, so every component renders unstyled. The one-time wiring

Prompt for your coding agent

Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.

Add the brand-ui FieldRow component (from @elabs-ai/components-ui) to this project.

1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`.
2. Look FieldRow up before writing code: `npx -y @elabs-ai/components-cli docs FieldRow` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-ui is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`.
4. Use it here: [the screen or file, and what it should show]
5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop.
6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.

Examples

7 live examples

With help

With error

With help and error

With a Textarea control

Composes with a Textarea just as readily as an Input — any single-element control works.

Driven by external state (no react-hook-form)

Issue #26's fourth ask: a worked example of `FieldRow` driven by a form runtime that is NOT react-hook-form — this is the story the docs and PR point readers at as "starts already invalid so the `aria-invalid`/ `role="alert"` wiring is visible without interaction". Its play function therefore only ASSERTS the error state; it never resolves it, so the canvas a reader inspects (both in the story view and on the autodocs page, where Storybook runs `play` on mount the same way) stays on the documented error state. See "Driven by external state — validity transitions" below for the invalid→valid interaction instead (#26 fix-round-1 finding 1 — the previous version of this story cleared its own error inside `play`, so the state the docs claimed to show never actually survived a render).

Driven by external state — validity transitions

The invalid→valid half of the lifecycle, kept in its own story so the documented error example above (`RuntimeAgnostic`) never settles into the success state on render (#26 fix-round-1 finding 1). Also locks finding 2: "jane@invalid" has an `@` but no valid domain and must NOT clear the error — `value.includes("@")` used to accept it.

API

FieldRow

PropTypeDescription
labelrequiredReactNodeField label — rendered as a real `<label>`, click-to-focus associated with the control.
descriptionReactNodeOptional help/description text, always visible (not gated by an error). Renders nothing — and contributes no id to `aria-describedby` — when `description` is falsy (`false`/`0`/`""`/`null`/`undefined`), an empty array, or an array containing only falsy values (`{list.map(...)}` on an empty list; `[a && "x", b && "y"]` with both false). **Known limit:** a value that is itself a COMPONENT that renders nothing (returns `null`/an empty fragment) is not knowable before render, so it still produces an empty paragraph that `aria-describedby` points at. `FieldDescription` and the wider React ecosystem share this limit — pass a falsy value instead of a component that may render nothing, e.g. `description={hasHint ? hintText : undefined}`, never `description={<MaybeRendersNull />}`.
errorReactNodeValidation error text. When present: sets `aria-invalid` on the control, is appended to `aria-describedby`, and is announced via `role="alert"`. Renders nothing — and leaves `aria-invalid="false"` — when `error` is falsy (`false`/`0`/`""`/`null`/`undefined`), an empty array, or an array containing only falsy values (`{errors.map(...)}` on an empty list — the idiomatic "no errors" shape). **Known limit:** a value that is itself a COMPONENT that renders nothing (returns `null`/an empty fragment) is not knowable before render, so it still produces an empty `role="alert"` element that `aria-describedby` points at. `FieldError` and the wider React ecosystem share this limit — pass a falsy value instead of a component that may render nothing, e.g. `error={hasError ? message : undefined}`, never `error={<MaybeRendersNull />}`.
childrenrequiredReactElementThe single field control (`Input`, `Textarea`, `Select`, …). Receives `id`/`aria-describedby`/`aria-invalid` via a Radix `Slot` — must be a single element that forwards those props to a real form control.

Also accepts: Omit<HTMLAttributes<HTMLDivElement>, "children">