QdsRowActions

The action affordance at the end of a table row: up to a few inline icon buttons, everything else behind an overflow menu.

Overview

The qds-row-actions component provides a compact set of actions for a single row in a table or a card footer. It renders a small number of inline icon buttons (visible on row hover or keyboard focus) and an overflow menu trigger for the remaining actions.

Hover-reveal rule

Inline actions are revealed on row hover and on focus-within, NOT always visible. A column of repeated icons down 200 rows is visual noise that competes with the data. The overflow trigger stays visible so the affordance is never hidden, which is what makes hover-reveal acceptable rather than a discoverability bug.

Reveal uses opacity only, never display: none, visibility: hidden, or aria-hidden. Elements with opacity: 0 remain focusable, which is exactly what focus-within reveal depends on, keyboard users get the same affordances as mouse users.

Destructive actions are never inline

Destructive actions are NEVER rendered inline, regardless of inline-count. A delete button one pixel from a duplicate button, revealed on hover, in a 200-row table, will destroy data. Destructive items sit below a divider at the end of the overflow menu and are coloured with --qds-row-actions-destructive-color (defaults to --theme-error). The red is reinforcement only, the label carries the meaning (“Delete”).

Coarse pointer

On coarse pointers (@media (pointer: coarse)) there is no hover, so inline actions are always visible and sized 44x44. This is set from the media query, not a user-agent check.

Confirmation gap

This component fires the qds-row-action event and stops. Something must confirm before a destructive action takes effect, the row disappears. There is no QDS dialog or confirm component at this time, that is a real gap and probably a work item of its own. Until then, consumers own confirmation, the README documents this so it is not forgotten.

Usage

<qds-row-actions row-id="row-1" inline-count="2"></qds-row-actions>
<script>
  const el = document.querySelector('qds-row-actions');
  el.actions = [
    { id: 'edit', label: 'Edit', icon: 'pi-pencil' },
    { id: 'duplicate', label: 'Duplicate', icon: 'pi-clone' },
    { id: 'export', label: 'Export', icon: 'pi-download' },
    { id: 'delete', label: 'Delete', icon: 'pi-trash', destructive: true }
  ];
  el.addEventListener('qds-row-action', (e) => {
    console.log('Action:', e.detail.action, 'Row:', e.detail.rowId);
  });
</script>

API Reference

Attributes

Properties

QdsRowAction shape

interface QdsRowAction {
  id: string;
  label: string;              // always required, it is the accessible name
  icon?: string;              // `prime:pi-*`, `lucide:*`, or legacy `pi-*`; shown in inline buttons and menu items
  disabled?: boolean;
  disabledReason?: string;    // shown as tooltip and appended to accessible name when disabled
  destructive?: boolean;      // never inline; sits below a divider at end of menu
  group?: string;             // one divider inserted between different groups, none at ends
}

CSS Parts

CSS Custom Properties

Events

Keyboard

Key Behaviour
Tab Inline buttons and the overflow trigger are each a tab stop
Enter / Space Activates the focused button; opens the menu on the trigger
ArrowDown / ArrowUp Moves through menu items when open, wrapping
Escape Closes the menu, returns focus to the trigger
Home / End First / last enabled menu item

Only one row’s menu is open at a time. Opening a second row’s menu closes the first.

Example

<qds-row-actions row-id="acme-1" inline-count="2" size="sm"></qds-row-actions>
<script>
  const el = document.querySelector('qds-row-actions');
  el.actions = [
    { id: 'edit', label: 'Edit', icon: 'pi-pencil' },
    { id: 'duplicate', label: 'Duplicate', icon: 'pi-clone' },
    { id: 'export', label: 'Export', icon: 'pi-download', group: 'file' },
    { id: 'archive', label: 'Archive', icon: 'pi-folder', group: 'file' },
    { id: 'delete', label: 'Delete', icon: 'pi-trash', destructive: true }
  ];
  el.addEventListener('qds-row-action', (e) => {
    if (e.detail.action === 'delete') {
      // Consumer owns confirmation, QDS has no dialog component yet
      if (confirm(`Delete row ${e.detail.rowId}?`)) {
        // proceed with deletion
      }
    }
  });
</script>