개요
메뉴바는 애플리케이션 메뉴에 항상 접근할 수 있도록 제공하는 수평 탐색 막대입니다. 메뉴바는 명령을 File, Edit, View와 같은 논리적인 범주로 나눕니다. 사용자는 키보드나 마우스로 애플리케이션 기능을 찾고 실행할 수 있습니다.
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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, Insert, Format 등)를 만들 때
- 인터페이스에서 항상 표시되는 탐색 기능을 만들 때
- 명령을 논리적인 최상위 범주로 구성할 때
- 키보드를 지원하는 수평 메뉴 탐색이 필요할 때
- 데스크탑 스타일의 애플리케이션 인터페이스를 만들 때
메뉴바를 피해야 하는 경우:
- 개별 작업의 드롭다운 메뉴를 만들 때(트리거가 있는 메뉴를 사용하세요)
- 컨텍스트 메뉴를 만들 때(메뉴 가이드의 패턴을 사용하세요)
- 간단한 독립형 작업 목록을 만들 때(메뉴를 사용하세요)
- 가로 공간이 제한된 모바일 인터페이스
- 사이드바나 헤더 탐색 패턴에 배치하는 것이 적합한 탐색 기능
주요 기능
- 수평 탐색: 왼쪽/오른쪽 방향키로 최상위 범주 사이를 이동합니다.
- 항상 표시: 모달이 아니며 닫을 수 없고 항상 표시됩니다.
- 마우스를 올려 열기: 처음 키보드로 조작하거나 클릭한 뒤에는 마우스를 올리면 하위 메뉴가 열립니다.
- 중첩 하위 메뉴: 여러 단계 깊이의 메뉴를 지원합니다.
- 키보드 탐색: 방향키, 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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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,
},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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},
]"
[cdkConnectedOverlayDisableClose]="true"
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]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='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 언어 사용자에게 자연스러운 탐색을 제공합니다.
테스트
Angular Aria는 메뉴바 컴포넌트를 테스트할 수 있는 컴포넌트 하네스를 제공합니다. 컴포넌트 테스트에서 하네스를 사용하는 예제는 다음과 같습니다.
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness} from '@angular/aria/menu/testing';
import {MyMenubarComponent} from './my-menubar'; // 테스트할 컴포넌트
describe('MyMenubarComponent', () => {
let fixture: ComponentFixture<MyMenubarComponent>;
let loader: HarnessLoader;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [MyMenubarComponent],
});
fixture = TestBed.createComponent(MyMenubarComponent);
await fixture.whenStable();
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should interact with menubar items', async () => {
// 메뉴바 하네스를 불러옵니다(선택자가 '[ngMenuBar]'인 MenuHarness입니다).
const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'}));
// 메뉴바는 항상 표시되며 언제나 "열린" 상태입니다.
expect(await menubar.isOpen()).toBe(true);
expect(await menubar.isMenuBar()).toBe(true);
// 최상위 항목을 가져옵니다.
const items = await menubar.getItems();
expect(items.length).toBe(2);
expect(await items[0].getText()).toBe('File');
expect(await items[1].getText()).toBe('Edit');
// 항목을 클릭해서 드롭다운 메뉴를 엽니다.
await items[0].click();
const fileMenu = await items[0].getSubmenu();
expect(fileMenu).toBeTruthy();
expect(await fileMenu!.isOpen()).toBe(true);
});
});
API 레퍼런스
자세한 API 문서는 다음 API 레퍼런스를 참고하세요.