import { FieldRow } from "@elabs-ai/components-ui";npm install @elabs-ai/components-tokens @elabs-ai/components-uiPaste 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
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
| Prop | Type | Description |
|---|---|---|
labelrequired | ReactNode | Field label — rendered as a real `<label>`, click-to-focus associated with the control. |
description | ReactNode | Optional 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 />}`. |
error | ReactNode | Validation 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 />}`. |
childrenrequired | ReactElement | The 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. |