Elements

30 / 122 · Tool use

Elicitation form

A server pausing mid-tool-call to ask you for the fields it still needs.

github-mcpneeds input

Confirm where the release notes should be published before the tool runs.

Repository *assistant-ui/assistant-ui
Visibility
PublicPrivate
Notify watchersOn

Installation

npx shadcn@latest add "@assistant-ui/elements-elicitation-form"

Usage

import { ElicitationForm } from "@/components/elements/elicitation-form";

<ElicitationForm
  server="github-mcp"
  message="Confirm where the release notes should be published."
  fields={fields}
  state="request"
  onAccept={send}
  onDecline={decline}
/>

Props

ElicitationForm

server*stringWhich server paused to ask.
message*stringWhy the input is needed, in the server's words.
fields*ElicitationField[]The schema the server sent, already resolved to display values.
state*"request" | "accepted" | "declined"Swaps the action row for the outcome once the user answers.
onAccept() => voidCalled when the filled form is sent back.
onDecline() => voidCalled when the request is refused.
classNamestringExtra classes merged onto the root.

ElicitationField

name*stringField name sent back to the server.
label*stringLabel shown above the control.
value*stringCurrent value, already resolved to what should be displayed.
kind*"text" | "choice" | "toggle"Chooses the control: a value chip, a row of options, or a switch.
optionsstring[]Choices rendered when kind is choice.
requiredbooleanMarks the label with an asterisk.

Source

elicitation-form.tsx
"use client";

import { CheckIcon, PlugIcon, XIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { field, inkButton, mono, paper } from "./surfaces";

export type ElicitationState = "request" | "accepted" | "declined";

export interface ElicitationField {
  name: string;
  label: string;
  value: string;
  kind: "text" | "choice" | "toggle";
  options?: readonly string[];
  required?: boolean;
}

export function ElicitationForm({
  server,
  message,
  fields,
  state,
  onAccept,
  onDecline,
  className,
}: {
  server: string;
  message: string;
  fields: readonly ElicitationField[];
  state: ElicitationState;
  onAccept?: () => void;
  onDecline?: () => void;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-sm flex-col gap-3.5 rounded-[20px] p-4",
        className,
      )}
    >
      <div className="flex items-center gap-2.5">
        <span className="bg-foreground/[0.05] text-foreground/45 flex size-7 shrink-0 items-center justify-center rounded-lg">
          <PlugIcon className="size-3.5" />
        </span>
        <span className="min-w-0 flex-1 truncate text-[13.5px] font-medium">
          {server}
        </span>
        <span className={cn(mono, "text-foreground/30 shrink-0")}>
          needs input
        </span>
      </div>

      <p className="text-foreground/55 text-xs leading-relaxed">{message}</p>

      <div className="flex flex-col gap-2.5">
        {fields.map((item) => (
          <div key={item.name} className="flex flex-col gap-1">
            <span className={cn(mono, "text-foreground/35")}>
              {item.label}
              {item.required && <span className="text-foreground/25"> *</span>}
            </span>
            {item.kind === "choice" ? (
              <div className="flex flex-wrap gap-1.5">
                {item.options?.map((option) => (
                  <span
                    key={option}
                    className={cn(
                      "rounded-full px-2.5 py-1 text-xs transition-colors",
                      option === item.value
                        ? "bg-foreground text-background"
                        : cn(field, "text-foreground/55"),
                    )}
                  >
                    {option}
                  </span>
                ))}
              </div>
            ) : item.kind === "toggle" ? (
              <span className="flex items-center gap-2">
                <span
                  aria-hidden
                  className={cn(
                    "flex h-4 w-7 items-center rounded-full p-0.5 transition-colors duration-200",
                    item.value === "true"
                      ? "bg-foreground/80"
                      : "bg-foreground/15",
                  )}
                >
                  <span
                    className={cn(
                      "bg-background size-3 rounded-full transition-transform duration-200 motion-reduce:transition-none",
                      item.value === "true" && "translate-x-3",
                    )}
                  />
                </span>
                <span className="text-foreground/55 text-xs">
                  {item.value === "true" ? "On" : "Off"}
                </span>
              </span>
            ) : (
              <span
                className={cn(
                  field,
                  "text-foreground/80 rounded-lg px-2.5 py-1.5 text-xs",
                )}
              >
                {item.value}
              </span>
            )}
          </div>
        ))}
      </div>

      <div className="flex h-8 items-center justify-end gap-2">
        {state === "request" ? (
          <>
            <button
              type="button"
              onClick={onDecline}
              className="text-foreground/55 hover:bg-foreground/[0.06] hover:text-foreground/90 h-8 rounded-full px-3.5 text-xs font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96]"
            >
              Decline
            </button>
            <button
              type="button"
              onClick={onAccept}
              className={cn(
                inkButton,
                "flex h-8 items-center rounded-full px-3.5 text-xs font-medium",
              )}
            >
              Send
            </button>
          </>
        ) : (
          <span
            key={state}
            className="fade-in animate-in text-foreground/55 flex items-center gap-2 text-xs duration-300"
          >
            {state === "accepted" ? (
              <>
                <CheckIcon className="size-3.5 text-emerald-500" />
                Sent to {server}
              </>
            ) : (
              <>
                <XIcon className="text-foreground/45 size-3.5" />
                Declined
              </>
            )}
          </span>
        )}
      </div>
    </div>
  );
}