import { SchemaForm } 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 SchemaForm 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 SchemaForm up before writing code: `npx -y @elabs-ai/components-cli docs SchemaForm` 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
Controlled
Controlled — the parent owns `values`; a live JSON preview reflects them as you type.
Conditional Field
Conditional visibility (`visibleWhen: { field, equals }`, issue #22) — the "client secret" field only renders (and only participates in validation) once "Auth method" is switched to OAuth; while hidden it can never block submit, even though it's `required`.
Validation
Validation — submitting with missing required fields and a bad email-shaped URL surfaces inline errors and moves focus to the first invalid control.
Submitting
Submitting — the request is in flight: controls are blocked, the submit button shows a spinner and stays focusable (`aria-disabled`, never native `disabled`).
Submitted
Submitted — a terminal, inert record: controls are read-only, values stay visible, the submit button is replaced by a status badge.
Submission Error
A terminal, form-level submission error (e.g. the save request failed) rendered above the submit control.
Loading
No fields yet (spec still loading) → a layout-shaped, announced skeleton — never a blank panel. Composed directly from the compound parts (`SchemaFormProvider` → `SchemaFormRoot` → `SchemaFormFields`) rather than the `SchemaForm` convenience wrapper, to also document that lower-level API.
Empty
Empty (not loading) — a spec with zero fields renders a calm fallback, never a broken empty form.
Fallback
Fallback — a malformed / non-object spec renders a typed fallback, never a throw.
Overflowing Content
Overflowing content — long labels, a long description, and many enum options still wrap/scroll gracefully instead of breaking the layout.
File Field States
A file field's designed states: a valid file, a rejected wrong-type file, and a rejected too-large file all render distinctly (never a silent drop).
Disabled
Disabled — the whole form is a deliberate, durable read-only surface (every control natively disabled, not just visually dimmed).
Dark Theme
Dark theme — verifies token-driven surfaces + focus rings in dark. Scoped via a `data-theme` decorator, not a `globals` override, so it doesn't re-theme the whole Docs page.
Test Connection Action
`SchemaFormTestAction` (issue #22 maintainer ruling, 2026-09-01) — a form-level "Test connection" affordance, composed alongside `SchemaFormSubmit` but entirely independent of it: it never reads or blocks field validity, and its own pending/success/failure state never gates `submit()`. Placed directly under `SchemaFormRoot`, not inside the default `SchemaForm` composition — it's an opt-in part a consumer places.
From Json Schema
`fromJsonSchema()` (issue #22 maintainer ruling, 2026-09-01) — maps a documented JSON Schema subset (an OpenAPI request body or connector manifest already described that way) onto `FormSpec`, so `SchemaForm` renders it without hand-authoring the field list.
Sections
`sections` groups fields into disclosures; `readOnly` shows a value as text.
API
SchemaForm
| Prop | Type | Description |
|---|---|---|
specrequired | FormSpec | unknown | The serializable form specification. |
values | FormValues | Controlled values (`{ [fieldName]: value }`). Omit for uncontrolled. |
onChange | (values: FormValues) => void | Called with the next full values object on any change. |
onSubmit | (state: FormSubmitState) => void | Called with `{ formName, values }` on a valid submit. |
submitLabel | string | Submit button label (overrides `spec.submitLabel`). |
disabled | boolean | Disable every control (the whole form is read-only). |
submitted | boolean | Terminal submitted state: inert, values visible, submit replaced. |
submitting | boolean | In-flight submit: controls transiently blocked, spinner on submit. |
loading | boolean | No fields to render yet (spec still loading) → skeleton. |
error | ReactNode | A terminal, form-level submission error rendered above the submit control. |
SchemaFormDescription
SchemaFormError
SchemaFormFallback
| Prop | Type | Description |
|---|---|---|
message | string | Short human reason the form could not render. |
SchemaFormField
| Prop | Type | Description |
|---|---|---|
namerequired | string | The field's `name` (its key in the spec + values). Resolved anywhere in the tree, including inside `group` branches. |
SchemaFormFields
SchemaFormProvider
| Prop | Type | Description |
|---|---|---|
specrequired | NormalizedFormSpec | A validated/normalized spec (a plain `FormSpec` also satisfies this). |
values | FormValues | Controlled values. When provided the component is controlled and `onChange` is the only way to update state. |
onChange | (values: FormValues) => void | Called with the next full values object on any field change. |
onSubmit | (state: FormSubmitState) => void | Called with `{ formName, values }` when a valid form is submitted. |
disabled | boolean | Disable every control (form is read-only). A deliberate, durable state. |
submitted | boolean | Terminal submitted state: controls are inert, values visible, no submit. |
submitting | boolean | In-flight submit: controls transiently blocked, submit shows a spinner. |
loading | boolean | No fields to render yet (e.g. the spec is still being fetched) → skeleton. |
error | ReactNode | A terminal, form-level submission error rendered above the submit control. |
childrenrequired | ReactNode |
SchemaFormRoot
SchemaFormSubmit
| Prop | Type | Description |
|---|---|---|
label | string | Submit button label (overrides `spec.submitLabel`). @default "Submit" |
SchemaFormTestAction
| Prop | Type | Description |
|---|---|---|
onTestrequired | (values: FormValues) => void | Promise<void> | Runs the test (e.g. calls an API with the fields typed so far). Receives the form's CURRENT effective values — the same object `validateForm` would resolve against — but this call is entirely independent of validation: a field that is currently invalid, or an unrelated required field left empty, never blocks a test run. Resolve to signal success; reject (an `Error`, or throw) to signal failure — a rejected `Error`'s `message` becomes the shown failure reason. |
label | string | Button label. @default "Test connection" |
pendingLabel | string | Label shown while pending. @default "Testing…" |
successLabel | string | Label shown on success. @default "Connected" |
failureLabel | string | Fallback failure label when the rejection carries no message. @default "Test failed" |