# MessageActionCard

> A card an agent attaches to its message with an action already filled in, for a person to confirm; afterwards it records who did it and when.

Source: https://raft-design.botiverse.build/components/message-action-card. Generated from the current release; where it differs from what you remember, this page is right.

An action card is how an agent asks for something it may not do alone: create a channel, add people, merge, publish, spend money. The agent fills in the action and attaches the card to its message; a person confirms it. Afterwards the card stays in the conversation as the record of who did it and when. A request waiting for someone is also listed in Activity ([Hand-offs and approvals](https://raft-design.botiverse.build/people-and-agents/handoffs-and-approvals.md#approvals-the-agent-prepares-a-person-confirms)).

```tsx
import { useCallback, useState } from "react";
import { SegmentedControl, SegmentedControlItem, SegmentedControlLabel } from "@botiverse/raft-ui";

import { ActionCardDemo } from "../action-card-demo.tsx";
import { ActionCardMessage } from "../action-card-message.tsx";
import { ACTION_CARD_FIXTURES, ACTION_CARD_STATES } from "../action-card.fixtures.ts";
import type { ActionCardKind, ActionCardState } from "../action-card.fixtures.ts";

const EXAMPLES: ReadonlyArray<{ value: ActionCardKind; label: string }> = [
  { value: "channel", label: "Review · Create channel" },
  { value: "login", label: "Approve · App login" },
];

/** One card you can walk through every state; resolved states fold, with the
 *  header toggle to open them again. */
export function ActionCardInteractiveComposition() {
  const [kind, setKind] = useState<ActionCardKind>("channel");
  const [state, setState] = useState<ActionCardState>("pending");
  const handleKindChange = useCallback((value: unknown) => setKind(value as ActionCardKind), []);
  const handleStateChange = useCallback((value: unknown) => setState(value as ActionCardState), []);

  return (
    <div className="grid w-full gap-4">
      <div className="flex flex-wrap items-center gap-3">
        <SegmentedControl value={kind} onValueChange={handleKindChange} aria-label="Pattern">
          {EXAMPLES.map((example) => (
            <SegmentedControlItem key={example.value} value={example.value}>
              <SegmentedControlLabel>{example.label}</SegmentedControlLabel>
            </SegmentedControlItem>
          ))}
        </SegmentedControl>
        <SegmentedControl value={state} onValueChange={handleStateChange} aria-label="State">
          {ACTION_CARD_STATES.map((option) => (
            <SegmentedControlItem key={option.value} value={option.value}>
              <SegmentedControlLabel>{option.label}</SegmentedControlLabel>
            </SegmentedControlItem>
          ))}
        </SegmentedControl>
      </div>
      <ActionCardMessage time="2:27 PM">
        <ActionCardDemo card={ACTION_CARD_FIXTURES[kind]} state={state} />
      </ActionCardMessage>
    </div>
  );
}
```

## When to use

| Situation                                              | Use                                                                                   |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| An agent wants to do something a person must authorize | MessageActionCard                                                                     |
| An agent reports that it finished                      | Its message, with links to its proof ([MessageItem](https://raft-design.botiverse.build/components/message-item.md)) |
| An agent claimed or handed off work                    | An event line ([SystemMessage](https://raft-design.botiverse.build/components/system-message.md))                  |
| The person must decide now, outside a conversation     | [AlertDialog](https://raft-design.botiverse.build/components/alert-dialog.md)                                        |

## Anatomy

- `MessageActionCard`: the card, inside the body of the agent's message. Required.
- `MessageActionCardHeader`: the top row. Required.
  - `MessageActionCardHeaderIcon`: the kind of action, in a neutral tile like an `sm` [IconTile](https://raft-design.botiverse.build/components/icon-tile.md).
  - `MessageActionCardHeaderTitle`: what will be done. `MessageActionCardHeaderDescription` under it says what happened, or why the person can't act.
  - `MessageActionCardStatus`: who or what the card waits on, or how it ended, as a pixel and a word.
  - `MessageActionCardCollapseTrigger`: folds the card to its header and back.
- `MessageActionCardContent`: everything under the header; it folds.
  - `MessageActionCardNotice`: a standing note from Raft about consequences or permissions. At most one.
  - `MessageActionCardProperties`, `MessageActionCardProperty`, `MessageActionCardPropertyLabel`, `MessageActionCardPropertyValue`: the values the person checks, as a description list ([DescriptionList](https://raft-design.botiverse.build/components/description-list.md)).
  - `MessageActionCardHintLabel`, `MessageActionCardHint`: the agent's own note, such as its reason or what it filled in.
  - `MessageActionCardScopeList`, `MessageActionCardScopeListLabel`, `MessageActionCardScopeListViewport`, `MessageActionCardScope`, `MessageActionCardScopeName`, `MessageActionCardScopeDescription`: the permissions an app asks for.
  - `MessageActionCardActions`: the buttons, with `MessageActionCardActionsNote` before them.
  - `MessageActionCardCompletion`: the record of who did it and when.

```tsx
import { ActionCardDemo } from "../action-card-demo.tsx";
import { ActionCardMessage } from "../action-card-message.tsx";
import { ACTION_CARD_FIXTURES, REVIEW_KINDS } from "../action-card.fixtures.ts";

/** Review pattern: the primary button opens the regular dialog, prefilled. */
export function ActionCardChannelAndAgentComposition() {
  return (
    <ActionCardMessage time="2:31 PM">
      {REVIEW_KINDS.map((kind) => (
        <ActionCardDemo key={kind} card={ACTION_CARD_FIXTURES[kind]} state="pending" />
      ))}
    </ActionCardMessage>
  );
}
```

```tsx
import { ActionCardDemo } from "../action-card-demo.tsx";
import { ActionCardMessage } from "../action-card-message.tsx";
import { ACTION_CARD_FIXTURES, APPROVE_KINDS } from "../action-card.fixtures.ts";

/** Approve pattern: the primary button runs the action in one click. */
export function ActionCardIntegrationComposition() {
  return (
    <ActionCardMessage time="2:42 PM">
      {APPROVE_KINDS.map((kind) => (
        <ActionCardDemo key={kind} card={ACTION_CARD_FIXTURES[kind]} state="pending" />
      ))}
    </ActionCardMessage>
  );
}
```

## States

| State         | The card                                                                                    |
| ------------- | ------------------------------------------------------------------------------------------- |
| Waiting       | Open, with its buttons. The status says whom it waits on, with a pink pixel: Needs an admin |
| Paused        | Open; the note before the buttons says why, and the buttons are disabled                    |
| Confirm again | Open; something changed since the agent prepared it, and the note says what                 |
| Done          | Folded to its header; the description says who did it and when                              |
| Declined      | Folded; the description says who declined it and that nothing changed                       |
| View only     | Folded; the description says why the person can't act. No buttons                           |

- When the action fails, show a toast and leave the card as it was, so the person can try again ([Toast](https://raft-design.botiverse.build/components/toast.md)).

```tsx
import { ActionCardDemo } from "../action-card-demo.tsx";
import { ActionCardMessage } from "../action-card-message.tsx";
import { ACTION_CARD_FIXTURES, ACTION_CARD_STATES } from "../action-card.fixtures.ts";

/** Every state of one card. Done, declined and view-only start folded. */
export function ActionCardResolutionStatesComposition() {
  return (
    <ActionCardMessage time="3:02 PM">
      {ACTION_CARD_STATES.map((option) => (
        <ActionCardDemo
          key={option.value}
          card={ACTION_CARD_FIXTURES.channel}
          state={option.value}
        />
      ))}
    </ActionCardMessage>
  );
}
```

## Behavior

- The card is open by default. Fold a resolved or view-only card with `defaultOpen={false}`; the collapse trigger opens it again.
- A long list of permissions scrolls inside its own area, so the card doesn't grow with it.

## Content

- Title the card with a verb and its object, in sentence case: Create channel #launch-week, Merge #26 into main.
- Make the confirm button repeat the verb and the object of the title, in Title Case: Create Channel. Unlike a dialog's button, it keeps the object, because it appears away from a dialog title and has to say on its own which action it approves ([Writing](https://raft-design.botiverse.build/content/writing.md#actions-and-their-dialogs)).
- List only the properties the person needs to check.
- When the agent filled values in itself, say so in the hint: "Bugen filled these in from the thread. You can change them before it's created."
- Record the result with the person and the time: Approved by Martin · 10:51.

## Accessibility

`MessageActionCard` wraps Base UI's Collapsible: the collapse trigger and the folding content follow it.

- Give `MessageActionCardCollapseTrigger` an `aria-label` ("Show or hide details"); it shows only an icon.
- Give `MessageActionCardScopeListViewport` an `aria-label` that names the list ("Permissions"). It is a Tab stop only while it overflows, so keyboard users can scroll it.
- The header icon is hidden from assistive technology; the title carries the meaning.

## Rules

- Make the confirm button the small ink-bordered button (`Button` at the default `size="md"`), not pink: the screen's one pink button is the composer's send button.
- Put a ghost Edit First button beside it, which opens the ordinary dialog for the action with the agent's values filled in. Don't edit fields on the card: the card is a summary.
- Run the action as the person who confirmed it.
- Keep the card in the conversation after it is resolved, as the record of the action.
- Show no buttons to someone without the permission to act. Say whom the card waits on instead.
- Keep a notice neutral, never in a warning color.

## API

### MessageActionCard

The card is a disclosure: open by default, and a consumer can fold a resolved or view-only card to its header with `defaultOpen={false}` (or control it with `open` / `onOpenChange`). The fold is offered by placing a MessageActionCardCollapseTrigger in the header; without one the card simply stays open.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` | `true` | Uncontrolled initial state. Defaults to open. |
| `onOpenChange` | `(open: boolean, eventDetails: CollapsibleRootChangeEventDetails) => void` |  |  |
| `open` | `boolean` |  | Controlled open state. Open shows the content; closed folds to the header. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardActions

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardActionsNote

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardCollapseTrigger

Folds the card to its header and back. Needs an accessible `aria-label`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` |  | Defines a string value that labels the current element. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardCompletion

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardProperty

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardPropertyLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardProperties

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardPropertyValue

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHeaderDescription

A line under the title: what happened on a resolved card ("Installed by Ada → Lens"), or why you cannot act on a view-only one. Place it inside MessageActionCardHeaderTitle; it stays visible while the card is folded.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHeaderIcon

The card type's icon. Without children it shows a generic action icon.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHeaderStatus

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHeaderTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHint

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardHintLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardNotice

Raft's standing note. Leads with an info icon unless `icon` replaces it.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `icon` | `ReactNode` |  | Replaces the default info icon. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardScope

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardScopeDescription

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardScopeList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardScopeListLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardScopeListViewport

Scrolls a long permission list. It is a Tab stop only while it overflows, so keyboard users can scroll it; a short list adds no empty stop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardScopeName

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageActionCardStatus

The card's state label in the header: plain text led by a square pixel in the variant's colour, so a column of "Done" cards does not shout.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `appearance` | `"outline" \| "solid" \| "soft"` |  |  |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "information" \| "muted" \| "success"` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.10.7 (Fixed): In `MessageActionCardProperties`, a label lines up with the first line of its value instead of the middle of a value that wraps.
- 0.5.63 (Added): `MessageActionCardHeaderDescription`: a line under the title, placed inside `MessageActionCardHeaderTitle`, for what happened on a Done or Declined card or why you cannot act on a View only one. It stays visible while the card is folded.
- 0.5.63 (Changed): `MessageActionCardHeader` aligns to the top: icon, status and collapse toggle line up with the title's first line when the title wraps or carries a description.
- 0.5.63 (Changed): `MessageActionCardNotice` is a tinted block (Elegant: soft fill, rounded; Brutal: inset fill, hairline border) meant to open the content, above the properties; it now carries a bottom margin instead of a top one.
- 0.5.63 (Changed): `MessageActionCardHintLabel` and `MessageActionCardScopeListLabel` match the property label style in Elegant; `MessageActionCardActionsNote` reads in the foreground colour; the notice icon is centred on the first line.
- 0.5.61 (Added): `MessageActionCard` is a disclosure: `defaultOpen` / `open` / `onOpenChange`, and `MessageActionCardCollapseTrigger` (chevron toggle, `aria-label` required) folds the content with a 200ms height animation (off under reduced motion).
- 0.5.61 (Added): Header parts `MessageActionCardHeaderIcon` (type icon; a generic action icon when given no children), `MessageActionCardHeaderTitle` and `MessageActionCardStatus` (state label: solid and uppercase in Brutal, soft in Elegant).
- 0.5.61 (Added): `MessageActionCardHintLabel` ("Reason") above the agent's italic `MessageActionCardHint` quote, and `MessageActionCardNotice` for Raft's note about what the action does or who may run it: one plain line with a default info icon (`icon` replaces it).
