> For AI agents: the complete documentation index is available at https://wallarm.github.io/design-system/llms.txt, the full documentation bundle is available at https://wallarm.github.io/design-system/llms-full.txt.

# ApprovalCard

Asks the user to approve an action Wally wants to take — a tool call, a config change — before it runs. While `status` is `pending` the card shows its children (`ApprovalCardContent` and `ApprovalCardActions`); once the app sets `approved` or `rejected`, the children are replaced by a one-line status, so the buttons need no gating of their own. The card has no outer margins or horizontal padding: the panel gutter (`ChatBody` / `ChatFooter`) places it, whether it sits inline in the conversation or docked above the input. Buttons are plain DS components composed by the app, which also owns loading — disable them while the request is in flight.

[Source](https://github.com/wallarm/design-system/tree/main/packages/design-system/src/components/ApprovalCard) · [Storybook](https://wallarm.github.io/design-system/storybook/?path=/docs/ai-approvalcard--overview)

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const Pending = () => (
  <ApprovalCard data-testid='approval-card'>
    <ApprovalCardContent>Add 203.0.113.42 to the denylist on all applications</ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options' data-testid='approval-card-more'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem data-testid='approval-card-allow-once'>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

## Usage

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';
```

## Examples

### Pending long text

Long text wraps, and unbroken strings such as URLs break rather than overflow the card.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const PendingLongText = () => (
  <ApprovalCard data-testid='approval-card'>
    <ApprovalCardContent>
      Disable the rule "Block SQL injection attempts" for the application "Payments API" and
      redeploy the policy to every node in the eu-west-1 cluster. Affected endpoint:
      https://payments.example.com/api/v2/transactions/refunds/batch-processing/callbacks
    </ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options' data-testid='approval-card-more'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem data-testid='approval-card-allow-once'>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

### Pending with code snippet

Arguments the user should read verbatim go in a `CodeSnippet`, 8px under the text.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { CodeSnippetCode, CodeSnippetContent, CodeSnippetRoot } from '@wallarm-org/design-system/CodeSnippet';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const PendingWithCodeSnippet = () => (
  <ApprovalCard data-testid='approval-card'>
    <ApprovalCardContent>
      <p>Run this command on the node:</p>
      <CodeSnippetRoot
        code='wallarm-cli rules disable --id 4021 --app payments-api'
        language='bash'
      >
        <CodeSnippetContent>
          <CodeSnippetCode />
        </CodeSnippetContent>
      </CodeSnippetRoot>
    </ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options' data-testid='approval-card-more'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem data-testid='approval-card-allow-once'>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

### Allow once menu

The chevron half of the split button opens the narrower "Allow once" option.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const AllowOnceMenu = () => (
  <ApprovalCard data-testid='approval-card'>
    <ApprovalCardContent>Add 203.0.113.42 to the denylist on all applications</ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }} defaultOpen>
        <SplitButton>
          <Button>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options' data-testid='approval-card-more'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem data-testid='approval-card-allow-once'>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

### Loading

There is no loading variant: while the decision is being sent, the app disables its own buttons.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const Loading = () => (
  <ApprovalCard data-testid='approval-card'>
    <ApprovalCardContent>Add 203.0.113.42 to the denylist on all applications</ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button disabled>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options' data-testid='approval-card-more' disabled>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem data-testid='approval-card-allow-once'>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral' disabled>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

### Approved

After approval the content and buttons collapse into a status row; the caption stays. The
children are kept on purpose: the app renders the same tree and only flips `status`.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const Approved = () => (
  <ApprovalCard data-testid='approval-card' status='approved'>
    <ApprovalCardContent>Add 203.0.113.42 to the denylist on all applications</ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

### Rejected

The rejected status row. `rejectedLabel` (like `approvedLabel` and `caption`) is a prop, so
the app can say what was rejected.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const Rejected = () => (
  <ApprovalCard
    data-testid='approval-card'
    status='rejected'
    rejectedLabel='Denylist change rejected'
  >
    <ApprovalCardContent>Add 203.0.113.42 to the denylist on all applications</ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button>Always allow in this session</Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

### Analytics

Analytics attributes go on the composed DS buttons themselves — the card adds no seam of
its own, so each `data-analytics-id` lands on the real `<button>`.

```tsx
import { ApprovalCard, ApprovalCardActions, ApprovalCardContent } from '@wallarm-org/design-system/ApprovalCard';
import { Button } from '@wallarm-org/design-system/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { ChevronDown } from '@wallarm-org/design-system/icons';
import { SplitButton } from '@wallarm-org/design-system/SplitButton';

export const Analytics = () => (
  <ApprovalCard data-testid='approval-card' data-analytics-id='AI_APPROVAL_CARD'>
    <ApprovalCardContent>Add 203.0.113.42 to the denylist on all applications</ApprovalCardContent>
    <ApprovalCardActions>
      <DropdownMenu positioning={{ placement: 'bottom-end' }}>
        <SplitButton>
          <Button data-analytics-id='AI_APPROVAL_ALLOW_SESSION'>
            Always allow in this session
          </Button>
          <DropdownMenuTrigger asChild>
            <Button aria-label='More approval options' data-analytics-id='AI_APPROVAL_MORE'>
              <ChevronDown />
            </Button>
          </DropdownMenuTrigger>
        </SplitButton>
        <DropdownMenuContent>
          <DropdownMenuItem data-analytics-id='AI_APPROVAL_ALLOW_ONCE'>Allow once</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <Button variant='secondary' color='neutral' data-analytics-id='AI_APPROVAL_REJECT'>
        Reject
      </Button>
    </ApprovalCardActions>
  </ApprovalCard>
);
```

## Props

| Prop           | Type                                                 | Default                  | Description                                                                                   |
| -------------- | ---------------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------- |
| status         | `ApprovalCardStatus \| undefined`                    | `pending`                | Pending shows `children` (content + actions); resolved states replace them with a status row. |
| caption        | `ReactNode`                                          | `Requires your approval` | Shown above the card in every state. Pass `null` to hide it.                                  |
| approvedLabel  | `ReactNode`                                          | `Approved`               |                                                                                               |
| rejectedLabel  | `ReactNode`                                          | `Rejected`               |                                                                                               |
| defaultChecked | `boolean \| undefined`                               |                          |                                                                                               |
| defaultValue   | `string \| number \| readonly string[] \| undefined` |                          |                                                                                               |
| autoFocus      | `boolean \| undefined`                               |                          |                                                                                               |
| id             | `string \| undefined`                                |                          |                                                                                               |
| tabIndex       | `number \| undefined`                                |                          |                                                                                               |
| title          | `string \| undefined`                                |                          |                                                                                               |
| rel            | `string \| undefined`                                |                          |                                                                                               |
| onChange       | `ChangeEventHandler<T, Element> \| undefined`        |                          |                                                                                               |
| data-testid    | `string \| undefined`                                |                          |                                                                                               |

### ApprovalCardActions

| Prop           | Type                                                 | Default | Description |
| -------------- | ---------------------------------------------------- | ------- | ----------- |
| defaultChecked | `boolean \| undefined`                               |         |             |
| defaultValue   | `string \| number \| readonly string[] \| undefined` |         |             |
| autoFocus      | `boolean \| undefined`                               |         |             |
| id             | `string \| undefined`                                |         |             |
| tabIndex       | `number \| undefined`                                |         |             |
| title          | `string \| undefined`                                |         |             |
| rel            | `string \| undefined`                                |         |             |
| onChange       | `ChangeEventHandler<T, Element> \| undefined`        |         |             |
| data-testid    | `string \| undefined`                                |         |             |

### ApprovalCardContent

| Prop           | Type                                                 | Default | Description |
| -------------- | ---------------------------------------------------- | ------- | ----------- |
| defaultChecked | `boolean \| undefined`                               |         |             |
| defaultValue   | `string \| number \| readonly string[] \| undefined` |         |             |
| autoFocus      | `boolean \| undefined`                               |         |             |
| id             | `string \| undefined`                                |         |             |
| tabIndex       | `number \| undefined`                                |         |             |
| title          | `string \| undefined`                                |         |             |
| rel            | `string \| undefined`                                |         |             |
| onChange       | `ChangeEventHandler<T, Element> \| undefined`        |         |             |
| data-testid    | `string \| undefined`                                |         |             |

## Explorer

