メニューバー
概要
メニューバーは、アプリケーションメニューへの永続的なアクセスを提供する水平ナビゲーションバーです。メニューバーは、ファイル、編集、表示などの論理的なカテゴリーにコマンドを整理し、ユーザーがキーボードやマウスの操作を通じてアプリケーションの機能を発見し、実行するのに役立ちます。
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[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][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;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="material-menubar" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
padding: 0.25rem;
border-radius: 1rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[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(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="retro-menubar" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[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);
}
[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;
}
使い方
メニューバーは、アプリケーションのコマンドを永続的で発見しやすいナビゲーションに整理するのに適しています。
メニューバーを使用する場合:
- アプリケーションのコマンドバー(ファイル、編集、表示、挿入、フォーマットなど)を構築する場合
- インターフェース全体で表示され続ける永続的なナビゲーションを作成する場合
- コマンドを論理的なトップレベルのカテゴリーに整理する場合
- キーボードサポート付きの水平メニューナビゲーションが必要な場合
- デスクトップスタイルのアプリケーションインターフェースを構築する場合
メニューバーを避けるべき場合:
- 個々のアクションのためのドロップダウンメニューを構築する場合(代わりにトリガー付きMenuを使用してください)
- コンテキストメニューを作成する場合(Menuガイドパターンを使用してください)
- シンプルなスタンドアロンのアクションリストの場合(代わりにMenuを使用してください)
- 水平方向のスペースが限られているモバイルインターフェースの場合
- ナビゲーションがサイドバーまたはヘッダーのナビゲーションパターンに属する場合
機能
- 水平ナビゲーション - 左右の矢印キーでトップレベルのカテゴリー間を移動
- 永続的な可視性 - 常に表示され、モーダルでも非表示でもない
- ホバーで開く - 最初のキーボードまたはクリック操作の後、ホバーでサブメニューが開く
- ネストされたサブメニュー - 複数レベルのメニュー深度をサポート
- キーボードナビゲーション - 矢印キー、Enter/Space、Escape、および先行入力サーチ
- 無効状態 - メニューバー全体または個々の項目を無効化
- RTLサポート - 右から左へ記述する言語のナビゲーションを自動でサポート
例
基本的なメニューバー
メニューバーは、トップレベルのカテゴリーに整理されたアプリケーションコマンドへの永続的なアクセスを提供します。ユーザーは左/右矢印キーでカテゴリー間を移動し、Enterキーまたは下矢印キーでメニューを開きます。
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[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][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;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="material-menubar" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
padding: 0.25rem;
border-radius: 1rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[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(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="retro-menubar" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[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);
}
[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の間を移動します。Enterキーまたは下矢印キーを押すとメニューが開き、上/下矢印キーでサブメニュー項目を操作できます。
無効化されたメニューバーアイテム
特定のメニュー項目またはメニューバー全体を無効にして、インタラクションを防ぎます。softDisabled入力で、無効化された項目がキーボードフォーカスを受け取れるかどうかを制御します。
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[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][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;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="material-menubar" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
padding: 0.25rem;
border-radius: 1rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[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(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="retro-menubar" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[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);
}
[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;
}
メニューバーで[softDisabled]="true"の場合、無効化された項目はフォーカスを受け取れますが、アクティブにはできません。[softDisabled]="false"の場合、無効化された項目はキーボードナビゲーション中にスキップされます。
RTLサポート
メニューバーは、右から左へ記述する言語(RTL)に自動的に適応します。矢印キーによるナビゲーションの方向は逆になり、サブメニューは左側に配置されます。
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
import {Dir} from '@angular/cdk/bidi';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Dir, MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar dir="rtl" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[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][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;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
import {Dir} from '@angular/cdk/bidi';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Dir, MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="material-menubar" dir="rtl" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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));
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
padding: 0.25rem;
border-radius: 1rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[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(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
import {Dir} from '@angular/cdk/bidi';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Dir, MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
fileMenu = viewChild<Menu<string>>('fileMenu');
shareMenu = viewChild<Menu<string>>('shareMenu');
editMenu = viewChild<Menu<string>>('editMenu');
viewMenu = viewChild<Menu<string>>('viewMenu');
insertMenu = viewChild<Menu<string>>('insertMenu');
imageMenu = viewChild<Menu<string>>('imageMenu');
chartMenu = viewChild<Menu<string>>('chartMenu');
formatMenu = viewChild<Menu<string>>('formatMenu');
textMenu = viewChild<Menu<string>>('textMenu');
sizeMenu = viewChild<Menu<string>>('sizeMenu');
paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
alignMenu = viewChild<Menu<string>>('alignMenu');
rendered = signal(false);
onFocusIn() {
this.rendered.set(true);
}
}
app.html
<div ngMenuBar class="retro-menubar" dir="rtl" (focusin)="onFocusIn()">
<div
ngMenuItem
#fileEl
#fileItem="ngMenuItem"
class="menu-bar-item"
value="File"
[submenu]="fileMenu()"
>
File
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #fileMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="New">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>article</span
>
<span class="label">New</span>
<span class="shortcut">⌘N</span>
</div>
<div ngMenuItem value="Open">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>folder</span
>
<span class="label">Open</span>
<span class="shortcut">⌘O</span>
</div>
<div ngMenuItem value="Make a copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>file_copy</span
>
<span class="label">Make a copy</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!fileItem.expanded()"
[cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #shareMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Share with others">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>person_add</span
>
<span class="label">Share with others</span>
</div>
<div ngMenuItem value="Publish to web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>public</span
>
<span class="label">Publish to web</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Download">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>download</span
>
<span class="label">Download</span>
</div>
<div ngMenuItem value="Print">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>print</span
>
<span class="label">Print</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Rename">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Rename</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Move to trash</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#editEl
#editItem="ngMenuItem"
class="menu-bar-item"
value="Edit"
[submenu]="editMenu()"
>
Edit
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #editMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Undo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
<span class="label">Undo</span>
<span class="shortcut">⌘Z</span>
</div>
<div ngMenuItem value="Redo">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
<span class="label">Redo</span>
<span class="shortcut">⌘Y</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Cut">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_cut</span
>
<span class="label">Cut</span>
<span class="shortcut">⌘X</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
<span class="shortcut">⌘C</span>
</div>
<div ngMenuItem value="Paste">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_paste</span
>
<span class="label">Paste</span>
<span class="shortcut">⌘V</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Find and replace">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>find_replace</span
>
<span class="label">Find and replace</span>
<span class="shortcut">⇧⌘H</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#viewEl
#viewItem="ngMenuItem"
class="menu-bar-item"
value="View"
[submenu]="viewMenu()"
>
View
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #viewMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Show print layout" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show print layout</span>
</div>
<div ngMenuItem value="Show ruler" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="label">Show ruler</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Zoom in">
<span class="label">Zoom in</span>
<span class="shortcut">⌘+</span>
</div>
<div ngMenuItem value="Zoom out">
<span class="label">Zoom out</span>
<span class="shortcut">⌘-</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem value="Full screen">
<span class="label">Full screen</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#insertEl
#insertItem="ngMenuItem"
class="menu-bar-item"
value="Insert"
[submenu]="insertMenu()"
>
Insert
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #insertMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>image</span
>
<span class="label">Image</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #imageMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Upload from computer">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>upload</span
>
<span class="label">Upload from computer</span>
</div>
<div ngMenuItem value="Search the web">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>search</span
>
<span class="label">Search the web</span>
</div>
<div ngMenuItem value="By URL">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>link</span
>
<span class="label">By URL</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Table">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>table_chart</span
>
<span class="label">Table</span>
</div>
<div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Chart</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!insertItem.expanded()"
[cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #chartMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bar">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>bar_chart</span
>
<span class="label">Bar</span>
</div>
<div ngMenuItem value="Column">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>insert_chart</span
>
<span class="label">Column</span>
</div>
<div ngMenuItem value="Line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>show_chart</span
>
<span class="label">Line</span>
</div>
<div ngMenuItem value="Pie">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>pie_chart</span
>
<span class="label">Pie</span>
</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem value="Horizontal line">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>horizontal_rule</span
>
<span class="label">Horizontal line</span>
</div>
</ng-template>
</div>
</ng-template>
<div
ngMenuItem
#formatEl
#formatItem="ngMenuItem"
class="menu-bar-item"
value="Format"
[submenu]="formatMenu()"
>
Format
</div>
<ng-template
[cdkConnectedOverlayOpen]="rendered()"
[cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Text</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #textMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Bold">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_bold</span
>
<span class="label">Bold</span>
<span class="shortcut">⌘B</span>
</div>
<div ngMenuItem value="Italic">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_italic</span
>
<span class="label">Italic</span>
<span class="shortcut">⌘I</span>
</div>
<div ngMenuItem value="Underline">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_underlined</span
>
<span class="label">Underline</span>
<span class="shortcut">⌘U</span>
</div>
<div ngMenuItem value="Strikethrough">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>strikethrough_s</span
>
<span class="label">Strikethrough</span>
<span class="shortcut">⇧⌘X</span>
</div>
<div role="separator" class="example-menu-item-separator"></div>
<div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
<span class="label">Size</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!textItem.expanded()"
[cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{
originX: 'end',
originY: 'top',
overlayY: 'top',
overlayX: 'start',
offsetX: -12,
},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #sizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Increase font size">
<span class="label">Increase font size</span>
<span class="shortcut">⇧⌘.</span>
</div>
<div ngMenuItem value="Decrease font size">
<span class="label">Decrease font size</span>
<span class="shortcut">⇧⌘,</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Paragraph styles</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #paragraphMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Normal text">Normal text</div>
<div ngMenuItem value="Heading 1">Heading 1</div>
<div ngMenuItem value="Heading 2">Heading 2</div>
</ng-template>
</div>
</ng-template>
<div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_indent_increase</span
>
<span class="label">Align & indent</span>
<span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_left</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="!!formatItem.expanded()"
[cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #alignMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Align left">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_left</span
>
<span class="label">Align left</span>
</div>
<div ngMenuItem value="Align center">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_center</span
>
<span class="label">Align center</span>
</div>
<div ngMenuItem value="Align right">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_right</span
>
<span class="label">Align right</span>
</div>
<div ngMenuItem value="Justify">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>format_align_justify</span
>
<span class="label">Justify</span>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--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(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuBar] {
display: flex;
gap: 0.25rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[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);
}
[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;
}
dir="rtl"属性はRTLモードを有効にします。左矢印キーは右に、右矢印キーは左に移動し、RTL言語のユーザーにとって自然なナビゲーションを維持します。
API
メニューバーパターンはAngularのAriaライブラリのディレクティブを使用します。完全なAPIドキュメントについては、Menuガイドを参照してください。
MenuBar
トップレベルのメニューアイテムのための水平コンテナです。
Inputs
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
disabled |
boolean |
false |
メニューバー全体を無効にします |
wrap |
boolean |
true |
キーボードナビゲーションが最後のアイテムから最初のアイテムにラップするかどうか |
softDisabled |
boolean |
true |
trueの場合、無効化されたアイテムはフォーカス可能ですが、インタラクティブではありません |
利用可能なすべての入力とシグナルの詳細については、Menu APIドキュメントを参照してください。
MenuItem
メニューバー内の個々のアイテムです。Menuと同じAPIです - MenuItemを参照してください。
メニューバー固有の動作:
- 左右の矢印キーでメニューバーのアイテム間を移動します(垂直メニューでは上下)
- 最初のキーボード操作またはクリックで、サブメニューのホバーで開く機能が有効になります
- Enterキーまたは下矢印キーでサブメニューを開き、最初のアイテムにフォーカスします
aria-haspopup="menu"はサブメニューを持つアイテムを示します
MenuTrigger
通常メニューバーでは使用されません - MenuItemは関連するサブメニューがある場合、トリガーの動作を直接処理します。スタンドアロンのメニュートリガーパターンについては、MenuTriggerを参照してください。