Chatbot

Confirmation

An alert-based component for managing tool execution approval workflows with request, accept, and reject states.

The Confirmation component provides a flexible system for displaying tool approval requests and their outcomes. Perfect for showing users when AI tools require approval before execution, and displaying the approval status afterward.

"use client";import {  Confirmation,  ConfirmationAccepted,  ConfirmationAction,  ConfirmationActions,  ConfirmationRejected,  ConfirmationRequest,  ConfirmationTitle,} from "@/components/ai-elements/elements/confirmation";import { CheckIcon, XIcon } from "lucide-react";import { nanoid } from "nanoid";const Example = () => (  <div className="w-full max-w-2xl">    <Confirmation approval={{ id: nanoid() }} state="approval-requested">      <ConfirmationTitle>        <ConfirmationRequest>          This tool wants to delete the file{" "}          <code className="inline rounded bg-muted px-1.5 py-0.5 text-sm">            /tmp/example.txt          </code>          . Do you approve this action?        </ConfirmationRequest>        <ConfirmationAccepted>          <CheckIcon className="size-4 text-green-600 dark:text-green-400" />          <span>You approved this tool execution</span>        </ConfirmationAccepted>        <ConfirmationRejected>          <XIcon className="size-4 text-destructive" />          <span>You rejected this tool execution</span>        </ConfirmationRejected>      </ConfirmationTitle>      <ConfirmationActions>        <ConfirmationAction          onClick={() => {            // In production, call respondToConfirmationRequest with approved: false          }}          variant="outline"        >          Reject        </ConfirmationAction>        <ConfirmationAction          onClick={() => {            // In production, call respondToConfirmationRequest with approved: true          }}          variant="default"        >          Approve        </ConfirmationAction>      </ConfirmationActions>    </Confirmation>  </div>);export default Example;

Installation

npx ai-elements@latest add confirmation
npx shadcn@latest add @ai-elements/confirmation
"use client";import { Alert, AlertDescription } from "@repo/shadcn-ui/components/ui/alert";import { Button } from "@repo/shadcn-ui/components/ui/button";import { cn } from "@repo/shadcn-ui/lib/utils";import type { ToolUIPart } from "ai";import {  type ComponentProps,  createContext,  type ReactNode,  useContext,} from "react";type ConfirmationContextValue = {  approval: ToolUIPart["approval"];  state: ToolUIPart["state"];};const ConfirmationContext = createContext<ConfirmationContextValue | null>(  null);const useConfirmation = () => {  const context = useContext(ConfirmationContext);  if (!context) {    throw new Error("Confirmation components must be used within Confirmation");  }  return context;};export type ConfirmationProps = ComponentProps<typeof Alert> & {  approval?: ToolUIPart["approval"];  state: ToolUIPart["state"];};export const Confirmation = ({  className,  approval,  state,  ...props}: ConfirmationProps) => {  if (!approval || state === "input-streaming" || state === "input-available") {    return null;  }  return (    <ConfirmationContext.Provider value={{ approval, state }}>      <Alert className={cn("flex flex-col gap-2", className)} {...props} />    </ConfirmationContext.Provider>  );};export type ConfirmationTitleProps = ComponentProps<typeof AlertDescription>;export const ConfirmationTitle = ({  className,  ...props}: ConfirmationTitleProps) => (  <AlertDescription className={cn("inline", className)} {...props} />);export type ConfirmationRequestProps = {  children?: ReactNode;};export const ConfirmationRequest = ({ children }: ConfirmationRequestProps) => {  const { state } = useConfirmation();  // Only show when approval is requested  if (state !== "approval-requested") {    return null;  }  return children;};export type ConfirmationAcceptedProps = {  children?: ReactNode;};export const ConfirmationAccepted = ({  children,}: ConfirmationAcceptedProps) => {  const { approval, state } = useConfirmation();  // Only show when approved and in response states  if (    !approval?.approved ||    (state !== "approval-responded" &&      state !== "output-denied" &&      state !== "output-available")  ) {    return null;  }  return children;};export type ConfirmationRejectedProps = {  children?: ReactNode;};export const ConfirmationRejected = ({  children,}: ConfirmationRejectedProps) => {  const { approval, state } = useConfirmation();  // Only show when rejected and in response states  if (    approval?.approved !== false ||    (state !== "approval-responded" &&      state !== "output-denied" &&      state !== "output-available")  ) {    return null;  }  return children;};export type ConfirmationActionsProps = ComponentProps<"div">;export const ConfirmationActions = ({  className,  ...props}: ConfirmationActionsProps) => {  const { state } = useConfirmation();  // Only show when approval is requested  if (state !== "approval-requested") {    return null;  }  return (    <div      className={cn("flex items-center justify-end gap-2 self-end", className)}      {...props}    />  );};export type ConfirmationActionProps = ComponentProps<typeof Button>;export const ConfirmationAction = (props: ConfirmationActionProps) => (  <Button className="h-8 px-3 text-sm" type="button" {...props} />);

Usage

import {
  Confirmation,
  ConfirmationContent,
  ConfirmationRequest,
  ConfirmationAccepted,
  ConfirmationRejected,
  ConfirmationActions,
  ConfirmationAction,
} from '@/components/ai-elements/confirmation';
<Confirmation approval={{ id: 'tool-1' }} state="approval-requested">
  <ConfirmationContent>
    <ConfirmationRequest>
      This tool wants to access your file system. Do you approve?
    </ConfirmationRequest>
    <ConfirmationAccepted>
      <CheckIcon className="size-4" />
      <span>Approved</span>
    </ConfirmationAccepted>
    <ConfirmationRejected>
      <XIcon className="size-4" />
      <span>Rejected</span>
    </ConfirmationRejected>
  </ConfirmationContent>
  <ConfirmationActions>
    <ConfirmationAction variant="outline" onClick={handleReject}>
      Reject
    </ConfirmationAction>
    <ConfirmationAction variant="default" onClick={handleApprove}>
      Approve
    </ConfirmationAction>
  </ConfirmationActions>
</Confirmation>

Usage with AI SDK

Build a chat UI with tool approval workflow where dangerous tools require user confirmation before execution.

Add the following component to your frontend:

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport, type ToolUIPart } from 'ai';
import { useState } from 'react';
import { CheckIcon, XIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
  Confirmation,
  ConfirmationContent,
  ConfirmationRequest,
  ConfirmationAccepted,
  ConfirmationRejected,
  ConfirmationActions,
  ConfirmationAction,
} from '@/components/ai-elements/confirmation';
import { Response } from '@/components/ai-elements/response';

type DeleteFileInput = {
  filePath: string;
  confirm: boolean;
};

type DeleteFileToolUIPart = ToolUIPart<{
  delete_file: {
    input: DeleteFileInput;
    output: { success: boolean; message: string };
  };
}>;

const Example = () => {
  const { messages, sendMessage, status, respondToConfirmationRequest } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  const handleDeleteFile = () => {
    sendMessage({ text: 'Delete the file at /tmp/example.txt' });
  };

  const latestMessage = messages[messages.length - 1];
  const deleteTool = latestMessage?.parts?.find(
    (part) => part.type === 'tool-delete_file'
  ) as DeleteFileToolUIPart | undefined;

  return (
    <div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
      <div className="flex flex-col h-full space-y-4">
        <Button onClick={handleDeleteFile} disabled={status !== 'ready'}>
          Delete Example File
        </Button>

        {deleteTool?.approval && (
          <Confirmation approval={deleteTool.approval} state={deleteTool.state}>
            <ConfirmationContent>
              <ConfirmationRequest>
                This tool wants to delete: <code>{deleteTool.input?.filePath}</code>
                <br />
                Do you approve this action?
              </ConfirmationRequest>
              <ConfirmationAccepted>
                <CheckIcon className="size-4" />
                <span>You approved this tool execution</span>
              </ConfirmationAccepted>
              <ConfirmationRejected>
                <XIcon className="size-4" />
                <span>You rejected this tool execution</span>
              </ConfirmationRejected>
            </ConfirmationContent>
            <ConfirmationActions>
              <ConfirmationAction
                variant="outline"
                onClick={() =>
                  respondToConfirmationRequest({
                    approvalId: deleteTool.approval!.id,
                    approved: false,
                  })
                }
              >
                Reject
              </ConfirmationAction>
              <ConfirmationAction
                variant="default"
                onClick={() =>
                  respondToConfirmationRequest({
                    approvalId: deleteTool.approval!.id,
                    approved: true,
                  })
                }
              >
                Approve
              </ConfirmationAction>
            </ConfirmationActions>
          </Confirmation>
        )}

        {deleteTool?.output && (
          <Response>
            {deleteTool.output.success
              ? deleteTool.output.message
              : `Error: ${deleteTool.output.message}`}
          </Response>
        )}
      </div>
    </div>
  );
};

export default Example;

Add the following route to your backend:

app/api/chat/route.tsx
import { streamText, UIMessage, convertToModelMessages } from 'ai';
import { z } from 'zod';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),
    tools: {
      delete_file: {
        description: 'Delete a file from the file system',
        parameters: z.object({
          filePath: z.string().describe('The path to the file to delete'),
          confirm: z
            .boolean()
            .default(false)
            .describe('Confirmation that the user wants to delete the file'),
        }),
        requireApproval: true, // Enable approval workflow
        execute: async ({ filePath, confirm }) => {
          if (!confirm) {
            return {
              success: false,
              message: 'Deletion not confirmed',
            };
          }

          // Simulate file deletion
          await new Promise((resolve) => setTimeout(resolve, 500));

          return {
            success: true,
            message: `Successfully deleted ${filePath}`,
          };
        },
      },
    },
  });

  return result.toUIMessageStreamResponse();
}

Features

  • Context-based state management for approval workflow
  • Conditional rendering based on approval state
  • Support for approval-requested, approval-responded, output-denied, and output-available states
  • Built on shadcn/ui Alert and Button components
  • TypeScript support with comprehensive type definitions
  • Customizable styling with Tailwind CSS
  • Keyboard navigation and accessibility support
  • Theme-aware with automatic dark mode support

Examples

Approval Request State

Shows the approval request with action buttons when state is approval-requested.

"use client";import {  Confirmation,  ConfirmationAccepted,  ConfirmationAction,  ConfirmationActions,  ConfirmationRejected,  ConfirmationRequest,  ConfirmationTitle,} from "@/components/ai-elements/elements/confirmation";import { CheckIcon, XIcon } from "lucide-react";import { nanoid } from "nanoid";const Example = () => (  <div className="w-full max-w-2xl">    <Confirmation approval={{ id: nanoid() }} state="approval-requested">      <ConfirmationTitle>        <ConfirmationRequest>          This tool wants to execute a query on the production database:          <code className="mt-2 block rounded bg-muted p-2 text-sm">            SELECT * FROM users WHERE role = &apos;admin&apos;          </code>        </ConfirmationRequest>        <ConfirmationAccepted>          <CheckIcon className="size-4 text-green-600 dark:text-green-400" />          <span>You approved this tool execution</span>        </ConfirmationAccepted>        <ConfirmationRejected>          <XIcon className="size-4 text-destructive" />          <span>You rejected this tool execution</span>        </ConfirmationRejected>      </ConfirmationTitle>      <ConfirmationActions>        <ConfirmationAction          onClick={() => {            // In production, call respondToConfirmationRequest with approved: false          }}          variant="outline"        >          Reject        </ConfirmationAction>        <ConfirmationAction          onClick={() => {            // In production, call respondToConfirmationRequest with approved: true          }}          variant="default"        >          Approve        </ConfirmationAction>      </ConfirmationActions>    </Confirmation>  </div>);export default Example;

Approved State

Shows the accepted status when user approves and state is approval-responded or output-available.

"use client";import {  Confirmation,  ConfirmationAccepted,  ConfirmationRejected,  ConfirmationRequest,  ConfirmationTitle,} from "@/components/ai-elements/elements/confirmation";import { CheckIcon, XIcon } from "lucide-react";import { nanoid } from "nanoid";const Example = () => (  <div className="w-full max-w-2xl">    <Confirmation      approval={{ id: nanoid(), approved: true }}      state="approval-responded"    >      <ConfirmationTitle>        <ConfirmationRequest>          This tool wants to delete the file{" "}          <code className="rounded bg-muted px-1.5 py-0.5 text-sm">            /tmp/example.txt          </code>          . Do you approve this action?        </ConfirmationRequest>        <ConfirmationAccepted>          <CheckIcon className="size-4 text-green-600 dark:text-green-400" />          <span>You approved this tool execution</span>        </ConfirmationAccepted>        <ConfirmationRejected>          <XIcon className="size-4 text-destructive" />          <span>You rejected this tool execution</span>        </ConfirmationRejected>      </ConfirmationTitle>    </Confirmation>  </div>);export default Example;

Rejected State

Shows the rejected status when user rejects and state is output-denied.

"use client";import {  Confirmation,  ConfirmationAccepted,  ConfirmationRejected,  ConfirmationRequest,  ConfirmationTitle,} from "@/components/ai-elements/elements/confirmation";import { CheckIcon, XIcon } from "lucide-react";import { nanoid } from "nanoid";const Example = () => (  <div className="w-full max-w-2xl">    <Confirmation      approval={{ id: nanoid(), approved: false }}      state="output-denied"    >      <ConfirmationTitle>        <ConfirmationRequest>          This tool wants to delete the file{" "}          <code className="rounded bg-muted px-1.5 py-0.5 text-sm">            /tmp/example.txt          </code>          . Do you approve this action?        </ConfirmationRequest>        <ConfirmationAccepted>          <CheckIcon className="size-4 text-green-600 dark:text-green-400" />          <span>You approved this tool execution</span>        </ConfirmationAccepted>        <ConfirmationRejected>          <XIcon className="size-4 text-destructive" />          <span>You rejected this tool execution</span>        </ConfirmationRejected>      </ConfirmationTitle>    </Confirmation>  </div>);export default Example;

Props

<Confirmation />

Prop

Type

<ConfirmationContent />

Prop

Type

<ConfirmationRequest />

Prop

Type

<ConfirmationAccepted />

Prop

Type

<ConfirmationRejected />

Prop

Type

<ConfirmationActions />

Prop

Type

<ConfirmationAction />

Prop

Type