Response actions

Selectable subtle 44px icon controls for copying, reading aloud, rating through a combined feedback menu, and sharing an assistant response.

Act on an assistant response

AssistantComplete

The brief is ready for review. It names Mina as the owner, gives the next step a date, and links three supporting sources.

Four quiet controls keep the response in focus: copy, read aloud, rate, and share. The combined thumbs icon opens helpful and unhelpful choices; choose the checked item again to clear it.

The action bar composes subtle icon buttons from the library. Reading aloud uses browser speech when available. Feedback stays in this example, and sharing uses the browser share sheet or copies the response.

Use it for

  • Place in the actions slot of Response and supply the same plain text the reader sees.
  • actions chooses the built-in controls and their display and keyboard order: copy, read, feedback, share. The default includes all four; duplicates are ignored. Add custom controls as children.
  • Each action composes a subtle icon Button. Muted text turns to ink on hover while the surface stays transparent.
  • The combined feedback icon opens helpful and unhelpful choices. Selecting the checked choice again clears it.
  • Use onFeedback to persist positive, negative, or cleared feedback. A rejected promise keeps the previous selection available for retry.
  • Use feedback for controlled selection or defaultFeedback for an initial local selection.
  • Supply onShare for an application-owned share flow. Otherwise the browser shares the text or copies it when native sharing is unavailable.
  • Read aloud uses browser speech only after activation. onReadingChange can coordinate an avatar with narration. Removing read stops owned narration; removing feedback closes its menu.
  • CSS-only markup supplies named controls; clipboard, speech, the feedback menu, and sharing require React or application code.

Avoid

  • Adding copyText to the same Response, which would duplicate its copy control.
  • Assuming local feedback has been stored on a server without supplying onFeedback.
  • Passing raw markup as text or automatically starting narration when a message appears.

React package

npm install @noorddev/vlak-react

Load the stylesheet once at your app root, then import the components you use.

import "@noorddev/vlak-react/css";
import { Response, ResponseActions, type ResponseAction, type ResponseFeedback } from "@noorddev/vlak-react";

Per-component imports are available at @noorddev/vlak-react/components/response-actions.

Copy the source

npx @noorddev/vlak-cli add response-actions

Adds the component and its dependencies to components/vlak/. Follow the StyleX setup to compile the source in your application.

shadcn registry

npx shadcn add https://vlak.dev/r/response-actions.json

Installs the same source through the shadcn CLI.

"use client";
import { useState } from "react";
import { Response, ResponseActions, type ResponseAction, type ResponseFeedback } from "@noorddev/vlak-react";

const controls: readonly ResponseAction[] = ["copy", "read", "feedback", "share"];

export function AnswerActions() {
  const [feedback, setFeedback] = useState<ResponseFeedback>(null);
  const answer = "The brief now names one owner and one next step.";
  return <Response actions={<ResponseActions text={answer} actions={controls} feedback={feedback} onFeedback={setFeedback} />}>
    <p>{answer}</p>
  </Response>;
}

See the integration example to compose these components with your application's model and data.

ResponseActions

Compact message actions. Speech and sharing run only after a button activation.

PropTypeDefaultDescription
textrequiredstringPlain text used for copying, narration, and the default share action.
actionsreadonly ResponseAction[]["copy", "read", "feedback", "share"]Built-in controls in display and keyboard order. Omit for all four; duplicates are ignored.
feedbackResponseFeedback
defaultFeedbackResponseFeedbacknull
onFeedback(value: ResponseFeedback) => void | Promise<void>A rejected promise preserves the previous selection and allows retry.
onShare() => void | Promise<void>Overrides native sharing. Without it, unsupported browsers copy the text.
onReadingChange(reading: boolean) => voidReports this response's narration state for an avatar or other presentation.

Also accepts HTMLAttributes<HTMLDivElement>.

The ref reaches HTMLDivElement.

KeysDoes
TabMoves through the available copy, narration, feedback, and share controls
Enter, SpaceActivates the focused action or feedback choice; choosing the checked feedback item again clears it
Arrow Down, Arrow UpOpens feedback on the first or last choice, then moves between choices
Home, EndMoves to the first or last feedback choice
EscapeCloses the feedback menu and returns focus to its trigger
TabCloses the feedback menu and moves focus to the next action
  • Every icon button has a readable name and title, a 44px target, and a visible focus outline.
  • One Rate response trigger exposes its expanded menu state. Helpful and unhelpful menu choices expose aria-checked and remain mutually exclusive.
  • The menu moves focus between choices and returns focus on selection or Escape. Outside interaction dismisses it without stealing focus.
  • A polite, atomic status region announces completed actions and recoverable errors. Clipboard success is reported only after the write resolves.
  • Read aloud is disabled when browser speech is unavailable and becomes Stop reading while this response is narrated. Replacing the text or unmounting stops this component's narration.
  • Pending asynchronous actions prevent duplicate activation. The feedback trigger remains focusable with aria-disabled while saving. Replacing the response text closes its menu and invalidates stale results.
  • The group accepts native div attributes, children for extra actions, className, style, and a forwarded div ref.

The same styles are available as CSS. Native HTML provides the static presentation; React or application code supplies state updates and actions.

npm install @noorddev/vlak
import "@noorddev/vlak/css";
<div class="rs-response-actions-bar" role="group" aria-label="Response actions"><button class="rs-btn-subtle rs-btn-icon" type="button" aria-label="Copy response" title="Copy response"><svg class="rs-response-actions-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="8" y="8" width="12" height="13" rx="1"/><path d="M16 8V3H4v13h4"/></svg></button><button class="rs-btn-subtle rs-btn-icon" type="button" aria-label="Read aloud" title="Read aloud"><svg class="rs-response-actions-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="m11 4-6 5H2v6h3l6 5V4ZM15 8a6 6 0 0 1 0 8"/></svg></button><button class="rs-btn-subtle rs-btn-icon" type="button" aria-label="Rate response" title="Rate response" aria-haspopup="menu" aria-expanded="false"><svg class="rs-response-actions-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="M1 7h3v7H1zM4 7l3-5c1.5 0 2 1 1 4h4a1 1 0 0 1 1 1l-1.5 6a1 1 0 0 1-1 .8H4M23 17h-3v-7h3zM20 17l-3 5c-1.5 0-2-1-1-4h-4a1 1 0 0 1-1-1l1.5-6a1 1 0 0 1 1-.8H20"/></svg></button><button class="rs-btn-subtle rs-btn-icon" type="button" aria-label="Share response" title="Share response"><svg class="rs-response-actions-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path d="M12 16V2m-5 5 5-5 5 5M5 12H3v10h18V12h-2"/></svg></button><span class="rs-response-actions-status" role="status" aria-live="polite" aria-atomic="true"></span></div>

Classes

.rs-response-actions-bar.rs-response-actions-selected.rs-response-actions-icon.rs-response-actions-status.rs-response-actions-menu