> 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.

# MessageActions

The small row of icon buttons under a chat message — copy, feedback, a more menu and a timestamp. Every `MessageAction` is a ghost icon button whose required `aria-label` doubles as its tooltip, so the row never needs a visible caption. By default the row is transparent until the nearest `group/message` parent is hovered or holds focus, keeping its 30px height so the conversation does not jump; pass `visible` for the latest answer. Copy is composed with `Copyable` and `CopyableIcon`, menus with `DropdownMenuTrigger asChild`, and feedback with `aria-pressed` — the row itself holds no state. Give a menu-trigger action an explicit `data-testid`: the trigger injects its own `--trigger` id, which replaces the cascaded `--action` one.

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

```tsx
import { Copyable, CopyableIcon } from '@wallarm-org/design-system/Copyable';
import { MessageAction, MessageActions, MessageTimestamp } from '@wallarm-org/design-system/MessageActions';

export const User = () => (
  <MessageActions data-testid='user-actions' visible>
    <MessageTimestamp dateTime='2026-10-21T09:41:00Z'>2m ago</MessageTimestamp>
    <Copyable text='Show me blocked attacks'>
      <MessageAction aria-label='Copy' data-testid='user-actions--copy'>
        <CopyableIcon />
      </MessageAction>
    </Copyable>
  </MessageActions>
);
```

## Usage

```tsx
import { Copyable, CopyableIcon } from '@wallarm-org/design-system/Copyable';
import { MessageAction, MessageActions, MessageTimestamp } from '@wallarm-org/design-system/MessageActions';
```

## Examples

### Wally

Under a Wally answer: Copy, thumbs up and down, a more menu, then the timestamp. The
Ellipsis button is the menu trigger and keeps its own tooltip anchored to it.

```tsx
import { Copyable, CopyableIcon } from '@wallarm-org/design-system/Copyable';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { Ellipsis, ThumbsDown, ThumbsUp } from '@wallarm-org/design-system/icons';
import { MessageAction, MessageActions, MessageTimestamp } from '@wallarm-org/design-system/MessageActions';

export const Wally = () => (
  <MessageActions data-testid='wally-actions' visible>
    <Copyable text={ANSWER}>
      <MessageAction aria-label='Copy' data-testid='wally-actions--copy'>
        <CopyableIcon />
      </MessageAction>
    </Copyable>
    <MessageAction aria-label='Good response' data-testid='wally-actions--up'>
      <ThumbsUp />
    </MessageAction>
    <MessageAction aria-label='Bad response' data-testid='wally-actions--down'>
      <ThumbsDown />
    </MessageAction>
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <MessageAction aria-label='More actions' data-testid='wally-actions--more'>
          <Ellipsis />
        </MessageAction>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Regenerate</DropdownMenuItem>
        <DropdownMenuItem>Report a problem</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
    <MessageTimestamp dateTime='2026-10-21T09:42:00Z'>1m ago</MessageTimestamp>
  </MessageActions>
);
```

### Visible

Default versus `visible`. The first row stays transparent (but tabbable) until its message
is hovered or focused; the second is forced on, as for the latest answer.

```tsx
import { Copyable, CopyableIcon } from '@wallarm-org/design-system/Copyable';
import { MessageAction, MessageActions, MessageTimestamp } from '@wallarm-org/design-system/MessageActions';

export const Visible = () => (
  <div className='flex flex-col gap-16'>
    <div className='group/message flex flex-col items-start gap-4' data-testid='message-default'>
      <p className='text-sm text-text-primary'>Hover to reveal</p>
      <MessageActions data-testid='actions-default'>
        <Copyable text={ANSWER}>
          <MessageAction aria-label='Copy' data-testid='actions-default--copy'>
            <CopyableIcon />
          </MessageAction>
        </Copyable>
        <MessageTimestamp dateTime='2026-10-21T09:42:00Z'>1m ago</MessageTimestamp>
      </MessageActions>
    </div>
    <div className='group/message flex flex-col items-start gap-4' data-testid='message-visible'>
      <p className='text-sm text-text-primary'>visible</p>
      <MessageActions data-testid='actions-visible' visible>
        <Copyable text={ANSWER}>
          <MessageAction aria-label='Copy' data-testid='actions-visible--copy'>
            <CopyableIcon />
          </MessageAction>
        </Copyable>
        <MessageTimestamp dateTime='2026-10-21T09:42:00Z'>1m ago</MessageTimestamp>
      </MessageActions>
    </div>
  </div>
);
```

### Pressed feedback

Thumbs feedback is a plain toggle: pass `aria-pressed` and the button keeps the
`states-primary-active` background while pressed. Click one to try it.

```tsx
import { useState } from 'react';
import { ThumbsDown, ThumbsUp } from '@wallarm-org/design-system/icons';
import { MessageAction, MessageActions } from '@wallarm-org/design-system/MessageActions';

export const PressedFeedback = () => {
  const [feedback, setFeedback] = useState<'up' | 'down' | null>('up');

  const handleGoodClick = () => {
    setFeedback(current => (current === 'up' ? null : 'up'));
  };

  const handleBadClick = () => {
    setFeedback(current => (current === 'down' ? null : 'down'));
  };

  return (
    <MessageActions data-testid='feedback-actions' visible>
      <MessageAction
        aria-label='Good response'
        aria-pressed={feedback === 'up'}
        data-testid='feedback-up'
        onClick={handleGoodClick}
      >
        <ThumbsUp />
      </MessageAction>
      <MessageAction
        aria-label='Bad response'
        aria-pressed={feedback === 'down'}
        data-testid='feedback-down'
        onClick={handleBadClick}
      >
        <ThumbsDown />
      </MessageAction>
    </MessageActions>
  );
};
```

### Analytics

`data-analytics-id` and `data-analytics-props` go straight onto each `MessageAction` and
land on its real `<button>` — also through `Copyable` and `DropdownMenuTrigger asChild`.

```tsx
import { Copyable, CopyableIcon } from '@wallarm-org/design-system/Copyable';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@wallarm-org/design-system/DropdownMenu';
import { Ellipsis, ThumbsUp } from '@wallarm-org/design-system/icons';
import { MessageAction, MessageActions } from '@wallarm-org/design-system/MessageActions';

export const Analytics = () => (
  <MessageActions data-testid='analytics-actions' visible>
    <Copyable text={ANSWER}>
      <MessageAction
        aria-label='Copy'
        data-testid='analytics-actions--copy'
        data-analytics-id='WALLY_ANSWER_COPY'
      >
        <CopyableIcon />
      </MessageAction>
    </Copyable>
    <MessageAction
      aria-label='Good response'
      data-testid='analytics-actions--up'
      data-analytics-id='WALLY_ANSWER_FEEDBACK'
      data-analytics-props='{"rating":"up"}'
    >
      <ThumbsUp />
    </MessageAction>
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <MessageAction
          aria-label='More actions'
          data-testid='analytics-actions--more'
          data-analytics-id='WALLY_ANSWER_MORE'
        >
          <Ellipsis />
        </MessageAction>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Regenerate</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  </MessageActions>
);
```

## Props

| Prop           | Type                                                 | Default | Description                                                                                                                                                                                                                                 |
| -------------- | ---------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| visible        | `boolean \| undefined`                               | `false` | Force-show the row (e.g. the latest answer). By default the row is revealed on hover / focus-within of the nearest `group/message` parent, and always shown on `hover: none` devices. Hidden rows keep their 30px height and stay tabbable. |
| 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`                                |         |                                                                                                                                                                                                                                             |

### MessageAction

| Prop           | Type                                                 | Default | Description                                         |
| -------------- | ---------------------------------------------------- | ------- | --------------------------------------------------- |
| aria-label \*  | `string`                                             |         | Required: the accessible name and the tooltip text. |
| title          | `string \| undefined`                                |         |                                                     |
| defaultChecked | `boolean \| undefined`                               |         |                                                     |
| defaultValue   | `string \| number \| readonly string[] \| undefined` |         |                                                     |
| autoFocus      | `boolean \| undefined`                               |         |                                                     |
| id             | `string \| undefined`                                |         |                                                     |
| tabIndex       | `number \| undefined`                                |         |                                                     |
| rel            | `string \| undefined`                                |         |                                                     |
| onChange       | `ChangeEventHandler<T, Element> \| undefined`        |         |                                                     |
| type           | `"button" \| "submit" \| "reset" \| undefined`       |         |                                                     |
| disabled       | `boolean \| undefined`                               |         |                                                     |
| name           | `string \| undefined`                                |         |                                                     |
| value          | `string \| number \| readonly string[] \| undefined` |         |                                                     |
| data-testid    | `string \| undefined`                                |         |                                                     |

### MessageTimestamp

| 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

