Skip to main content

MessageForm

Conversationai

A model-emitted, zod-validated form rendered inside a chat message; returns structured values on submit.

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

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 MessageForm component (from @elabs-ai/components-ai) 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 MessageForm up before writing code: `npx -y @elabs-ai/components-cli docs MessageForm` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-ai is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-ai` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-ai`). 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.

Avoid

  • Letting a malformed spec throw — model output is untrusted; an invalid spec renders MessageFormFallback.
  • Surfacing validation errors while the spec is still streaming — half-arrived fields are dropped, not reported as errors.
  • Letting the model choose the look — it authors the spec; the component owns layout, tokens and focus handling.

MessageForm — not a chat input. A model-emitted form rendered INSIDE a message. It sits next to Composer in the sidebar and reads like a sibling of it; it is not one. Spec-driven and zod-validated: the model is the author, it never chooses the look. A malformed spec degrades to MessageFormFallback rather than throwing, half-arrived fields are dropped while streaming, and a submitted form renders inert with its values still visible — a chat message is a historical record.

Works with

Contains

  • MessageFormProvider
  • MessageFormFields
  • MessageFormSubmit
  • MessageFormFallback

Used inside

  • Message

Examples

11 live examples

Streaming Partial

Streaming / partial — the spec is still arriving. Only the fields that have fully parsed render; the rest is a skeleton. Never a crash, never null holes.

Streaming Skeleton

Streaming with nothing parsed yet → a layout-shaped skeleton.

Provider Streaming

The compound composition — `MessageFormProvider` + the parts directly, instead of the `MessageForm` convenience wrapper (see the file-level doc comment's "compound structure" section). `isStreaming` lives on the provider itself since it is the one that owns the lifted form state; a custom layout that skips `MessageForm` still needs to pass it through here.

Empty

Empty (not streaming) → a calm fallback, never broken UI.

Fallback

Fallback — a malformed / non-object spec renders a typed fallback (no throw).

Validation

Validation — submit with an invalid email + missing required fields.

Controlled

Controlled — the parent owns `values`; here a live JSON preview reflects them.

Submitting

Submitting — the request is in flight (controls disabled, spinner on submit).

Submitted

Submitted — a terminal, inert historical record with values visible.

Dark Theme

Dark theme — verifies token-driven surfaces + focus rings in dark. The theme is scoped to this story via a `data-theme` decorator (NOT a `globals` override, which would stick and re-theme the whole Docs page).

API

MessageForm

PropTypeDescription
specrequiredFormSpec | unknownThe serializable form specification produced by an LLM tool-call.
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.
submittedbooleanTerminal submitted state: inert, values visible, submit replaced.
submittingbooleanIn-flight submit: controls disabled, spinner on submit.
isStreamingbooleanThe spec is still streaming (renders a skeleton when no fields yet).
streamingboolean@deprecated Use `isStreaming`.

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

MessageFormDescription

Also accepts: HTMLAttributes<HTMLParagraphElement>

MessageFormFallback

PropTypeDescription
messagestringShort human reason the form could not render.

Also accepts: HTMLAttributes<HTMLDivElement>

MessageFormField

PropTypeDescription
namerequiredstringThe field's `name` (its key in the spec + values).

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

MessageFormFields

Also accepts: HTMLAttributes<HTMLDivElement>

MessageFormProvider

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 not interactable).
submittedbooleanTerminal submitted state: controls are inert, values visible, no submit.
submittingbooleanIn-flight submit: controls disabled, the submit button shows a spinner.
isStreamingbooleanThe spec is still streaming in (renders a skeleton when no fields yet).
streamingboolean@deprecated Use `isStreaming`.
childrenrequiredReactNode

MessageFormRoot

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

MessageFormSubmit

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

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

MessageFormTitle

Also accepts: HTMLAttributes<HTMLParagraphElement>