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.
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.
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
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
for | ID of the element whose context click (right-click, long-press, Shift+F10, or the Menu key) opens the menu. | for | string | | — |
open | Whether the context menu is open initially or when set externally. | open | boolean | false | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
for | string | | — |
open | boolean | false | — |
Events
| Name | Detail | Description |
|---|---|---|
close | object
| Emitted when the component transitions to a closed state. |
open | object
| Emitted when the component transitions to an open state. |
select | object
| Emitted when a selectable option becomes active. |
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--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
| Token | Fallback |
|---|---|
--ui-border-divider | — |
--ui-border-strong | — |
--ui-border-width | — |
--ui-elevation-overlay | var(--ui-shadow-lg) |
--ui-motion-ease | — |
--ui-motion-fast | — |
--ui-overlay-border | var(--ui-border-strong) |
--ui-overlay-radius | var(--ui-radius-lg) |
--ui-overlay-shadow | var(--ui-elevation-overlay, var(--ui-shadow-lg)) |
--ui-overlay-surface | var(--ui-surface-elevated) |
--ui-radius-lg | — |
--ui-shadow-lg | — |
--ui-space-1 | — |
--ui-surface-elevated | — |