react-ai-chat-actions
v0.5.0
Published
React action toolbar for AI chat messages with like, dislike, copy, regenerate, speak, pin, themes and tooltips
Maintainers
Readme
react-ai-chat-actions
React action toolbar for AI chat messages. Add like, dislike, copy, regenerate, speak, pin, and other message reaction buttons to ChatGPT-like interfaces, AI assistants, chatbot UIs, and React/Next.js chat apps.
When to use
Use react-ai-chat-actions when you are building:
- AI chat message feedback controls
- ChatGPT-like assistant message actions
- Like/dislike/copy/regenerate buttons for LLM responses
- A React or Next.js chatbot UI that needs a polished message toolbar
- A reusable action bar with themes, tooltips, loading states, and a liquid glass hover effect
The package is a focused UI component for message-level actions. It does not manage chat state, send API requests, or store feedback for you.
Install
npm install react-ai-chat-actionsUsage
import { ActionBar } from "react-ai-chat-actions";
<ActionBar
messageId="msg-1"
actions={["like", "dislike", "divider", "copy", "regenerate"]}
onAction={(messageId, action) => console.log(messageId, action)}
/>;Props
| Prop | Type | Default | Description |
| ----------------------- | -------------------- | -------------------- | --------------------------------------------- |
| messageId | string | — | Required. Passed back in onAction |
| theme | ThemeName | light-pill | Theme preset |
| visible | boolean | true | Show or hide the bar |
| transparent | boolean | false | Transparent background of bar |
| actions | ActionItem[] | — | Built-in names and/or custom action objects |
| onAction | Function | — | (id, action) => void, fires on every click |
| activeActions | ActionId[] | — | Controlled active state (like, pin, …) |
| defaultActiveActions | ActionId[] | [] | Initial active state (uncontrolled) |
| onActiveActionsChange | Function | — | (id, next) => void on every toggle |
| copyText | string \| Function | — | Enables built-in clipboard copy |
| speakText | string \| Function | — | Enables built-in text-to-speech |
| icons | object | — | Override icons for built-in actions |
| loading | ActionId[] | [] | Buttons in loading state |
| disabled | ActionId[] | [] | Buttons in disabled state |
| ariaLabel | string | "Message actions" | Accessible name of the toolbar |
| tooltip | boolean | true | Show tooltips on hover |
| liquidGlass | boolean | false | Enable liquid glass hover effect |
Exact types: onAction: (messageId: string, action: ActionId) => void, onActiveActionsChange: (messageId: string, active: ActionId[]) => void, copyText/speakText: string | (() => string), icons: Partial<Record<ActionType, ReactNode>>.
ActionType
type ActionType =
| "like"
| "dislike"
| "heart"
| "copy"
| "regenerate"
| "retry"
| "speak"
| "pin"
| "bookmark"
| "share"
| "edit"
| "report"
| "translate"
| "options"
| "divider";Controlled active state
Toggle actions (like, dislike, heart, speak, options, pin, bookmark, and custom actions with toggle: true) track an active state. By default it lives inside the component. To persist it (e.g. feedback stored on your server), control it:
const [active, setActive] = useState<ActionId[]>(["like"]); // from your API
<ActionBar
messageId="msg-1"
actions={["like", "dislike", "divider", "pin"]}
activeActions={active}
onActiveActionsChange={(messageId, next) => {
setActive(next);
saveFeedback(messageId, next);
}}
onAction={(messageId, action) => console.log(messageId, action)}
/>;For uncontrolled usage with an initial value, use defaultActiveActions instead:
<ActionBar defaultActiveActions={["like"]} ... />like and dislike stay mutually exclusive in both modes. onAction fires on every click, including toggling off.
Custom actions
Mix custom actions with built-in ones directly in the actions array:
import { Sparkles } from "lucide-react";
<ActionBar
messageId="msg-1"
actions={[
"like",
"dislike",
"divider",
{ id: "explain", icon: <Sparkles size={16} />, label: "Explain" },
{ id: "raw", icon: <Braces size={16} />, label: "Show raw", toggle: true },
]}
onAction={(messageId, action) => {
if (action === "explain") explainMessage(messageId);
}}
/>;| Field | Type | Default | Description |
| -------- | ----------- | ------- | --------------------------------------------------- |
| id | string | — | Passed to onAction; also used in loading/disabled/activeActions |
| icon | ReactNode | — | Any icon element |
| label | string | — | Tooltip text and aria-label |
| toggle | boolean | false | Track active state like like/pin |
Custom icons
Override the icon of any built-in action without touching a custom action:
import { Smile, Frown } from "lucide-react";
<ActionBar
messageId="msg-1"
actions={["like", "dislike", "divider", "copy"]}
icons={{ like: <Smile size={16} />, dislike: <Frown size={16} /> }}
onAction={(messageId, action) => console.log(messageId, action)}
/>;icons only replaces the icon — the label, aria-label, and tooltip text stay the built-in default. Actions not listed in icons keep their default icon.
Built-in handlers
The bar stays headless by default — onAction is yours. Two optional built-ins:
Copy. Pass copyText and the copy button writes to the clipboard and shows a "Copied" check for ~1.6s. onAction still fires.
<ActionBar actions={["copy"]} copyText={message.content} ... />
// or lazy: copyText={() => serializeMessage(message)}Speak. Pass speakText and the speak button reads the text aloud via the Web Speech API. The button stays active while speaking; clicking again stops it. Silently does nothing in browsers without speechSynthesis.
<ActionBar actions={["speak"]} speakText={message.content} ... />Accessibility
The bar follows the WAI-ARIA toolbar pattern:
role="toolbar"with an accessible name (ariaLabelprop, default"Message actions")- One tab stop for the whole bar;
←→↑↓move between buttons,Home/Endjump to first/last - Visible
:focus-visibleoutline on keyboard focus - Tooltips show on keyboard focus, not only hover
- Toggle buttons expose
aria-pressed, loading buttonsaria-busy, dividersrole="separator" - With
ActionBarWrapper showOn="hover", the bar also appears on keyboard focus
Themes
Fifteen built-in themes: five color families (light, dark, neon, olive, violet) × three shapes (pill, soft, sharp).
| Theme | Shape |
| -------------- | --------------------------- |
| light-pill | Light, fully rounded |
| light-soft | Light, slightly rounded |
| light-sharp | Light, square corners |
| dark-pill | Dark, fully rounded |
| dark-soft | Dark, slightly rounded |
| dark-sharp | Dark, square corners |
| neon-pill | Neon glow, fully rounded |
| neon-soft | Neon glow, slightly rounded |
| neon-sharp | Neon glow, square corners |
| olive-pill | Olive, fully rounded |
| olive-soft | Olive, slightly rounded |
| olive-sharp | Olive, square corners |
| violet-pill | Violet, fully rounded |
| violet-soft | Violet, slightly rounded |
| violet-sharp | Violet, square corners |
<ActionBar theme="dark-sharp" ... />Liquid glass
Enable a glass-morphism sliding indicator that follows the cursor across buttons:
<ActionBar
liquidGlass={true}
...
/>Works best on dark and neon themes.
ActionBarWrapper
Wrap any component to attach the action bar to it. Controls position, visibility behavior, and layout mode.
import { ActionBarWrapper } from "react-ai-chat-actions";
<ActionBarWrapper
messageId="msg-1"
actions={["like", "dislike", "copy"]}
onAction={(id, action) => console.log(id, action)}
verticalPosition="bottom"
horizontalPosition="left"
showOn="hover"
float={false}
>
<div className="message">AI response text goes here</div>
</ActionBarWrapper>;ActionBarWrapper props
| Prop | Type | Default | Description |
| -------------------- | ------------------------------- | ---------- | --------------------------------------------- |
| children | ReactNode | — | The component the bar attaches to |
| verticalPosition | "top" \| "bottom" | "bottom" | Render bar above or below children |
| horizontalPosition | "left" \| "center" \| "right" | "right" | Horizontal alignment of the bar |
| showOn | "always" \| "hover" | "always" | Show bar always or only on hover |
| float | boolean | false | Absolute positioning — bar floats over layout |
All ActionBar props are also supported.
AI coding assistant prompt
If you are using Codex, Claude Code, Cursor, Windsurf, or another AI coding assistant, ask for:
Install react-ai-chat-actions and add message action buttons to my React chat UI.
Use ActionBar or ActionBarWrapper for like, dislike, copy, and regenerate actions.The assistant should install the package, render ActionBar near each AI message, and handle onAction(messageId, action). Styles are bundled and load automatically with the import.
Examples
Roadmap
- [ ] Label overrides / i18n
- [ ] Animations
- [ ] Overflow menu for narrow containers
License
MIT
