Skip to main content
NachUI

Actions

The row of small icon buttons under an assistant message, with a copy button that confirms itself.

Keep the trigger and the region linked with aria-controls and aria-labelledby, and let the open state live in one place so both stay in sync.

1'use client';
2
3import { useState } from 'react';
4import { Actions } from '@/components/ui/actions';
5
6const ANSWER =
7 'Keep the trigger and the region linked with aria-controls and aria-labelledby, and let the open state live in one place so both stay in sync.';
8
9export function Default() {
10 const [vote, setVote] = useState<'up' | 'down' | null>(null);
11
12 return (
13 <div className="flex w-full max-w-md flex-col gap-2">
14 <p className="text-foreground/90 text-sm leading-relaxed">{ANSWER}</p>
15 <Actions>
16 <Actions.Copy text={ANSWER} />
17 <Actions.Button label="Retry">
18 <Actions.Icons.retry />
19 </Actions.Button>
20 <Actions.Button
21 label="Good response"
22 active={vote === 'up'}
23 onClick={() => setVote((current) => (current === 'up' ? null : 'up'))}
24 >
25 <Actions.Icons.thumbsUp />
26 </Actions.Button>
27 <Actions.Button
28 label="Bad response"
29 active={vote === 'down'}
30 onClick={() => setVote((current) => (current === 'down' ? null : 'down'))}
31 >
32 <Actions.Icons.thumbsDown />
33 </Actions.Button>
34 </Actions>
35 </div>
36 );
37}

Installation

pnpm dlx nachui add actions

Anatomy

1import { Actions } from '@/components/ui/actions';
1<Actions>
2 <Actions.Copy text={answer} />
3 <Actions.Button label="Retry" onClick={regenerate}>
4 <Actions.Icons.retry />
5 </Actions.Button>
6 <Actions.Button label="Good response" active={vote === 'up'} onClick={voteUp}>
7 <Actions.Icons.thumbsUp />
8 </Actions.Button>
9</Actions>
Every button takes a label. It becomes the accessible name and the native tooltip, so the icon alone is never the only description. active marks a pressed state, for example the vote the reader already gave.

Composition

Use the following composition to build Actions:
Actions
├── Actions.Copy
└── Actions.Button
└── Actions.Icons.*

With a message

Actions sit naturally in a Message.Footer. Keep them at the start of the row for assistant messages and at the end for the reader's own.
Assistant
Done. I moved the open state up to the root and the region follows it.
1'use client';
2
3import { Actions } from '@/components/ui/actions';
4import { Bubble } from '@/components/ui/bubble';
5import { Message } from '@/components/ui/message';
6
7const ANSWER = 'Done. I moved the open state up to the root and the region follows it.';
8
9export function WithMessage() {
10 return (
11 <div className="w-full max-w-md">
12 <Message>
13 <Message.Content>
14 <Message.Header>Assistant</Message.Header>
15 <Bubble variant="secondary">
16 <Bubble.Content>{ANSWER}</Bubble.Content>
17 </Bubble>
18 <Message.Footer className="px-0">
19 <Actions>
20 <Actions.Copy text={ANSWER} />
21 <Actions.Button label="Retry">
22 <Actions.Icons.retry />
23 </Actions.Button>
24 <Actions.Button label="Share">
25 <Actions.Icons.share />
26 </Actions.Button>
27 </Actions>
28 </Message.Footer>
29 </Message.Content>
30 </Message>
31 </div>
32 );
33}

Copy

Actions.Copy writes text to the clipboard and swaps to a check for a moment, with the label changing to "Copied" so screen readers get the confirmation too. label, copiedLabel and timeout tune it.

Icons

Actions.Icons ships copy, check, retry, thumbsUp, thumbsDown and share as plain SVG components, so the common row needs no icon package. Any SVG works as a child.

API Reference

Actions

PropTypeDefaultDescription
align'start' | 'end''start'Side of the row the buttons sit on
classNamestring-Additional CSS classes

Actions.Button

PropTypeDefaultDescription
labelstring-Accessible name and tooltip, required
activebooleanfalsePressed state, sets data-active
childrenReactNode-The icon
classNamestring-Additional CSS classes

Actions.Copy

PropTypeDefaultDescription
textstring-What gets copied
labelstring'Copy'Label at rest
copiedLabelstring'Copied'Label after copying
timeoutnumber1500Milliseconds before the label returns
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue