# Hover Actions

> A row's quick icon actions in a small glass toolbar that sits on the row and springs in while the row is hovered or holds focus. A small tooltip above each icon says what it does.

- Name: `hover-actions`
- Status: beta
- Categories: actions
- File: the install writes `hover-actions.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/tool-button`, `@arbokui/tooltip`. The install adds them.
- Gallery: https://arbokui.com/components/hover-actions
- Registry item: https://arbokui.com/r/hover-actions.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/hover-actions
   ```

## Use it for

- The quick jobs on a list row, like Reply, Edit, Archive, or Move, so people finish them without leaving the page.
- Keep two to four actions. Put the rest in a Menu.
- Set visible while the row is hovered or holds focus. Focus inside the bar shows it too, so keyboard users always reach it.
- Give each action a shortcut, and use RowActions to make the keys work. The tooltip teaches each one.
- Use icons. Each icon's tooltip says what it does, so the bar stays small and never covers the row.
- Give an action a result when its press changes something, like an archive. The button says what happened, with Undo.
- Place it yourself, usually absolute at the row's right edge, inside a row with position: relative.

## Avoid it for

- The only way to do something on touch screens. Touch has no hover, so keep the bar visible there, or offer the action in the row's own page.
- A row's main action, like opening it. Make the row itself the link or button.
- A long bar of actions. Use a Menu for more than four.
- Words in a row's bar, like a text Remind button. They crowd the row. Let the tooltip say the word.

## Props

- `actions`: `HoverAction[]`. Required. The actions, in order. Pass the same objects to RowActions and ActionKeys, so the key, the tooltip, and the button never drift apart.
- `HoverAction`: `{ label: string; icon?: ReactNode; onClick?: () => void | Promise<unknown>; shortcut?: string; text?: boolean; tone?: "neutral" | "danger"; result?: ActionResult; disabled?: boolean }`. Type. label names the button. shortcut is one key, taught in the tooltip. text shows the label beside the icon. A promise from onClick shows a spinner, and a failure shows Retry. result is ToolButton's ActionResult.
- `visible`: `boolean`. Default `true`. Shows the bar. Set it while the row is hovered or holds focus. Hidden, the bar stays in the Tab order, and focus inside shows it.
- `aria-label`: `string`. Default `"Actions"`. Names the toolbar. Name the row, like 'Actions for Tom Hale', when a list has many.
- `ref`: `Ref<HTMLDivElement>`. Reaches the toolbar. ActionKeys takes it as within.
- `className`: `string`. Extra classes on the toolbar. Later classes win.

## Accessibility

- The bar is a toolbar: one Tab stop, and the arrow keys move between its buttons.
- Each button is named by its action's label. The tooltip shows the label and the key to sighted mouse and keyboard users.
- A hidden bar is only see-through. It stays in the Tab order and the accessibility tree, and focus inside shows it at once.
- A hidden bar ignores the pointer, so a press never lands on a button no one can see.
- Results are said once through a polite live region, and each button's name follows its state.

## Examples

### On a list row

```tsx
import { Archive, Pencil, Reply } from "lucide-react";
import { useState } from "react";

function PersonRow({ name }: { name: string }) {
  const [lit, setLit] = useState(false);
  return (
    <div
      className="relative flex h-11 items-center px-4"
      onMouseEnter={() => setLit(true)}
      onMouseLeave={() => setLit(false)}
      onFocus={() => setLit(true)}
      onBlur={(event) => !event.currentTarget.contains(event.relatedTarget) && setLit(false)}
    >
      <a href="#">{name}</a>
      <HoverActions
        visible={lit}
        aria-label={`Actions for ${name}`}
        className="absolute right-4 top-1/2 -translate-y-1/2"
        actions={[
          { icon: <Reply />, label: "Reply", shortcut: "R" },
          { icon: <Pencil />, label: "Edit", shortcut: "E" },
          { icon: <Archive />, label: "Archive", shortcut: "A", result: { label: "Archived", onUndo: () => {} } },
        ]}
      />
    </div>
  );
}
```
