Actions
The Actions
component provides a flexible row of action buttons for AI responses with common actions like retry, like, dislike, copy, and share.
Hello, how are you?
I am fine, thank you!
Installation
npx ai-elements@latest add actions
Usage
import { Actions, Action } from '@/components/ai-elements/actions';import { ThumbsUpIcon } from 'lucide-react';
<Actions className="mt-2"> <Action label="Like"> <ThumbsUpIcon className="size-4" /> </Action></Actions>
Usage with AI SDK
Build a simple chat UI where the user can copy or regenerate the most recent message.
Add the following component to your frontend:
app/page.tsx
'use client';
import { useState } from 'react';import { Actions, Action } from '@/components/ai-elements/actions';import { Message, MessageContent } from '@/components/ai-elements/message';import { Conversation, ConversationContent, ConversationScrollButton,} from '@/components/ai-elements/conversation';import { Input, PromptInputTextarea, PromptInputSubmit,} from '@/components/ai-elements/prompt-input';import { Response } from '@/components/ai-elements/response';import { RefreshCcwIcon, CopyIcon } from 'lucide-react';import { useChat } from '@ai-sdk/react';
const ActionsDemo = () => { const [input, setInput] = useState(''); const { messages, sendMessage, status, regenerate } = useChat();
const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } };
return ( <div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]"> <div className="flex flex-col h-full"> <Conversation> <ConversationContent> {messages.map((message, messageIndex) => ( <Message from={message.role} key={message.id}> <MessageContent> {message.parts.map((part, i) => { switch (part.type) { case 'text': const isLastMessage = messageIndex === messages.length - 1; return ( <div key={`${message.id}-${i}`}> <Response>{part.text}</Response> {message.role === 'assistant' && isLastMessage && ( <Actions className="mt-2"> <Action onClick={() => regenerate()} label="Retry" > <RefreshCcwIcon className="size-3" /> </Action> <Action onClick={() => navigator.clipboard.writeText(part.text) } label="Copy" > <CopyIcon className="size-3" /> </Action> </Actions> )} </div> ); default: return null; } })} </MessageContent> </Message> ))} </ConversationContent> <ConversationScrollButton /> </Conversation>
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative" > <PromptInputTextarea value={input} placeholder="Say something..." onChange={(e) => setInput(e.currentTarget.value)} className="pr-12" /> <PromptInputSubmit status={status === 'streaming' ? 'streaming' : 'ready'} disabled={!input.trim()} className="absolute bottom-1 right-1" /> </Input> </div> </div> );};
export default ActionsDemo;
Add the following route to your backend:
api/chat/route.ts
import { streamText, UIMessage, convertToModelMessages } from 'ai';
// Allow streaming responses up to 30 secondsexport const maxDuration = 30;
export async function POST(req: Request) { const { model, messages }: { messages: UIMessage[]; model: string } = await req.json();
const result = streamText({ model: 'openai/gpt-4o', messages: convertToModelMessages(messages), });
return result.toUIMessageStreamResponse();}
Features
- Row of composable action buttons with consistent styling
- Support for custom actions with tooltips
- Hover-to-show behavior by default (configurable)
- State management for toggle actions (like, dislike, favorite)
- Keyboard accessible with proper ARIA labels
- Clipboard and Web Share API integration
- TypeScript support with proper type definitions
- Consistent with design system styling
Examples
This is a response from an assistant.
Try hovering over this message to see the actions appear!
Props
<Actions />
[...props]:
React.HTMLAttributes<HTMLDivElement>
HTML attributes to spread to the root div.
<Action />
tooltip?:
string
Optional tooltip text shown on hover.
label?:
string
Accessible label for screen readers. Also used as fallback if tooltip is not provided.
[...props]:
React.ComponentProps<typeof Button>
Any other props are spread to the underlying shadcn/ui Button component.