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.
/tmp/example.txt. Do you approve this action?"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 confirmationnpx 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:
'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:
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.
SELECT * FROM users WHERE role = 'admin'"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 = 'admin' </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