Skip to content
txbrendan/ui

Row Actions

beta

A row's HoverActions and their single-key shortcuts together. While visible, the bar shows and every key its tooltips teach works.

Preview

Point at a row, or Tab to it, then press R, E, A, or M. The keys work only on the row that is lit.

Last action: Nothing yet

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/row-actions

Use it for

  • Quick actions on list rows, with keys that work, like R for Reply.
  • Set visible while the row is hovered or holds focus. The keys work only then, so one key acts on one row.
  • It takes the same props as HoverActions.

Avoid it for

  • Leaving visible at its default true on every row of a list. Each row would then answer every key.
  • Actions with no keys. Use HoverActions.

Props

  • actions

    Required. The actions, in order. Each with a shortcut gets its key.

    HoverAction[]

  • visible

    Shows the bar and turns its keys on. Set it while the row is hovered or holds focus.

    boolean · default true

  • aria-label

    Names the toolbar.

    string · default "Actions"

  • ref

    Reaches the toolbar.

    Ref<HTMLDivElement>

  • className

    Extra classes on the toolbar. Later classes win.

    string

Accessibility

  • Everything HoverActions does: one Tab stop, arrow keys between buttons, a name per button, and the key in each tooltip.
  • A key presses its button, so it shows the same spinner, result, and Undo a click does, and screen readers hear the same message.
  • Each button with a single-key shortcut sets aria-keyshortcuts.
  • Keys are ignored while someone types, while a modifier is held, and inside an open dialog or menu.

Examples

On a list row

import { BellRing, 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>
      <RowActions
        visible={lit}
        aria-label={`Actions for ${name}`}
        className="absolute right-2 top-1/2 -translate-y-1/2"
        actions={[
          { icon: <Reply />, label: "Reply", shortcut: "R" },
          { icon: <Pencil />, label: "Edit", shortcut: "E" },
          { icon: <BellRing />, label: "Remind", shortcut: "M", result: { label: "Reminder sent", doneLabel: "Reminded" } },
        ]}
      />
    </div>
  );
}