React UI Base
Loading...

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

PropTypeDefaultDescription
requestTamboElicitationRequestRequiredIncoming elicitation request schema and message.
onResponse(response: TamboElicitationResponse) => voidRequiredCallback 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.

PropTypeDefaultDescription
fieldElicitationFieldRequiredThe field model from Fields.

Default children:

  • Elicitation.FieldLabel
  • Elicitation.FieldInput
  • Elicitation.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.FieldBooleanInput
  • Elicitation.FieldEnumInput
  • Elicitation.FieldStringInput
  • Elicitation.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).

PropTypeDefaultDescription
trueLabelReact.ReactNode"Yes"Label for the true button.
falseLabelReact.ReactNode"No"Label for the false button.
trueButtonPropsReact.ButtonHTMLAttributes (partial)Extra props spread onto the true button.
falseButtonPropsReact.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).

PropTypeDefaultDescription
getOptionLabel(option: string, index: number) => React.ReactNodeCustom label renderer per option. Falls back to enumNames.
getOptionProps(option: string, index: number, selected: boolean) => ButtonAttributesExtra 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.

PropTypeDefaultDescription
keepMountedbooleanfalseKeep 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.ActionCancel
  • Elicitation.ActionDecline
  • Elicitation.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.

PropTypeDefaultDescription
keepMountedbooleanfalseKeep 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, errorId
  • required, invalid

Accessibility

  • Keep label to input associations (htmlFor/id) when replacing FieldLabel.
  • Preserve validation associations (aria-invalid and aria-describedby) for custom inputs.
  • Keep action controls keyboard-accessible (button semantics).

Styling Hooks

  • data-slot="elicitation-root"
  • data-slot="elicitation-fields"
  • data-slot="elicitation-field"
  • data-slot="elicitation-field-label"
  • data-slot="elicitation-field-control" (on FieldInput container)
  • data-slot="elicitation-field-input" (on FieldStringInput and FieldNumberInput elements)
  • 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 root
  • data-kind on field and field-control
  • data-name on field
  • data-state="selected" | "unselected" on boolean/enum buttons
  • data-required and data-invalid on field
  • data-hidden and data-disabled on submit action