Vue d'ensemble
La menubar est une barre de navigation horizontale qui donne un accès permanent aux menus de l'application. Les menubars organisent les commandes en catégories logiques comme Fichier, Édition et Affichage, ce qui aide les utilisateurs à découvrir et à exécuter les fonctionnalités de l'application au clavier ou à la souris.
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;
}
Utilisation
Les menubars conviennent bien pour organiser les commandes d'une application dans une navigation permanente et facile à découvrir.
Utilisez une menubar pour :
- Construire des barres de commandes d'application (comme Fichier, Édition, Affichage, Insertion, Format)
- Créer une navigation permanente qui reste visible dans toute l'interface
- Organiser les commandes en catégories logiques de premier niveau
- Obtenir une navigation horizontale dans les menus avec prise en charge du clavier
- Construire des interfaces d'application de type bureau
Évitez les menubars pour :
- Construire des menus déroulants pour des actions individuelles (utilisez plutôt un menu avec déclencheur)
- Créer des menus contextuels (utilisez le pattern du guide Menu)
- De simples listes d'actions standalone (utilisez plutôt un menu)
- Les interfaces mobiles où l'espace horizontal est limité
- Une navigation qui relève d'un pattern de barre latérale ou d'en-tête
Fonctionnalités
- Navigation horizontale - Les touches fléchées gauche/droite permettent de passer d'une catégorie de premier niveau à l'autre
- Visibilité permanente - Toujours visible, ni modale ni masquable
- Ouverture au survol - Les sous-menus s'ouvrent au survol après une première interaction au clavier ou au clic
- Sous-menus imbriqués - Prise en charge de plusieurs niveaux de profondeur de menu
- Navigation au clavier - Touches fléchées, Entrée/Espace, Échap et recherche par saisie (typeahead)
- États désactivés - Désactivez la menubar entière ou des éléments individuels
- Prise en charge du RTL - Navigation automatique pour les langues de droite à gauche
Exemples
Menubar simple
Une menubar donne un accès permanent aux commandes de l'application, organisées en catégories de premier niveau. Les utilisateurs naviguent entre les catégories avec les flèches gauche/droite et ouvrent les menus avec Entrée ou la flèche bas.
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;
}
Appuyez sur la flèche droite pour passer de Fichier à Édition puis à Affichage. Appuyez sur Entrée ou sur la flèche bas pour ouvrir un menu, puis parcourez les éléments du sous-menu avec les flèches haut/bas.
Éléments de menubar désactivés
Désactivez des éléments de menu spécifiques ou la menubar entière pour empêcher toute interaction. Contrôlez si les éléments désactivés peuvent recevoir le focus clavier avec l'input 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;
}
Avec [softDisabled]="true" sur la menubar, les éléments désactivés peuvent recevoir le focus mais ne peuvent pas être activés. Avec [softDisabled]="false", les éléments désactivés sont ignorés pendant la navigation au clavier.
Prise en charge du RTL
Les menubars s'adaptent automatiquement aux langues de droite à gauche (RTL). La navigation avec les touches fléchées s'inverse et les sous-menus se positionnent sur le côté gauche.
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;
}
L'attribut dir="rtl" active le mode RTL. La flèche gauche déplace vers la droite et la flèche droite vers la gauche, ce qui préserve une navigation naturelle pour les utilisateurs de langues RTL.
Tests
Angular Aria fournit des harnais de composant pour tester les composants menubar. Voici un exemple d'utilisation des harnais dans un test de composant :
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'; // Your component
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 () => {
// Load the menubar harness (which is a MenuHarness with selector '[ngMenuBar]')
const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'}));
// Menubars are persistent and always "open"
expect(await menubar.isOpen()).toBe(true);
expect(await menubar.isMenuBar()).toBe(true);
// Get top-level items
const items = await menubar.getItems();
expect(items.length).toBe(2);
expect(await items[0].getText()).toBe('File');
expect(await items[1].getText()).toBe('Edit');
// Click an item to open its dropdown menu
await items[0].click();
const fileMenu = await items[0].getSubmenu();
expect(fileMenu).toBeTruthy();
expect(await fileMenu!.isOpen()).toBe(true);
});
});
Référence de l'API
Pour une documentation détaillée de l'API, consultez les références suivantes :