# Context Menu

Apply `uiContextMenu` to any host element and pass `uiContextMenuItems` or `uiContextMenuSections`. The directive anchors the overlay to the cursor, closes on outside interaction or Escape, and emits the selected action through `contextMenuItemClick`.

## Import
```ts
import { ContextMenuDirective, type MenuItem, type MenuSection } from 'ui';
```

## Basic right-click surface
```ts
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
import { ButtonComponent, ContextMenuDirective, type MenuItem } from 'ui';

@Component({
  selector: 'app-context-menu-basic-demo',
  standalone: true,
  imports: [ButtonComponent, ContextMenuDirective],
  changeDetection: ChangeDetectionStrategy.Eager,
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:42rem">
      <div
        style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        <ui-button variant="secondary" appearance="outline" (click)="reset()">Reset</ui-button>
        <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
          Last action: <strong>{{ lastAction() || 'none' }}</strong>
        </span>
      </div>

      <div
        uiContextMenu
        [uiContextMenuItems]="menuItems"
        uiContextMenuAriaLabel="Canvas actions"
        (contextMenuItemClick)="onAction($event)"
        style="display:flex;flex-direction:column;gap:0.5rem;justify-content:center;min-height:10rem;padding:1.25rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest);cursor:context-menu"
      >
        <div style="font-size:0.9375rem;font-weight:600">Design canvas</div>
        <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
          Right-click anywhere in this surface to open the context menu.
        </div>
      </div>
    </div>
  `,
})
export class ContextMenuBasicDemoComponent {
  protected readonly lastAction = signal('');

  protected readonly menuItems: MenuItem[] = [
    { id: 'open', label: 'Open', icon: 'open' },
    { id: 'duplicate', label: 'Duplicate', icon: 'document_copy' },
    { id: 'rename', label: 'Rename', icon: 'edit' },
    { id: 'archive', label: 'Archive', icon: 'archive' },
  ];

  protected onAction(item: MenuItem): void {
    this.lastAction.set(item.label);
  }

  protected reset(): void {
    this.lastAction.set('');
  }
}
```

## Sections and shortcuts
```ts
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
import { ContextMenuDirective, type MenuItem, type MenuSection } from 'ui';

interface WorkspaceRow {
  id: string;
  label: string;
  meta: string;
  sections: MenuSection[];
}

@Component({
  selector: 'app-context-menu-sections-demo',
  standalone: true,
  imports: [ContextMenuDirective],
  changeDetection: ChangeDetectionStrategy.Eager,
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:46rem">
      <div
        style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest);padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        Last action: <strong>{{ lastAction() || 'none' }}</strong>
      </div>

      <div
        style="display:flex;flex-direction:column;gap:0.75rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        @for (row of rows; track row.id) {
          <div
            uiContextMenu
            [uiContextMenuSections]="row.sections"
            (contextMenuItemClick)="onAction($event, row.label)"
            style="display:flex;flex-direction:column;gap:0.1875rem;padding:0.875rem 1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest);cursor:context-menu"
          >
            <div style="font-weight:600">{{ row.label }}</div>
            <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
              {{ row.meta }}
            </div>
          </div>
        }
      </div>
    </div>
  `,
})
export class ContextMenuSectionsDemoComponent {
  protected readonly lastAction = signal('');

  protected readonly rows: WorkspaceRow[] = [
    {
      id: 'brief',
      label: 'Creative brief',
      meta: 'Updated 10 minutes ago',
      sections: [
        {
          header: 'File',
          items: [
            { id: 'open', label: 'Open', icon: 'open', shortcut: 'Enter' },
            { id: 'duplicate', label: 'Duplicate', icon: 'document_copy', shortcut: 'Ctrl+D' },
          ],
          divider: true,
        },
        {
          header: 'Workflow',
          items: [
            { id: 'share', label: 'Share', icon: 'share' },
            { id: 'archive', label: 'Archive', icon: 'archive' },
          ],
        },
      ],
    },
    {
      id: 'assets',
      label: 'Campaign assets',
      meta: '5 files waiting for approval',
      sections: [
        {
          items: [
            { id: 'preview', label: 'Preview', icon: 'eye' },
            { id: 'download', label: 'Download', icon: 'arrow_download' },
            { id: 'delete', label: 'Delete', icon: 'delete', disabled: true },
          ] as MenuItem[],
        },
      ],
    },
  ];

  protected onAction(item: MenuItem, label: string): void {
    this.lastAction.set(`${item.label} on ${label}`);
  }
}
```

## Nested submenu branches
```ts
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
import { ContextMenuDirective, type MenuItem } from 'ui';

@Component({
  selector: 'app-context-menu-submenu-demo',
  standalone: true,
  imports: [ContextMenuDirective],
  changeDetection: ChangeDetectionStrategy.Eager,
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:42rem">
      <div
        style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest);padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        Last action: <strong>{{ lastAction() || 'none' }}</strong>
      </div>

      <div
        uiContextMenu
        [uiContextMenuItems]="menuItems"
        uiContextMenuAriaLabel="Document actions"
        (contextMenuItemClick)="onAction($event)"
        style="display:flex;flex-direction:column;gap:0.5rem;justify-content:center;min-height:10rem;padding:1.25rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest);cursor:context-menu"
      >
        <div style="font-size:0.9375rem;font-weight:600">Quarterly report.docx</div>
        <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
          Right-click to open export and share branches.
        </div>
      </div>
    </div>
  `,
})
export class ContextMenuSubmenuDemoComponent {
  protected readonly lastAction = signal('');

  protected readonly menuItems: MenuItem[] = [
    { id: 'open', label: 'Open', icon: 'open' },
    {
      id: 'export',
      label: 'Export as',
      icon: 'arrow_export',
      submenuItems: [
        { id: 'pdf', label: 'PDF', icon: 'document_pdf' },
        { id: 'docx', label: 'Word', icon: 'document' },
        { id: 'csv', label: 'CSV', icon: 'table' },
      ],
    },
    {
      id: 'share',
      label: 'Share',
      icon: 'share',
      submenuItems: [
        { id: 'link', label: 'Copy link', icon: 'link' },
        { id: 'invite', label: 'Invite people', icon: 'person_add' },
      ],
    },
    { id: 'delete', label: 'Delete', icon: 'delete', variant: 'danger' },
  ];

  protected onAction(item: MenuItem): void {
    this.lastAction.set(item.label);
  }
}
```

## Accessibility

### Pointer and keyboard behavior
The menu opens on the native `contextmenu` event and prevents the browser menu when actions are configured. By default, context menu does not show a focus ring on the first row after a right-click. Keyboard navigation still works: arrow keys move through items, Enter activates, and Escape dismisses.

### Naming and structure
Provide `uiContextMenuAriaLabel` when multiple context menus on the page need distinct accessible names. Keep action lists short and mirror the labels users already expect from row overflow menus.

### Context menu versus overflow menu
Use `ui-menu` when the action entry point should stay visible, such as a kebab button in a dense table row. Use `uiContextMenu` when right-click is the primary or secondary affordance on a larger surface.
