# Confirmation

Asks for approval before an agent runs a tool, then shows the call as a step once the person answers.

Source: https://suiss.app/ui/docs/ai/confirmation

**React**

**Example**

```tsx
"use client"

import { CheckCircleIcon, TerminalIcon } from "@phosphor-icons/react"
import * as React from "react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const command = "rm -rf dist"

export default function ConfirmationDemo() {
  const [approval, setApproval] = React.useState<"approved" | "denied">()

  return (
    <div className="flex min-h-72 w-full max-w-sm flex-col gap-4 py-6 text-sm">
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>Clean up the build folder.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent className="gap-4">
          <Trace>
            <TraceCollapsible>
              <TraceCollapsibleTrigger>
                <TraceLine>
                  <TraceIcon>
                    <CheckCircleIcon />
                  </TraceIcon>
                  <TraceTitle>Thought</TraceTitle>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent>
                The build output lives in dist. Deleting it cannot be undone, so
                I should ask before I run the command.
              </TraceCollapsibleContent>
            </TraceCollapsible>
          </Trace>
          {approval ? (
            <Trace>
              <TraceLine>
                <TraceIcon>
                  <TerminalIcon />
                </TraceIcon>
                <TraceTitle>
                  {approval === "approved" ? "Ran" : "Denied"} {command}
                </TraceTitle>
              </TraceLine>
            </Trace>
          ) : (
            <Item variant="outline" size="sm">
              <ItemMedia variant="icon">
                <TerminalIcon />
              </ItemMedia>
              <ItemContent>
                <ItemTitle>Run {command}?</ItemTitle>
                <ItemDescription>
                  The command runs in this project.
                </ItemDescription>
              </ItemContent>
              <ItemActions>
                <Button
                  size="sm"
                  variant="outline"
                  onClick={() => setApproval("denied")}
                >
                  Deny
                </Button>
                <Button size="sm" onClick={() => setApproval("approved")}>
                  Approve
                </Button>
              </ItemActions>
            </Item>
          )}
          {approval && (
            <p>
              {approval === "approved"
                ? "Removed the dist folder."
                : "Okay, I left the dist folder in place."}
            </p>
          )}
        </MessageContent>
      </Message>
      {approval && (
        <Button
          variant="outline"
          size="sm"
          className="mt-auto self-start"
          onClick={() => setApproval(undefined)}
        >
          Reset
        </Button>
      )}
    </div>
  )
}
```

Confirmation asks the person for a yes or no before a tool runs. The question is an [Item](https://suiss.app/ui/docs/components/item) with Deny and Approve buttons. Once the person answers, the call becomes a [Tool](https://suiss.app/ui/docs/ai/tool) row.

A pending approval is a prompt, not activity, so it sits outside the [Trace](https://suiss.app/ui/docs/components/trace).

## Installation

Confirmation is built with the [Item](https://suiss.app/ui/docs/components/item), [Button](https://suiss.app/ui/docs/components/button) and [Trace](https://suiss.app/ui/docs/components/trace) components.

```bash title="Terminal"
npx shadcn@latest add @esuiss/item @esuiss/button @esuiss/trace
```

Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

```bash title="Terminal"
npx @esuiss/ui@latest add item button trace
```

## Usage

```tsx
<Item variant="outline" size="sm">
  <ItemMedia variant="icon">
    <TerminalIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Run {command}?</ItemTitle>
    <ItemDescription>The command runs in this project.</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button size="sm" variant="outline" onClick={deny}>
      Deny
    </Button>
    <Button size="sm" onClick={approve}>
      Approve
    </Button>
  </ItemActions>
</Item>
```

After the answer, replace the item with a row that says what happened: "Ran rm -rf dist" or "Denied rm -rf dist".

## AI SDK

While the part is `approval-requested`, answer with the approval id. Pass `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses` so the chat continues once every approval has an answer.

```tsx
addToolApprovalResponse({
  id: part.approval.id,
  approved: true,
})
```

```tsx
useChat({
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
})
```

Approve streams the tool output. Deny streams `output-denied`. Either way, the part becomes a Tool row. Set `needsApproval` on the tool on the server to ask first.

## API Reference

See [Item](https://suiss.app/ui/docs/components/item#api-reference), [Button](https://suiss.app/ui/docs/components/button#api-reference) and [Trace](https://suiss.app/ui/docs/components/trace#api-reference).

## Accessibility

- Put the command in the item title, so the question names what will run.
- Deny comes before Approve, so the safe answer is reached first with Tab.
- Once answered, the buttons are removed and the row title says the outcome in words.

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { ShieldWarningIcon, TerminalIcon } from "phosphor-react-native"
import * as React from "react"
import { View } from "react-native"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { Message, MessageContent } from "@/components/ui/message"
import { Text } from "@/components/ui/text"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

type Decision = "pending" | "approved" | "denied"

function ConfirmationExample() {
  const [decision, setDecision] = React.useState<Decision>("pending")
  return (
    <View className="w-full gap-4">
      <Message align="end">
        <MessageContent>
          <Bubble variant="muted">
            <BubbleContent>Clean up the build folder.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      {decision === "pending" ? (
        <View className="gap-3">
          <Trace>
            <TraceLine>
              <TraceIcon>
                <Icon as={ShieldWarningIcon} />
              </TraceIcon>
              <TraceTitle>Wants to run a command</TraceTitle>
            </TraceLine>
          </Trace>
          <CodeBlock size="sm">
            <CodeBlockContent language="bash">rm -rf .next</CodeBlockContent>
          </CodeBlock>
          <View className="flex-row gap-2">
            <Button size="sm" onPress={() => setDecision("approved")}>
              Approve
            </Button>
            <Button
              variant="outline"
              size="sm"
              onPress={() => setDecision("denied")}
            >
              Deny
            </Button>
          </View>
        </View>
      ) : (
        <View className="gap-3">
          <Trace>
            <TraceLine status={decision === "denied" ? "error" : undefined}>
              <TraceIcon>
                <Icon as={TerminalIcon} />
              </TraceIcon>
              <TraceTitle>
                {decision === "approved"
                  ? "Ran rm -rf .next"
                  : "Denied rm -rf .next"}
              </TraceTitle>
            </TraceLine>
          </Trace>
          <Text>
            {decision === "approved"
              ? "Removed the build folder."
              : "Left the build folder as it is."}
          </Text>
          <View className="items-start">
            <Button
              variant="ghost"
              size="sm"
              onPress={() => setDecision("pending")}
            >
              Reset
            </Button>
          </View>
        </View>
      )}
    </View>
  )
}

export function AiConfirmationExample() {
  return (
    <ConfirmationExample />
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

enum ConfirmationDecision {
  case pending, approved, denied
}

struct ConfirmationExample: View {
  @State private var decision = ConfirmationDecision.pending

  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Message(align: .end) {
        MessageContent {
          Bubble(variant: .muted, align: .end) { BubbleContent("Clean up the build folder.") }
        }
      }
      if decision == .pending {
        Trace {
          TraceLine {
            TraceIcon { Ph.shieldWarning.regular }
            TraceTitle("Wants to run a command")
          }
        }
        CodeBlock(size: .sm) { CodeBlockContent("rm -rf .next", language: "bash") }
        HStack(spacing: 8) {
          Button("Approve") { decision = .approved }
            .buttonStyle(.variant(.default, size: .sm))
          Button("Deny") { decision = .denied }
            .buttonStyle(.variant(.outline, size: .sm))
        }
      } else {
        Trace {
          TraceLine(status: decision == .denied ? .error : nil) {
            TraceIcon { Ph.terminal.regular }
            TraceTitle(verbatim: decision == .approved ? "Ran rm -rf .next" : "Denied rm -rf .next")
          }
        }
        Text(decision == .approved ? "Removed the build folder." : "Left the build folder as it is.")
          .textStyle(.base)
          .foregroundStyle(EsuissColor.foreground)
        Button("Reset") { decision = .pending }
          .buttonStyle(.variant(.ghost, size: .sm))
      }
    }
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.ShieldWarning
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.Bubble
import com.esuiss.ui.components.BubbleContent
import com.esuiss.ui.components.BubbleVariant
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Message
import com.esuiss.ui.components.MessageAlign
import com.esuiss.ui.components.MessageContent
import com.esuiss.ui.components.Text
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

@Composable
fun ConfirmationAIDefault() {
    var decision by rememberSaveable { mutableStateOf("pending") }
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Message(Modifier.fillMaxWidth(), align = MessageAlign.End) {
            MessageContent {
                Bubble(variant = BubbleVariant.Muted) { BubbleContent("Clean up the build folder.") }
            }
        }
        if (decision == "pending") {
            Trace {
                TraceLine {
                    TraceIcon { Icon(PhosphorIcons.Regular.ShieldWarning, contentDescription = null) }
                    TraceTitle("Wants to run a command")
                }
            }
            CodeBlock(size = CodeBlockSize.Sm) { CodeBlockContent("rm -rf .next", language = "bash") }
            Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                Button("Approve", onClick = { decision = "approved" }, size = ButtonSize.Sm)
                Button("Deny", onClick = { decision = "denied" }, variant = ButtonVariant.Outline, size = ButtonSize.Sm)
            }
        } else {
            Trace {
                TraceLine(status = if (decision == "denied") TraceStatus.Error else null) {
                    TraceIcon { Icon(PhosphorIcons.Regular.Terminal, contentDescription = null) }
                    TraceTitle(if (decision == "approved") "Ran rm -rf .next" else "Denied rm -rf .next")
                }
            }
            Text(if (decision == "approved") "Removed the build folder." else "Left the build folder as it is.")
            Button("Reset", onClick = { decision = "pending" }, variant = ButtonVariant.Ghost, size = ButtonSize.Sm)
        }
    }
}
```

Confirmation on mobile is built from the same parts as on the web: [Trace](https://suiss.app/ui/docs/components/trace), [Code Block](https://suiss.app/ui/docs/components/code-block), [Button](https://suiss.app/ui/docs/components/button), [Message](https://suiss.app/ui/docs/components/message), [Bubble](https://suiss.app/ui/docs/components/bubble). The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

## Installation

**React Native**

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block button message bubble
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block button message bubble --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block button message bubble --platform kotlin
```

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace), [Code Block](https://suiss.app/ui/docs/components/code-block), [Button](https://suiss.app/ui/docs/components/button), [Message](https://suiss.app/ui/docs/components/message), [Bubble](https://suiss.app/ui/docs/components/bubble).

