Generated Form

A short form the agent builds to collect structured input, with validation and inline errors.

Pattern

≈ 1,330 tokens per use (tool 781 · guidance 356 · typical payload 193)How token costs are estimated

Example: Generated Form

Add the person I met at the trade show to the CRM.

New contact

When to use

  • The agent needs several pieces of structured data to create or update a record: a new contact, a shipping address, an event RSVP.
  • The values are the user's to provide, and the agent can't look them up.

When not to use

  • You need one or two decisions. Use the Question Stepper or ask in a sentence.
  • The agent can find the values in data, files or earlier messages. Look them up, and prefill or skip those fields.
  • The user is choosing among records the agent fetched. Use the Entity Picker.

How the agent should use it

  • Never ask for passwords, payment card numbers, bank details or government IDs. Send the user to the system's own secure page instead.
  • Keep it to 10 fields or fewer, in the order the user thinks about them, with required fields first.
  • Prefill default from real records (the saved address, the account owner) and mark only truly necessary fields required.
  • select options are data-bound. They come from the system the agent will write to, never invented.
  • Use pattern only for real format rules, always with a patternMessage.
  • Reach for the specific type: money for an amount, phone for a number, tags for a free list, mention for people you fetched.
  • Do nothing with the data until submit arrives. On cancel, stop.

Beyond text: money, phone, tags and mentions

Four field types cover what text fields get wrong:

TypeWhat the user seesValue in the result
moneyA number box with the currency code (currency, ISO 4217; default USD). Accepts 1,234.50; rejects more decimals than the currency has (2 for USD, 0 for JPY)A number, in major units
phoneA telephone box (7–15 digits, with spaces, dots, dashes and brackets allowed)The trimmed text as typed. It's a plausibility check, not a lookup
tagsChips: type, then Enter or a comma to add; Backspace on an empty box removes the last. Repeats are dropped; min/max set the countAn array of strings, or null when empty
mentionA text area where typing @ opens the field's options as a listbox (a full ARIA combobox: arrows, Enter or Tab to choose, Escape to dismiss)The text, with chosen names as @Name. The result also carries mentions: { <fieldId>: [ids] }, read from the final text, so edits are respected

money is an amount the user decides (a budget, a price cap), never card or account numbers. mention options are data-bound: the people or records you fetched, with value their id and label their name.

{
  action: "submit",
  values: { budget: 24000, topics: ["launch", "retention"], notes: "Review with @Priya Nair" },
  mentions: { notes: ["usr_priya"] },
}

Validation

WhenWhat happens
Leaving a text fieldThe field is checked and its error shown inline
Choosing an option or ticking a checkboxChecked straight away
SubmitEvery field is checked. Focus moves to the first invalid field, and the footer says how many need attention

Errors are linked to their control with aria-describedby, and invalid controls carry aria-invalid. Checks cover required, email format, number and money min/max, money precision, phone plausibility, tag count, text length, date bounds, select options and pattern. The rules are exported as validateFormField, so a server can re-run them.

Installation

npx shadcn@latest add https://your-site.com/r/generated-form.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { generatedFormTool } from "@/components/genui/generated-form.schema"

const tools = {
  // No execute(): the call is completed by the user's submission in the UI.
  [generatedFormTool.name]: tool({
    description: generatedFormTool.description,
    inputSchema: generatedFormTool.inputSchema,
  }),
}
components/message.tsx
import { GeneratedForm } from "@/components/genui/generated-form"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-fill_form") return null

  return (
    <GeneratedForm
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) =>
        addToolResult({
          tool: "fill_form",
          toolCallId: part.toolCallId,
          output: result,
        })
      }
    />
  )
}

The agent receives one of:

{ action: "submit", values: { first_name: "Dana", guests: 1, save: true, notes: null } }
{ action: "cancel" }

Values are keyed by field id. number and money fields are numbers, checkboxes are booleans, tags are arrays, and empty optional fields are null. When a mention field names someone, the result also has mentions.

With LangGraph

Push it from a graph node with ui.push({ name: "fill_form", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. The form renders the submitted values read-only (or the cancelled state) and never calls onResult again.

<GeneratedForm
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
titlestringWhat the form is for: "New contact"
descriptionstring?One quiet line under the title, such as the venue and time. Skip it when it restates the title
fieldsFormField[]1–10
submitLabelstring?Verb for the submit button: "Create contact". Default "Submit"

FormField

FieldTypeNotes
idstringKey in the result's values
labelstring
typetext | email | number | textarea | select | checkbox | date | money | phone | tags | mentionDefault text
requiredbooleanDefault false
placeholderstring?An example value
descriptionstring?Help for the field. Read to screen readers with the control, not shown; put constraints the user must see in the label or placeholder
defaultstring | number | booleanData-bound when it comes from a record. Dates as YYYY-MM-DD
currencystring?ISO 4217 code for money. Default USD
options{ value, label }[]Data-bound. Required for select and mention
min / maxnumber | stringNumber or money range, text length, tag count, or date bounds (YYYY-MM-DD)
patternstring?Regular expression the whole value must match
patternMessagestring?Shown when the pattern fails

Host props: status (streaming, ready, submitting, submitted, cancelled), onResult, result and className.

Source

On this page