import { FieldRoot } 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 FieldRoot 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 FieldRoot up before writing code: `npx -y @elabs-ai/components-cli docs FieldRoot` 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 description and error
Two controls in one row
#43's first motivating case: `FieldRow`'s `children: ReactElement` can only ever wrap ONE control, so a first/last-name row (or a control plus an inline unit suffix) can't be expressed. Here two `FieldControl`s share one `FieldRoot` — each gets its own independent `id` (given explicitly, the same way any two form controls would need distinct ids) while both read the SAME `invalid`/`aria-describedby` wiring from context. `FieldLabel`'s `htmlFor` can only point to ONE control (the first), so each `Input` also carries its own `aria-label` — a shared visual label plus a placeholder is not enough of an accessible name for the second control (axe: `label-title-only`).
Description between label and control
#43's second motivating case: `FieldRow` always renders description AFTER the control. Here `FieldDescription` is placed BEFORE `FieldControl` in JSX — the visible order follows the JSX order, but `aria-describedby` wiring is unaffected either way (`FieldRoot` composes it from context, not DOM position).
API
FieldRoot
| Prop | Type | Description |
|---|---|---|
invalid | boolean | Whether the field is currently invalid. Drives `aria-invalid` on every `FieldControl` and error styling on `FieldLabel` — independent of whether a `FieldError` with content is actually mounted, so a caller can mark the control invalid before its message has settled. |
required | boolean | Whether the field is required. Drives `aria-required` on every `FieldControl`. |
FieldControl
| Prop | Type | Description |
|---|---|---|
childrenrequired | ReactElement<{ id?: string; "aria-describedby"?: string; }> | The single field control (`Input`, `Textarea`, `Select`, …). Receives `id`/`aria-describedby`/`aria-invalid`/`aria-required` via a Radix `Slot` — must be a single element that forwards those props to a real form control. Compose more than one `FieldControl` inside one `FieldRoot` for a multi-control row (each gets its own independent id; give every control but the first its own explicit `id`, the same way you would for any two form controls that must not collide). `FieldLabel` can only associate with ONE control (the first to mount), so give every OTHER control in the row its own `aria-label` — a shared visual label plus a placeholder is not a real accessible name for the rest. |