Context Menu

Action menu that opens where its target is context-clicked: right-click, touch long-press, or Shift+F10 / the Menu key on the focused target. A plain click does nothing; for a button that opens a menu, use Menu.

Target binding

The for property binds the menu to a context target. Right-click or long-press it, or focus it and press Shift+F10. A plain click does nothing.

Right-click this area

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-context-menu/ui-context-menu.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-menu-item/ui-menu-item.html"
>

<div
  id="context-menu-target"
  tabindex="0"
  style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
> Right-click this area </div>
<ui-context-menu
  for="context-menu-target"
>
  <ui-menu-item
    value="first"
  >First item</ui-menu-item>
  <ui-menu-item
    value="second"
  >Second item</ui-menu-item>
</ui-context-menu>
<script setup>
  import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";
</script>

<template>
  <div
    id="context-menu-target"
    tabindex="0"
    style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
  > Right-click this area </div>
  <UiContextMenu
    for="context-menu-target"
  >
    <UiMenuItem
      value="first"
    >First item</UiMenuItem>
    <UiMenuItem
      value="second"
    >Second item</UiMenuItem>
  </UiContextMenu>
</template>
import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";

export default function Example() {
  return <>
    <div
      id="context-menu-target"
      tabIndex={0}
      style={{"display":"grid","placeItems":"center","blockSize":"8rem","border":"1px dashed var(--ui-border-strong)","borderRadius":"var(--ui-radius-lg)","color":"var(--ui-text-secondary)","userSelect":"none","WebkitUserSelect":"none","WebkitTouchCallout":"none"}}
    >{" Right-click this area "}</div>
    <UiContextMenu
      htmlFor="context-menu-target"
    >
      <UiMenuItem
        value="first"
      >First item</UiMenuItem>
      <UiMenuItem
        value="second"
      >Second item</UiMenuItem>
    </UiContextMenu>
  </>;
}
/** @jsxImportSource preact */
import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";

export default function Example() {
  return <>
    <div
      id="context-menu-target"
      tabIndex={0}
      style={{"display":"grid","placeItems":"center","blockSize":"8rem","border":"1px dashed var(--ui-border-strong)","borderRadius":"var(--ui-radius-lg)","color":"var(--ui-text-secondary)","userSelect":"none","WebkitUserSelect":"none","WebkitTouchCallout":"none"}}
    >{" Right-click this area "}</div>
    <UiContextMenu
      htmlFor="context-menu-target"
    >
      <UiMenuItem
        value="first"
      >First item</UiMenuItem>
      <UiMenuItem
        value="second"
      >Second item</UiMenuItem>
    </UiContextMenu>
  </>;
}
<script>
  import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";
</script>

<div
  id="context-menu-target"
  tabindex="0"
  style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
> Right-click this area </div>
<UiContextMenu
  for="context-menu-target"
>
  <UiMenuItem
    value="first"
  >First item</UiMenuItem>
  <UiMenuItem
    value="second"
  >Second item</UiMenuItem>
</UiContextMenu>
import { Component } from "@angular/core";
import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiContextMenu, UiMenuItem],
  template: `
    <div
      id="context-menu-target"
      tabindex="0"
      style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
    > Right-click this area </div>
    <span
      ui-context-menu
      [for]="'context-menu-target'"
    >
      <ng-template
        ui-menu-item
        [value]="'first'"
      >First item</ng-template>
      <ng-template
        ui-menu-item
        [value]="'second'"
      >Second item</ng-template>
    </span>
  `,
})
export default class Example {}

Disabled items

Right-click the target to open the menu. A disabled item is announced and shown but cannot be chosen, and arrow keys skip it.

Right-click this area

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-context-menu/ui-context-menu.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-menu-item/ui-menu-item.html"
>

<div
  id="context-menu-disabled-target"
  tabindex="0"
  style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
> Right-click this area </div>
<ui-context-menu
  for="context-menu-disabled-target"
>
  <ui-menu-item
    value="rename"
  >Rename</ui-menu-item>
  <ui-menu-item
    value="delete"
    disabled
  >Delete (locked)</ui-menu-item>
  <ui-menu-item
    value="duplicate"
  >Duplicate</ui-menu-item>
</ui-context-menu>
<script setup>
  import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";
</script>

<template>
  <div
    id="context-menu-disabled-target"
    tabindex="0"
    style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
  > Right-click this area </div>
  <UiContextMenu
    for="context-menu-disabled-target"
  >
    <UiMenuItem
      value="rename"
    >Rename</UiMenuItem>
    <UiMenuItem
      value="delete"
      :disabled="true"
    >Delete (locked)</UiMenuItem>
    <UiMenuItem
      value="duplicate"
    >Duplicate</UiMenuItem>
  </UiContextMenu>
</template>
import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";

export default function Example() {
  return <>
    <div
      id="context-menu-disabled-target"
      tabIndex={0}
      style={{"display":"grid","placeItems":"center","blockSize":"8rem","border":"1px dashed var(--ui-border-strong)","borderRadius":"var(--ui-radius-lg)","color":"var(--ui-text-secondary)","userSelect":"none","WebkitUserSelect":"none","WebkitTouchCallout":"none"}}
    >{" Right-click this area "}</div>
    <UiContextMenu
      htmlFor="context-menu-disabled-target"
    >
      <UiMenuItem
        value="rename"
      >Rename</UiMenuItem>
      <UiMenuItem
        value="delete"
        disabled
      >Delete (locked)</UiMenuItem>
      <UiMenuItem
        value="duplicate"
      >Duplicate</UiMenuItem>
    </UiContextMenu>
  </>;
}
/** @jsxImportSource preact */
import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";

export default function Example() {
  return <>
    <div
      id="context-menu-disabled-target"
      tabIndex={0}
      style={{"display":"grid","placeItems":"center","blockSize":"8rem","border":"1px dashed var(--ui-border-strong)","borderRadius":"var(--ui-radius-lg)","color":"var(--ui-text-secondary)","userSelect":"none","WebkitUserSelect":"none","WebkitTouchCallout":"none"}}
    >{" Right-click this area "}</div>
    <UiContextMenu
      htmlFor="context-menu-disabled-target"
    >
      <UiMenuItem
        value="rename"
      >Rename</UiMenuItem>
      <UiMenuItem
        value="delete"
        disabled
      >Delete (locked)</UiMenuItem>
      <UiMenuItem
        value="duplicate"
      >Duplicate</UiMenuItem>
    </UiContextMenu>
  </>;
}
<script>
  import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";
</script>

<div
  id="context-menu-disabled-target"
  tabindex="0"
  style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
> Right-click this area </div>
<UiContextMenu
  for="context-menu-disabled-target"
>
  <UiMenuItem
    value="rename"
  >Rename</UiMenuItem>
  <UiMenuItem
    value="delete"
    disabled
  >Delete (locked)</UiMenuItem>
  <UiMenuItem
    value="duplicate"
  >Duplicate</UiMenuItem>
</UiContextMenu>
import { Component } from "@angular/core";
import { UiContextMenu, UiMenuItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiContextMenu, UiMenuItem],
  template: `
    <div
      id="context-menu-disabled-target"
      tabindex="0"
      style="display: grid; place-items: center; block-size: 8rem; border: 1px dashed var(--ui-border-strong); border-radius: var(--ui-radius-lg); color: var(--ui-text-secondary); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none"
    > Right-click this area </div>
    <span
      ui-context-menu
      [for]="'context-menu-disabled-target'"
    >
      <ng-template
        ui-menu-item
        [value]="'rename'"
      >Rename</ng-template>
      <ng-template
        ui-menu-item
        [value]="'delete'"
        [disabled]="true"
      >Delete (locked)</ng-template>
      <ng-template
        ui-menu-item
        [value]="'duplicate'"
      >Duplicate</ng-template>
    </span>
  `,
})
export default class Example {}

Checkable context choices

Right-click or long-press the button, or focus it and press Shift+F10. Checkboxes and radio choices show their state before and after a change; choosing one leaves the menu open.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-context-menu/ui-context-menu.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-menu-item/ui-menu-item.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-menu-group/ui-menu-group.html"
>

<ui-button
  id="view-target"
  type="button"
>Right-click for view options</ui-button>
<ui-context-menu
  for="view-target"
>
  <ui-menu-item
    type="checkbox"
    value="grid"
    checked
  >Show grid</ui-menu-item>
  <ui-menu-group
    label="Density"
  >
    <ui-menu-item
      type="radio"
      value="comfortable"
      checked
    >Comfortable</ui-menu-item>
    <ui-menu-item
      type="radio"
      value="compact"
    >Compact</ui-menu-item>
  </ui-menu-group>
</ui-context-menu>
<script setup>
  import { UiButton, UiContextMenu, UiMenuGroup, UiMenuItem } from "@threadlabs/looma";
</script>

<template>
  <UiButton
    id="view-target"
    type="button"
  >Right-click for view options</UiButton>
  <UiContextMenu
    for="view-target"
  >
    <UiMenuItem
      type="checkbox"
      value="grid"
      :checked="true"
    >Show grid</UiMenuItem>
    <UiMenuGroup
      label="Density"
    >
      <UiMenuItem
        type="radio"
        value="comfortable"
        :checked="true"
      >Comfortable</UiMenuItem>
      <UiMenuItem
        type="radio"
        value="compact"
      >Compact</UiMenuItem>
    </UiMenuGroup>
  </UiContextMenu>
</template>
import { UiButton, UiContextMenu, UiMenuGroup, UiMenuItem } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="view-target"
      type="button"
    >Right-click for view options</UiButton>
    <UiContextMenu
      htmlFor="view-target"
    >
      <UiMenuItem
        type="checkbox"
        value="grid"
        checked
      >Show grid</UiMenuItem>
      <UiMenuGroup
        label="Density"
      >
        <UiMenuItem
          type="radio"
          value="comfortable"
          checked
        >Comfortable</UiMenuItem>
        <UiMenuItem
          type="radio"
          value="compact"
        >Compact</UiMenuItem>
      </UiMenuGroup>
    </UiContextMenu>
  </>;
}
/** @jsxImportSource preact */
import { UiButton, UiContextMenu, UiMenuGroup, UiMenuItem } from "@threadlabs/looma";

export default function Example() {
  return <>
    <UiButton
      id="view-target"
      type="button"
    >Right-click for view options</UiButton>
    <UiContextMenu
      htmlFor="view-target"
    >
      <UiMenuItem
        type="checkbox"
        value="grid"
        checked
      >Show grid</UiMenuItem>
      <UiMenuGroup
        label="Density"
      >
        <UiMenuItem
          type="radio"
          value="comfortable"
          checked
        >Comfortable</UiMenuItem>
        <UiMenuItem
          type="radio"
          value="compact"
        >Compact</UiMenuItem>
      </UiMenuGroup>
    </UiContextMenu>
  </>;
}
<script>
  import { UiButton, UiContextMenu, UiMenuGroup, UiMenuItem } from "@threadlabs/looma";
</script>

<UiButton
  id="view-target"
  type="button"
>Right-click for view options</UiButton>
<UiContextMenu
  for="view-target"
>
  <UiMenuItem
    type="checkbox"
    value="grid"
    checked
  >Show grid</UiMenuItem>
  <UiMenuGroup
    label="Density"
  >
    <UiMenuItem
      type="radio"
      value="comfortable"
      checked
    >Comfortable</UiMenuItem>
    <UiMenuItem
      type="radio"
      value="compact"
    >Compact</UiMenuItem>
  </UiMenuGroup>
</UiContextMenu>
import { Component } from "@angular/core";
import { UiButton, UiContextMenu, UiMenuGroup, UiMenuItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiButton, UiContextMenu, UiMenuItem, UiMenuGroup],
  template: `
    <ng-template
      ui-button
      [type]="'button'"
      [hnAttributes]="{ id: 'view-target' }"
    >Right-click for view options</ng-template>
    <span
      ui-context-menu
      [for]="'view-target'"
    >
      <ng-template
        ui-menu-item
        [type]="'checkbox'"
        [value]="'grid'"
        [checked]="true"
      >Show grid</ng-template>
      <div
        ui-menu-group
        [label]="'Density'"
      >
        <ng-template
          ui-menu-item
          [type]="'radio'"
          [value]="'comfortable'"
          [checked]="true"
        >Comfortable</ng-template>
        <ng-template
          ui-menu-item
          [type]="'radio'"
          [value]="'compact'"
        >Compact</ng-template>
      </div>
    </span>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: span

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
forID of the element whose context click (right-click, long-press, Shift+F10, or the Menu key) opens the menu.forstring—
openWhether the context menu is open initially or when set externally.openbooleanfalse—

Framework props

NameTypeDefaultAllowed values
forstring—
openbooleanfalse—

Events

NameDetailDescription
closeobject
  • open: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted when the component transitions to a closed state.
openobject
  • open: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted when the component transitions to an open state.
selectobject
  • value: string (required)
  • checked: boolean
  • trigger: keyword (required)
Emitted when a selectable option becomes active.

Slots

NamePurpose
defaultDefault child content.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-context-menu-border—var(--ui-overlay-border, var(--ui-border-strong))
--ui-context-menu-radius—var(--ui-overlay-radius, var(--ui-radius-lg))
--ui-context-menu-shadow—var(--ui-overlay-shadow, var(--ui-elevation-overlay, var(--ui-shadow-lg)))
--ui-context-menu-surface—var(--ui-overlay-surface, var(--ui-surface-elevated))
--ui-context-menu-z-index—1000

Shared tokens

TokenFallback
--ui-border-divider—
--ui-border-strong—
--ui-border-width—
--ui-elevation-overlayvar(--ui-shadow-lg)
--ui-motion-ease—
--ui-motion-fast—
--ui-overlay-bordervar(--ui-border-strong)
--ui-overlay-radiusvar(--ui-radius-lg)
--ui-overlay-shadowvar(--ui-elevation-overlay, var(--ui-shadow-lg))
--ui-overlay-surfacevar(--ui-surface-elevated)
--ui-radius-lg—
--ui-shadow-lg—
--ui-space-1—
--ui-surface-elevated—