Skip to main content

SchemaForm

Forms

Convenience composition: Provider → Root(form) → title/description → Fields → Error → Submit.

import { SchemaForm } 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 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

17 live 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

PropTypeDescription
specrequiredFormSpec | unknownThe serializable form specification.
valuesFormValuesControlled values (`{ [fieldName]: value }`). Omit for uncontrolled.
onChange(values: FormValues) => voidCalled with the next full values object on any change.
onSubmit(state: FormSubmitState) => voidCalled with `{ formName, values }` on a valid submit.
submitLabelstringSubmit button label (overrides `spec.submitLabel`).
disabledbooleanDisable every control (the whole form is read-only).
submittedbooleanTerminal submitted state: inert, values visible, submit replaced.
submittingbooleanIn-flight submit: controls transiently blocked, spinner on submit.
loadingbooleanNo fields to render yet (spec still loading) → skeleton.
errorReactNodeA terminal, form-level submission error rendered above the submit control.

Also accepts: Omit< HTMLAttributes<HTMLDivElement>, "onSubmit" | "onChange" | "title" >

SchemaFormDescription

Also accepts: HTMLAttributes<HTMLParagraphElement>

SchemaFormError

Also accepts: HTMLAttributes<HTMLDivElement>

SchemaFormFallback

PropTypeDescription
messagestringShort human reason the form could not render.

Also accepts: HTMLAttributes<HTMLDivElement>

SchemaFormField

PropTypeDescription
namerequiredstringThe field's `name` (its key in the spec + values). Resolved anywhere in the tree, including inside `group` branches.

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

SchemaFormFields

Also accepts: HTMLAttributes<HTMLDivElement>

SchemaFormProvider

PropTypeDescription
specrequiredNormalizedFormSpecA validated/normalized spec (a plain `FormSpec` also satisfies this).
valuesFormValuesControlled values. When provided the component is controlled and `onChange` is the only way to update state.
onChange(values: FormValues) => voidCalled with the next full values object on any field change.
onSubmit(state: FormSubmitState) => voidCalled with `{ formName, values }` when a valid form is submitted.
disabledbooleanDisable every control (form is read-only). A deliberate, durable state.
submittedbooleanTerminal submitted state: controls are inert, values visible, no submit.
submittingbooleanIn-flight submit: controls transiently blocked, submit shows a spinner.
loadingbooleanNo fields to render yet (e.g. the spec is still being fetched) → skeleton.
errorReactNodeA terminal, form-level submission error rendered above the submit control.
childrenrequiredReactNode

SchemaFormRoot

Also accepts: Omit<HTMLAttributes<HTMLFormElement>, "onSubmit">

SchemaFormSubmit

PropTypeDescription
labelstringSubmit button label (overrides `spec.submitLabel`). @default "Submit"

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

SchemaFormTestAction

PropTypeDescription
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.
labelstringButton label. @default "Test connection"
pendingLabelstringLabel shown while pending. @default "Testing…"
successLabelstringLabel shown on success. @default "Connected"
failureLabelstringFallback failure label when the rejection carries no message. @default "Test failed"

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

SchemaFormTitle

Also accepts: HTMLAttributes<HTMLParagraphElement>