Actions

The quiet icon-action row under assistant replies — type-enforced accessible names, per-action tooltips, and an opt-in reveal-on-hover inside Message.

<Actions>  <TooltipProvider>    <Tooltip>      <TooltipTrigger asChild>        <Clipboard          value={reply}          aria-label="Copy message"          variant="ghost"          size="xs"          className="text-fg-muted hover:text-fg-default"        />      </TooltipTrigger>      <TooltipContent>Copy message</TooltipContent>    </Tooltip>  </TooltipProvider>  <Action tooltip="Regenerate response">    <ArrowClockwise />  </Action>  <Action tooltip="Good response">    <ThumbsUp />  </Action>  <Action tooltip="Poor response">    <ThumbsDown />  </Action></Actions>

Usage

import { Action, Actions, actionsRevealOnHoverClasses } from "@reva/ui";

Actions is the row of icon-only controls under an assistant reply — copy, regenerate, vote. The container bakes in no specific actions; the consumer composes Actions. Each one is the house IconButton (ghost / xs) with muted ink that sharpens on hover, so the row reads as quiet chrome under the message rather than a toolbar. Children are the Phosphor icon.

An Action is icon-only, so it must carry an accessible name — enforced at the type level: pass tooltip (which titles the control and doubles as the default aria-label — a tooltip describes, it does not name) or an explicit aria-label. The props union makes "neither" a type error.

Each tooltipped Action wraps its own TooltipProvider: the house Tooltip requires a provider ancestor (the Radix provider context has no default), so the per-action provider makes the bar zero-setup — and apps with an app-level provider just get the inner one's house-default instant delay.

The copy control composes as a sibling. The house Clipboard renders its own icon-button-shaped control, so a copy action sits as a sibling of Actions — never nested inside one, which would nest buttons:

<Clipboard
  value={text}
  aria-label="Copy message"
  variant="ghost"
  size="xs"
  className="text-fg-muted hover:text-fg-default"
/>

The className matches Action's quiet ink — a bare ghost renders text-neutral-fg-default, which would read warmer than its neighbours. Clipboard has no tooltip of its own; wrap it in the house Tooltip if the row's other actions are tooltipped (as the hero does).

Disabled actions can't show their tooltip

Button's disabled styling uses pointer-events-none, so a disabled Action never receives the hover that would open its tooltip — the accessible name survives, the visual hint doesn't. If users need to learn why a control is off, surface that outside the tooltip.

Composition

Use the following composition to build an Actions bar:

Actions          (the quiet bar — flex row, 4px gap)
├── Clipboard    (copy — a sibling control, Tooltip-wrapped to match the row)
└── Action (× n) (IconButton ghost/xs — tooltip or aria-label required)

Examples

Reveal on message hover

actionsRevealOnHoverClasses is the opt-in: inside a Message — whose root carries the named group the classes pair with — the bar rests hidden and reveals on row hover, or when focus lands anywhere inside the message, so keyboard users tabbing onto the actions reveal them too. Two always-visible escapes: touch devices (no hover to reveal with) and reduced motion.

It is deliberately not the default: outside a Message there is no group ancestor, and a baked-in opacity-0 would leave a standalone bar permanently invisible.

The Hargreaves ISA is 8% over its equity target — a £14,200 trim brings it back in line, tax-free inside the wrapper.
<Message from="assistant">  <MessageContent>{reply}</MessageContent>  <Actions className={actionsRevealOnHoverClasses}>…</Actions></Message>

Accessibility

  • Accessible names are enforced at the type level — tooltip or aria-label, "neither" doesn't compile — and the tooltip string doubles as the default name so the two never drift apart.
  • The reveal opt-in is keyboard- and touch-safe: focus anywhere inside the message reveals the bar, and touch or reduced-motion users see it always.
  • The reveal is a pure CSS opacity transition, kept instant under reduced motion.

Props

ComponentPropTypeDefaultDescription
Actionsdiv propsThe bar — a flex row with a 4px gap; compose Actions and the copy control inside.
ActiontooltipstringTitles the control and doubles as the default accessible name. One of tooltip / aria-label is required (type-enforced).
Actionaria-labelstringthe tooltip stringExplicit accessible name — required when tooltip is omitted.
Actionvariant / sizeIconButton variants"ghost" / "xs"Pass through to the underlying IconButton.
ActionchildrenReactNodeThe Phosphor icon.

actionsRevealOnHoverClasses is exported alongside the components — a class string passed via className to opt an in-Message bar into the reveal behaviour.

On this page