メニュー
概要
メニューは、ユーザーにアクションやオプションのリストを提供し、通常はボタンのクリックや右クリックに応じて表示されます。メニューは、矢印キーによるキーボードナビゲーション、サブメニュー、チェックボックス、ラジオボタン、無効化されたアイテムをサポートしています。
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </ng-template> </div> </ng-template> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem value="Archive"> <span class="icon material-symbols-outlined" translate="no">archive</span> <span class="label">Archive</span> </div> <div ngMenuItem value="Report spam"> <span class="icon material-symbols-outlined" translate="no">report</span> <span class="label">Report spam</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font); --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; border-radius: 0.5rem; color: var(--primary-contrast); border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { margin: 0; width: 15rem; padding: 0.25rem; border-radius: 0.5rem; border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem; border-radius: 0.25rem;}[ngMenuTrigger]:hover,[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuItem]:focus,[ngMenuTrigger]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--border-color); margin: 0.25rem 0; opacity: 0.25;}
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div class="group"> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </div> <div class="group"> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div class="group"> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </div> </ng-template> </div> </ng-template> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; border-radius: 0.5rem; border: 1px solid transparent; background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent); color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { gap: 3px; width: 15rem; display: flex; flex-direction: column;}[ngMenu] .group { padding: 0.25rem; border-radius: 0.25rem; background-color: var(--page-background); box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);}[ngMenu] .group:first-of-type { border-top-left-radius: 1rem; border-top-right-radius: 1rem;}[ngMenu] .group:last-of-type { border-bottom-left-radius: 1rem; border-bottom-right-radius: 1rem;}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.75rem; font-size: 0.875rem; border-radius: 0.75rem;}[ngMenuTrigger]:hover,[ngMenuTrigger][aria-expanded='true'] { background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);}[ngMenuItem][data-active='true'] { color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast)); background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);}[ngMenuItem]:focus,[ngMenuTrigger]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--gray-500); margin: 0.25rem 0; opacity: 0.25;}
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()"> Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </ng-template> </div> </ng-template> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem value="Archive"> <span class="icon material-symbols-outlined" translate="no">archive</span> <span class="label">Archive</span> </div> <div ngMenuItem value="Report spam"> <span class="icon material-symbols-outlined" translate="no">report</span> <span class="label">Report spam</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-size: 0.8rem; --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background)); font-family: 'Press Start 2P'; --retro-button-color: var(--vivid-pink); --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; font-family: 'Press Start 2P'; color: #000; background-color: var(--vivid-pink); box-shadow: var(--retro-clickable-shadow); transition: transform 0.1s, box-shadow 0.1s;}[ngMenuTrigger]:hover { transform: translate(1px, 1px);}[ngMenuTrigger]:active { background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff); box-shadow: var(--retro-pressed-shadow); transform: translate(4px, 4px);}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { margin-top: 8px; width: 15rem; padding: 0.25rem; background-color: var(--page-background); box-shadow: var(--retro-flat-shadow);}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem;}[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuTrigger]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: 8px;}[ngMenuItem]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: -4px;}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 4px solid #000; margin: 0.25rem 0; opacity: 0.25;}
使い方
メニューは、ユーザーが選択できるアクションやコマンドのリストを提示するのに適しています。
メニューを使用する場合:
- アプリケーションのコマンドメニュー(File、Edit、View)を構築する場合
- コンテキストメニュー(右クリックアクション)を作成する場合
- ドロップダウンのアクションリストを表示する場合
- ツールバーのドロップダウンを実装する場合
- 設定やオプションを整理する場合
メニューを避けるべき場合:
- サイトナビゲーションを構築する場合(代わりにナビゲーションランドマークを使用してください)
- フォームのセレクトを作成する場合(Selectコンポーネントを使用してください)
- コンテンツパネルを切り替える場合(Tabsを使用してください)
- 折りたたみ可能なコンテンツを表示する場合(Accordionを使用してください)
機能
- キーボードナビゲーション - 矢印キー、Home/End、文字検索による効率的なナビゲーション
- サブメニュー - 自動配置の機能を備えたネストされたメニューのサポート
- メニュータイプ - スタンドアロンメニュー、トリガーメニュー、メニューバー
- チェックボックスとラジオボタン - トグルおよび選択メニューアイテム
- 無効化されたアイテム - フォーカス管理を備えたソフトまたはハードな無効状態
- 自動クローズ動作 - 選択時に閉じる設定が可能
- RTLサポート - 右から左へ記述する言語のナビゲーション
例
トリガー付きメニュー
トリガーボタンとメニューを組み合わせることで、ドロップダウンメニューを作成します。トリガーはメニューを開閉します。
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </ng-template> </div> </ng-template> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem value="Archive"> <span class="icon material-symbols-outlined" translate="no">archive</span> <span class="label">Archive</span> </div> <div ngMenuItem value="Report spam"> <span class="icon material-symbols-outlined" translate="no">report</span> <span class="label">Report spam</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font); --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; border-radius: 0.5rem; color: var(--primary-contrast); border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { margin: 0; width: 15rem; padding: 0.25rem; border-radius: 0.5rem; border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem; border-radius: 0.25rem;}[ngMenuTrigger]:hover,[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuItem]:focus,[ngMenuTrigger]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--border-color); margin: 0.25rem 0; opacity: 0.25;}
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div class="group"> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </div> <div class="group"> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div class="group"> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </div> </ng-template> </div> </ng-template> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; border-radius: 0.5rem; border: 1px solid transparent; background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent); color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { gap: 3px; width: 15rem; display: flex; flex-direction: column;}[ngMenu] .group { padding: 0.25rem; border-radius: 0.25rem; background-color: var(--page-background); box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);}[ngMenu] .group:first-of-type { border-top-left-radius: 1rem; border-top-right-radius: 1rem;}[ngMenu] .group:last-of-type { border-bottom-left-radius: 1rem; border-bottom-right-radius: 1rem;}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.75rem; font-size: 0.875rem; border-radius: 0.75rem;}[ngMenuTrigger]:hover,[ngMenuTrigger][aria-expanded='true'] { background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);}[ngMenuItem][data-active='true'] { color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast)); background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);}[ngMenuItem]:focus,[ngMenuTrigger]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--gray-500); margin: 0.25rem 0; opacity: 0.25;}
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()"> Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </ng-template> </div> </ng-template> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem value="Archive"> <span class="icon material-symbols-outlined" translate="no">archive</span> <span class="label">Archive</span> </div> <div ngMenuItem value="Report spam"> <span class="icon material-symbols-outlined" translate="no">report</span> <span class="label">Report spam</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-size: 0.8rem; --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background)); font-family: 'Press Start 2P'; --retro-button-color: var(--vivid-pink); --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; font-family: 'Press Start 2P'; color: #000; background-color: var(--vivid-pink); box-shadow: var(--retro-clickable-shadow); transition: transform 0.1s, box-shadow 0.1s;}[ngMenuTrigger]:hover { transform: translate(1px, 1px);}[ngMenuTrigger]:active { background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff); box-shadow: var(--retro-pressed-shadow); transform: translate(4px, 4px);}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { margin-top: 8px; width: 15rem; padding: 0.25rem; background-color: var(--page-background); box-shadow: var(--retro-flat-shadow);}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem;}[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuTrigger]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: 8px;}[ngMenuItem]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: -4px;}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 4px solid #000; margin: 0.25rem 0; opacity: 0.25;}
ユーザーがアイテムを選択するかEscapeキーを押すと、メニューは自動的に閉じます。
コンテキストメニュー
コンテキストメニューは、ユーザーが要素を右クリックしたときにカーソル位置に表示されます。
app.ts
import {Component} from '@angular/core';@Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css',})export class App {}
app.html
<h1>Coming Soon</h1>
contextmenuイベントの座標を使用してメニューを配置します。
スタンドアロンメニュー
スタンドアロンメニューはトリガーを必要とせず、インターフェース上で常に表示されたままになります。
app.ts
import {Component, signal, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, OverlayModule],})export class App { hasInteracted = signal(false); updateMenu = viewChild<Menu<string>>('updateMenu');}
app.html
<div ngMenu (mouseover)="hasInteracted.set(true)" (focusin)="hasInteracted.set(true)"> <ng-template ngMenuContent> <span id="security-label" class="heading">SECURITY</span> <div role="group" aria-labelledby="security-label"> <div ngMenuItem value="Change password"> <span class="icon material-symbols-outlined" translate="no">lock_open</span> <span class="label">Change password</span> </div> <div ngMenuItem value="Two-factor authentication"> <span class="icon material-symbols-outlined" translate="no">security_key</span> <span class="label">Two-factor authentication</span> </div> <div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()"> <span class="icon material-symbols-outlined" translate="no">refresh</span> <span class="label">Reset</span> <span class="icon material-symbols-outlined" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="hasInteracted()" [cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu #updateMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Email address"> <span class="icon material-symbols-outlined" translate="no">email</span> <span class="label">Email address</span> </div> <div ngMenuItem value="Phone number"> <span class="icon material-symbols-outlined" translate="no">phone</span> <span class="label">Phone number</span> </div> <div ngMenuItem value="Password"> <span class="icon material-symbols-outlined" translate="no">vpn_key</span> <span class="label">Password</span> </div> </ng-template> </div> </ng-template> </div> <div role="separator" class="separator"></div> <span id="help-label" class="heading">HELP</span> <div role="group" aria-labelledby="help-label"> <div ngMenuItem value="Support"> <span class="icon material-symbols-outlined" translate="no">help</span> <span class="label">Support</span> </div> <div ngMenuItem value="Feedback"> <span class="icon material-symbols-outlined" translate="no">feedback</span> <span class="label">Feedback</span> </div> </div> <div role="separator" class="separator"></div> <div ngMenuItem value="Logout"> <span class="icon material-symbols-outlined" translate="no">logout</span> <span class="label">Logout</span> </div> </ng-template></div>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font); --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));}[ngMenu] { margin: 0; width: 30rem; padding: 0.25rem; border-radius: 0.5rem; border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenu] [ngMenu] { width: 15rem;}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem; border-radius: 0.25rem;}[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuItem]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--border-color); margin: 0.25rem 0; opacity: 0.25;}[ngMenu] .heading { display: block; font-weight: bold; opacity: 0.6; font-size: 0.75rem; padding: 0.75rem; letter-spacing: 0.05rem;}
app.ts
import {Component, signal, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, OverlayModule],})export class App { hasInteracted = signal(false); updateMenu = viewChild<Menu<string>>('updateMenu');}
app.html
<div ngMenu class="material-menu" (mouseover)="hasInteracted.set(true)" (focusin)="hasInteracted.set(true)"> <ng-template ngMenuContent> <span id="security-label" class="heading">SECURITY</span> <div role="group" aria-labelledby="security-label"> <div ngMenuItem value="Change password"> <span class="icon material-symbols-outlined" translate="no">lock_open</span> <span class="label">Change password</span> </div> <div ngMenuItem value="Two-factor authentication"> <span class="icon material-symbols-outlined" translate="no">security_key</span> <span class="label">Two-factor authentication</span> </div> <div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()"> <span class="icon material-symbols-outlined" translate="no">refresh</span> <span class="label">Reset</span> <span class="icon material-symbols-outlined" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="hasInteracted()" [cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu #updateMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Email address"> <span class="icon material-symbols-outlined" translate="no">email</span> <span class="label">Email address</span> </div> <div ngMenuItem value="Phone number"> <span class="icon material-symbols-outlined" translate="no">phone</span> <span class="label">Phone number</span> </div> <div ngMenuItem value="Password"> <span class="icon material-symbols-outlined" translate="no">vpn_key</span> <span class="label">Password</span> </div> </ng-template> </div> </ng-template> </div> <div role="separator" class="separator"></div> <span id="help-label" class="heading">HELP</span> <div role="group" aria-labelledby="help-label"> <div ngMenuItem value="Support"> <span class="icon material-symbols-outlined" translate="no">help</span> <span class="label">Support</span> </div> <div ngMenuItem value="Feedback"> <span class="icon material-symbols-outlined" translate="no">feedback</span> <span class="label">Feedback</span> </div> </div> <div role="separator" class="separator"></div> <div ngMenuItem value="Logout"> <span class="icon material-symbols-outlined" translate="no">logout</span> <span class="label">Logout</span> </div> </ng-template></div>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngMenu] { gap: 3px; width: 30rem; padding: 1rem; border-radius: 1rem; display: flex; flex-direction: column; background-color: color-mix(in srgb, var(--vivid-pink) 5%, var(--page-background)); border: 1px solid color-mix(in srgb, var(--full-contrast) 10%, var(--page-background));}[ngMenu] [ngMenu] { width: 15rem;}[ngMenu] .group { padding: 0.25rem; border-radius: 0.25rem; background-color: var(--page-background); box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);}[ngMenu] .group:first-of-type { border-top-left-radius: 1rem; border-top-right-radius: 1rem;}[ngMenu] .group:last-of-type { border-bottom-left-radius: 1rem; border-bottom-right-radius: 1rem;}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.75rem; font-size: 0.875rem; border-radius: 0.75rem;}[ngMenuItem][data-active='true'] { color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast)); background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);}[ngMenuItem]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--gray-500); margin: 0.25rem 0; opacity: 0.25;}[ngMenu] .heading { display: block; font-weight: bold; opacity: 0.6; font-size: 0.75rem; padding: 0.75rem; letter-spacing: 0.05rem;}
app.ts
import {Component, signal, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, OverlayModule],})export class App { hasInteracted = signal(false); updateMenu = viewChild<Menu<string>>('updateMenu');}
app.html
<div ngMenu class="retro-menu" (mouseover)="hasInteracted.set(true)" (focusin)="hasInteracted.set(true)"> <ng-template ngMenuContent> <span id="security-label" class="heading">SECURITY</span> <div role="group" aria-labelledby="security-label"> <div ngMenuItem value="Change password"> <span class="icon material-symbols-outlined" translate="no">lock_open</span> <span class="label">Change password</span> </div> <div ngMenuItem value="Two-factor authentication"> <span class="icon material-symbols-outlined" translate="no">security_key</span> <span class="label">Two-factor authentication</span> </div> <div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()"> <span class="icon material-symbols-outlined" translate="no">refresh</span> <span class="label">Reset</span> <span class="icon material-symbols-outlined" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="hasInteracted()" [cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu #updateMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Email address"> <span class="icon material-symbols-outlined" translate="no">email</span> <span class="label">Email address</span> </div> <div ngMenuItem value="Phone number"> <span class="icon material-symbols-outlined" translate="no">phone</span> <span class="label">Phone number</span> </div> <div ngMenuItem value="Password"> <span class="icon material-symbols-outlined" translate="no">vpn_key</span> <span class="label">Password</span> </div> </ng-template> </div> </ng-template> </div> <div role="separator" class="separator"></div> <span id="help-label" class="heading">HELP</span> <div role="group" aria-labelledby="help-label"> <div ngMenuItem value="Support"> <span class="icon material-symbols-outlined" translate="no">help</span> <span class="label">Support</span> </div> <div ngMenuItem value="Feedback"> <span class="icon material-symbols-outlined" translate="no">feedback</span> <span class="label">Feedback</span> </div> </div> <div role="separator" class="separator"></div> <div ngMenuItem value="Logout"> <span class="icon material-symbols-outlined" translate="no">logout</span> <span class="label">Logout</span> </div> </ng-template></div>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-size: 0.8rem; --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background)); font-family: 'Press Start 2P'; --retro-button-color: var(--vivid-pink); --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngMenu] { margin-top: 8px; width: 30rem; padding: 0.25rem; background-color: var(--page-background); box-shadow: var(--retro-flat-shadow);}[ngMenu] [ngMenu] { width: 15rem;}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem;}[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuItem]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: -4px;}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 4px solid #000; margin: 0.25rem 0; opacity: 0.25;}[ngMenu] .heading { display: block; font-weight: bold; opacity: 0.6; font-size: 0.75rem; padding: 0.75rem; letter-spacing: 0.05rem;}
スタンドアロンメニューは、常に表示されるアクションリストやナビゲーションに適しています。
無効化されたメニューアイテム
disabled入力を使用して特定のメニューアイテムを無効にします。softDisabledでフォーカスの動作を制御します。
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze" [disabled]="true"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" [disabled]="true" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </ng-template> </div> </ng-template> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem value="Archive"> <span class="icon material-symbols-outlined" translate="no">archive</span> <span class="label">Archive</span> </div> <div ngMenuItem value="Report spam"> <span class="icon material-symbols-outlined" translate="no">report</span> <span class="label">Report spam</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font); --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; border-radius: 0.5rem; color: var(--primary-contrast); border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { margin: 0; width: 15rem; padding: 0.25rem; border-radius: 0.5rem; border: 1px solid var(--border-color); background-color: var(--page-background);}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem; border-radius: 0.25rem;}[ngMenuItem][aria-disabled='true'] { opacity: 0.5; cursor: default;}[ngMenuTrigger]:hover,[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuItem]:focus,[ngMenuTrigger]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--border-color); margin: 0.25rem 0; opacity: 0.25;}
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div class="group"> <div ngMenuItem value="Mark as read"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div ngMenuItem value="Delete" [disabled]="true"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </div> <div class="group"> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div class="group"> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label" [disabled]="true"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </div> </ng-template> </div> </ng-template> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; border-radius: 0.5rem; border: 1px solid transparent; background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent); color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { gap: 3px; width: 15rem; display: flex; flex-direction: column;}[ngMenu] .group { padding: 0.25rem; border-radius: 0.25rem; background-color: var(--page-background); box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);}[ngMenu] .group:first-of-type { border-top-left-radius: 1rem; border-top-right-radius: 1rem;}[ngMenu] .group:last-of-type { border-bottom-left-radius: 1rem; border-bottom-right-radius: 1rem;}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.75rem; font-size: 0.875rem; border-radius: 0.75rem;}[ngMenuTrigger]:hover,[ngMenuTrigger][aria-expanded='true'] { background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);}[ngMenuItem][data-active='true'] { color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast)); background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);}[ngMenuItem]:focus,[ngMenuTrigger]:focus { outline: 2px solid var(--vivid-pink);}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 1px solid var(--gray-500); margin: 0.25rem 0; opacity: 0.25;}[ngMenuItem][aria-disabled='true'] { opacity: 0.5; cursor: default;}
app.ts
import {Component, viewChild} from '@angular/core';import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';import {OverlayModule} from '@angular/cdk/overlay';@Component({ selector: 'app-root', templateUrl: 'app.html', styleUrl: 'app.css', imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],})export class App { formatMenu = viewChild<Menu<string>>('formatMenu'); categorizeMenu = viewChild<Menu<string>>('categorizeMenu');}
app.html
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()"> Open Menu</button><ng-template [cdkConnectedOverlayOpen]="trigger.expanded()" [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4}]" cdkAttachPopoverAsChild> <div ngMenu class="menu" #formatMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as read" [disabled]="true"> <span class="icon material-symbols-outlined" translate="no">mark_email_read</span> <span class="label">Mark as read</span> </div> <div ngMenuItem value="Snooze"> <span class="icon material-symbols-outlined" translate="no">snooze</span> <span class="label">Snooze</span> </div> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem class="menu-item" value="Categorize" #categorizeItem [submenu]="categorizeMenu()" > <span class="icon material-symbols-outlined" translate="no">category</span> <span class="label">Categorize</span> <span class="icon material-symbols-outlined arrow" translate="no">arrow_right</span> </div> <ng-template [cdkConnectedOverlayOpen]="formatMenu.visible()" [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" cdkAttachPopoverAsChild > <div ngMenu class="menu" #categorizeMenu="ngMenu"> <ng-template ngMenuContent> <div ngMenuItem value="Mark as important"> <span class="icon material-symbols-outlined" translate="no">label_important</span> <span class="label">Mark as important</span> </div> <div ngMenuItem value="Star"> <span class="icon material-symbols-outlined" translate="no">star</span> <span class="label">Star</span> </div> <div ngMenuItem value="Label"> <span class="icon material-symbols-outlined" translate="no">label</span> <span class="label">Label</span> </div> </ng-template> </div> </ng-template> <div role="separator" aria-orientation="horizontal" class="separator"></div> <div ngMenuItem value="Archive"> <span class="icon material-symbols-outlined" translate="no">archive</span> <span class="label">Archive</span> </div> <div ngMenuItem value="Report spam"> <span class="icon material-symbols-outlined" translate="no">report</span> <span class="label">Report spam</span> </div> <div ngMenuItem value="Delete"> <span class="icon material-symbols-outlined" translate="no">delete</span> <span class="label">Delete</span> </div> </ng-template> </div></ng-template>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-size: 0.8rem; --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background)); font-family: 'Press Start 2P'; --retro-button-color: var(--vivid-pink); --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngMenuTrigger] { display: flex; cursor: pointer; align-items: center; padding: 0.6rem 2rem; font-family: 'Press Start 2P'; color: #000; background-color: var(--vivid-pink); box-shadow: var(--retro-clickable-shadow); transition: transform 0.1s, box-shadow 0.1s;}[ngMenuTrigger]:hover { transform: translate(1px, 1px);}[ngMenuTrigger]:active { background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff); box-shadow: var(--retro-pressed-shadow); transform: translate(4px, 4px);}[ngMenuTrigger] .icon { font-size: 1.5rem; opacity: 0.875;}[ngMenu] { margin-top: 8px; width: 15rem; padding: 0.25rem; background-color: var(--page-background); box-shadow: var(--retro-flat-shadow);}[ngMenu][data-visible='false'] { display: none;}[ngMenuItem] { outline: none; display: flex; cursor: pointer; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 0.875rem;}[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--border-color) 10%, transparent);}[ngMenuTrigger]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: 8px;}[ngMenuItem]:focus { outline: 4px dashed var(--vivid-pink); outline-offset: -4px;}[ngMenuItem] .icon { opacity: 0.875; font-size: 1.25rem;}[ngMenuItem] .label { flex: 1; opacity: 0.875; font-size: 0.875rem;}[ngMenuItem]:not([aria-expanded='true']) .arrow { opacity: 0.5;}[ngMenu] .separator { border-top: 4px solid #000; margin: 0.25rem 0; opacity: 0.25;}[ngMenuItem][aria-disabled='true'] { opacity: 0.5; cursor: default;}
[softDisabled]="true"の場合、無効化されたアイテムはフォーカスを受け取ることができますが、アクティブにできません。[softDisabled]="false"の場合、無効化されたアイテムはキーボードナビゲーション中にスキップされます。
API
Menu
メニューアイテムのコンテナディレクティブです。
入力
| Property | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
メニュー内のすべてのアイテムを無効にします |
wrap |
boolean |
true |
キーボードナビゲーションが端で折り返すかどうか |
softDisabled |
boolean |
true |
trueの場合、無効化されたアイテムはフォーカス可能ですが、インタラクティブではありません |
メソッド
| Method | Parameters | Description |
|---|---|---|
close |
none | メニューを閉じます |
focusFirstItem |
none | 最初のメニューアイテムにフォーカスを移動します |
MenuBar
複数のメニューを格納する水平コンテナです。
入力
| Property | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
メニューバー全体を無効にします |
wrap |
boolean |
true |
キーボードナビゲーションが端で折り返すかどうか |
softDisabled |
boolean |
true |
trueの場合、無効化されたアイテムはフォーカス可能ですが、インタラクティブではありません |
MenuItem
メニュー内の個々のアイテムです。
入力
| Property | Type | Default | Description |
|---|---|---|---|
value |
any |
— | 必須。 このアイテムの値です |
disabled |
boolean |
false |
このメニューアイテムを無効にします |
submenu |
Menu |
— | サブメニューへの参照です |
searchTerm |
string |
'' |
タイプアヘッドの検索語です(双方向バインディングをサポート) |
シグナル
| Property | Type | Description |
|---|---|---|
active |
Signal<boolean> |
アイテムが現在フォーカスを持っているかどうか |
expanded |
Signal<boolean> |
サブメニューが展開されているかどうか |
hasPopup |
Signal<boolean> |
アイテムに関連付けられたサブメニューがあるかどうか |
NOTE: MenuItemはパブリックメソッドを公開しません。submenu入力を使用して、サブメニューをメニューアイテムに関連付けます。
MenuTrigger
メニューを開くボタンまたは要素です。
入力
| Property | Type | Default | Description |
|---|---|---|---|
menu |
Menu |
— | 必須。 トリガーするメニューです |
disabled |
boolean |
false |
トリガーを無効にします |
softDisabled |
boolean |
true |
trueの場合、無効化されたトリガーはフォーカス可能です |
シグナル
| Property | Type | Description |
|---|---|---|
expanded |
Signal<boolean> |
メニューが現在開いているかどうか |
hasPopup |
Signal<boolean> |
トリガーに関連付けられたメニューがあるかどうか |
メソッド
| Method | Parameters | Description |
|---|---|---|
open |
none | メニューを開きます |
close |
none | メニューを閉じます |
toggle |
none | メニューの開閉を切り替えます |