Elicitation
Build composable elicitation request UIs from MCP form schemas with unstyled base primitives.
Elicitation
Elicitation in @tambo-ai/react-ui-base owns schema parsing, validation, and accept/decline/cancel behavior while exposing composable UI primitives.
Demo
Please configure your project settings:
import { Elicitation } from "@tambo-ai/react-ui-base/elicitation";import type { TamboElicitationRequest, TamboElicitationResponse } from "@tambo-ai/react/mcp";export function ElicitationForm({ request, onResponse,}: { request: TamboElicitationRequest; onResponse: (response: TamboElicitationResponse) => void;}) { return ( <Elicitation.Root request={request} onResponse={onResponse}> <Elicitation.Message className="mb-3 text-sm" /> <Elicitation.Fields render={(_props, { fields }) => ( <div className="flex flex-col gap-3"> {fields.map((field) => ( <Elicitation.Field key={field.name} field={field}> <Elicitation.FieldLabel className="text-sm font-medium" /> <Elicitation.FieldInput className="mt-1"> <Elicitation.FieldBooleanInput /> <Elicitation.FieldEnumInput /> <Elicitation.FieldStringInput className="w-full rounded-lg border px-3 py-2 text-sm" /> <Elicitation.FieldNumberInput className="w-full rounded-lg border px-3 py-2 text-sm" /> </Elicitation.FieldInput> <Elicitation.FieldError className="mt-1 text-xs text-red-600" /> </Elicitation.Field> ))} </div> )} /> <Elicitation.Actions className="mt-4 flex items-center gap-2"> <Elicitation.ActionDecline className="rounded-lg border px-3 py-1.5 text-sm"> Decline </Elicitation.ActionDecline> <Elicitation.ActionCancel className="rounded-lg border px-3 py-1.5 text-sm"> Cancel </Elicitation.ActionCancel> <Elicitation.ActionSubmit className="rounded-lg bg-neutral-900 px-3 py-1.5 text-sm text-white" /> </Elicitation.Actions> </Elicitation.Root> );}Anatomy
<Elicitation.Root request={request} onResponse={onResponse}>
<Elicitation.Message />
<Elicitation.Fields />
<Elicitation.Actions />
</Elicitation.Root>Examples
Single-Entry Auto Submit
Single-field boolean or enum requests auto-submit when the option is selected.
Custom Actions
<Elicitation.Actions>
<Elicitation.ActionDecline className="rounded px-3 py-2">
Skip
</Elicitation.ActionDecline>
<Elicitation.ActionCancel className="rounded px-3 py-2">
Cancel
</Elicitation.ActionCancel>
<Elicitation.ActionSubmit className="rounded px-3 py-2" />
</Elicitation.Actions>Compose Fields With Base Primitives
<Elicitation.Fields
render={({ fields }) => (
<>
{fields.map((field) => (
<Elicitation.Field key={field.name} field={field}>
<Elicitation.FieldLabel className="text-sm font-medium" />
<Elicitation.FieldInput className="mt-2">
<Elicitation.FieldBooleanInput />
<Elicitation.FieldEnumInput />
<Elicitation.FieldStringInput className="rounded border px-3 py-2" />
<Elicitation.FieldNumberInput className="rounded border px-3 py-2" />
</Elicitation.FieldInput>
<Elicitation.FieldError className="mt-1 text-xs text-red-600" />
</Elicitation.Field>
))}
</>
)}
/>Elicitation.FieldBooleanInput, Elicitation.FieldEnumInput, Elicitation.FieldStringInput, and Elicitation.FieldNumberInput are schema-aware. Each one only renders when it matches the current field kind and returns null otherwise, so you can compose all of them inside Elicitation.FieldInput and only the correct control will appear. If you provide children to Elicitation.FieldInput, you replace its default stack and fully control input rendering/order.
Build A Fully Custom Input
Use useElicitationField inside Elicitation.Field to own rendering while keeping base state/validation/actions.
import {
Elicitation,
useElicitationField,
} from "@tambo-ai/react-ui-base/elicitation";
function CustomInput() {
const { field, inputId, errorId, invalid, label } = useElicitationField();
if (field.schema.type !== "string") {
return null;
}
const value = typeof field.value === "string" ? field.value : "";
return (
<textarea
id={inputId}
aria-label={label}
aria-invalid={invalid || undefined}
aria-describedby={invalid ? errorId : undefined}
value={value}
onChange={(event) => field.setValue(event.currentTarget.value)}
/>
);
}
<Elicitation.Fields
render={({ fields }) => (
<>
{fields.map((field) => (
<Elicitation.Field key={field.name} field={field}>
<Elicitation.FieldLabel />
<CustomInput />
<Elicitation.FieldError />
</Elicitation.Field>
))}
</>
)}
/>;API reference
Root
| Prop | Type | Default | Description |
|---|---|---|---|
request | TamboElicitationRequest | Required | Incoming elicitation request schema and message. |
onResponse | (response: TamboElicitationResponse) => void | Required | Callback for accept/decline/cancel responses. |
Message
Renders the request message from Root. Supports render prop for custom rendering. Exposes message in render props.
Fields
Resolves fields from request.requestedSchema and renders default fields when no children or render is provided. Exposes fields and single in render props.
Field
Field-scoped container that provides field context for nested parts. Supports render prop with field, kind, label, inputId, errorId, required, and invalid in render props.
| Prop | Type | Default | Description |
|---|---|---|---|
field | ElicitationField | Required | The field model from Fields. |
Default children:
Elicitation.FieldLabelElicitation.FieldInputElicitation.FieldError
FieldLabel
Renders the field label and required marker by default. Supports render prop with label, required, and inputId in render props.
FieldInput
Input container with default typed input stack. Supports render prop with kind in render props. Not rendered when the field kind is "unsupported".
Default children:
Elicitation.FieldBooleanInputElicitation.FieldEnumInputElicitation.FieldStringInputElicitation.FieldNumberInput
If you pass children, they replace this default stack.
FieldBooleanInput
Boolean yes/no buttons, enabled only for boolean schema fields. Extends React.HTMLAttributes<HTMLDivElement> (minus children).
| Prop | Type | Default | Description |
|---|---|---|---|
trueLabel | React.ReactNode | "Yes" | Label for the true button. |
falseLabel | React.ReactNode | "No" | Label for the false button. |
trueButtonProps | React.ButtonHTMLAttributes (partial) | — | Extra props spread onto the true button. |
falseButtonProps | React.ButtonHTMLAttributes (partial) | — | Extra props spread onto the false button. |
FieldEnumInput
Option button list, enabled only for enum string schema fields. Extends React.HTMLAttributes<HTMLDivElement> (minus children).
| Prop | Type | Default | Description |
|---|---|---|---|
getOptionLabel | (option: string, index: number) => React.ReactNode | — | Custom label renderer per option. Falls back to enumNames. |
getOptionProps | (option: string, index: number, selected: boolean) => ButtonAttributes | — | Extra props spread onto each option button. |
FieldStringInput
Text-like input (including format-based types like email/url/date), enabled for non-enum string fields. Extends React.InputHTMLAttributes<HTMLInputElement> (minus id, type, value, autoFocus, required).
FieldNumberInput
Number input, enabled for number and integer schema fields. Extends React.InputHTMLAttributes<HTMLInputElement> (minus id, type, value, autoFocus, required).
FieldError
Renders validation message for the current field. Supports render prop with error, errorId, and invalid in render props.
| Prop | Type | Default | Description |
|---|---|---|---|
keepMounted | boolean | false | Keep mounted while valid and toggle visibility instead of unmounting. |
Actions
Container for action controls. Supports render prop with single, valid, handleAccept, handleDecline, and handleCancel in render props. By default it renders:
Elicitation.ActionCancelElicitation.ActionDeclineElicitation.ActionSubmit(hidden for single-entry mode)
If you pass children, they fully replace the default action buttons.
ActionCancel
Button that fires onResponse with { action: "cancel" }. Extends React.ButtonHTMLAttributes<HTMLButtonElement>. Default label is "Cancel".
Accepts children as a render function (props: { handleCancel }) => ReactNode for custom rendering.
ActionDecline
Button that fires onResponse with { action: "decline" }. Extends React.ButtonHTMLAttributes<HTMLButtonElement>. Default label is "Decline".
Accepts children as a render function (props: { handleDecline }) => ReactNode for custom rendering.
ActionSubmit
Button that fires onResponse with { action: "accept", content }. Extends React.ButtonHTMLAttributes<HTMLButtonElement>. Default label is "Submit". Hidden in single-entry mode. Disabled when the form is invalid.
Accepts children as a render function (props: { hidden, disabled, handleAccept }) => ReactNode for custom rendering.
| Prop | Type | Default | Description |
|---|---|---|---|
keepMounted | boolean | false | Keep mounted while hidden in single-entry mode and expose hidden state via data-hidden. |
useElicitationField
Hook for custom field UIs inside Elicitation.Field.
Returns:
field: full field model (name,schema,value,setValue, validation metadata)kind: one of"boolean" | "enum" | "string" | "number" | "unsupported"label,inputId,errorIdrequired,invalid
Accessibility
- Keep
labelto input associations (htmlFor/id) when replacingFieldLabel. - Preserve validation associations (
aria-invalidandaria-describedby) for custom inputs. - Keep action controls keyboard-accessible (
buttonsemantics).
Styling Hooks
data-slot="elicitation-root"data-slot="elicitation-fields"data-slot="elicitation-field"data-slot="elicitation-field-label"data-slot="elicitation-field-control"(onFieldInputcontainer)data-slot="elicitation-field-input"(onFieldStringInputandFieldNumberInputelements)data-slot="elicitation-field-error"data-slot="elicitation-field-boolean-options"data-slot="elicitation-field-boolean-true"data-slot="elicitation-field-boolean-false"data-slot="elicitation-field-enum-options"data-slot="elicitation-field-enum-option"data-slot="elicitation-actions"data-slot="elicitation-action-cancel"data-slot="elicitation-action-decline"data-slot="elicitation-action-submit"data-mode="single" | "multiple"on rootdata-kindon field and field-controldata-nameon fielddata-state="selected" | "unselected"on boolean/enum buttonsdata-requiredanddata-invalidon fielddata-hiddenanddata-disabledon submit action