Menu

A versatile menu component built on top of Angular CDK Menu for creating dropdown and context menus.

PreviousNext
import { Component } from '@angular/core';
 
import { ZardButtonComponent } from '../../button/button.component';
import { ZardDividerComponent } from '../../divider/divider.component';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardMenuModule } from '../menu.module';
 
@Component({
  selector: 'zard-demo-menu-default',
  imports: [ZardMenuModule, ZardButtonComponent, ZardDividerComponent, ZardIconComponent],
  standalone: true,
  template: `
    <nav class="flex items-center justify-between p-4">
      <div class="flex items-center space-x-6">
        <div class="flex items-center space-x-1">
          <div class="relative">
            <button z-button zType="ghost" z-menu zTrigger="hover" [zMenuTriggerFor]="productsMenu">
              Products
              <z-icon zType="chevron-down" class="ml-1" />
            </button>
 
            <ng-template #productsMenu>
              <div z-menu-content class="w-48">
                <button z-menu-item (click)="log('Analytics')">Analytics</button>
                <button z-menu-item (click)="log('Dashboard')">Dashboard</button>
                <button z-menu-item (click)="log('Reports')">Reports</button>
                <button z-menu-item zDisabled (click)="log('Insights')">Insights</button>
              </div>
            </ng-template>
          </div>
 
          <div class="relative">
            <button z-button zType="ghost" z-menu zTrigger="hover" [zMenuTriggerFor]="solutionsMenu">
              Solutions
              <z-icon zType="chevron-down" class="ml-1" />
            </button>
 
            <ng-template #solutionsMenu>
              <div z-menu-content class="w-80 p-2">
                <div class="grid gap-1">
                  <button z-menu-item (click)="log('For Startups')" class="flex h-auto flex-col items-start py-3">
                    <div class="text-sm font-medium">For Startups</div>
                    <div class="text-muted-foreground mt-1 text-xs">
                      Get started quickly with our startup-friendly tools
                    </div>
                  </button>
 
                  <button z-menu-item (click)="log('For Enterprise')" class="flex h-auto flex-col items-start py-3">
                    <div class="text-sm font-medium">For Enterprise</div>
                    <div class="text-muted-foreground mt-1 text-xs">
                      Scale your business with enterprise-grade features
                    </div>
                  </button>
 
                  <button z-menu-item (click)="log('For Agencies')" class="flex h-auto flex-col items-start py-3">
                    <div class="text-sm font-medium">For Agencies</div>
                    <div class="text-muted-foreground mt-1 text-xs">Manage multiple clients with our agency tools</div>
                  </button>
                </div>
              </div>
            </ng-template>
          </div>
 
          <div class="relative">
            <button z-button zType="ghost" z-menu zTrigger="hover" [zMenuTriggerFor]="resourcesMenu">
              Resources
              <z-icon zType="chevron-down" />
            </button>
 
            <ng-template #resourcesMenu>
              <div z-menu-content class="w-56">
                <button z-menu-item (click)="log('Blog')">
                  <z-icon zType="book-open" class="mr-2" />
                  Blog
                </button>
 
                <button z-menu-item (click)="log('Documentation')">
                  <z-icon zType="file-text" class="mr-2" />
                  Documentation
                </button>
 
                <button
                  z-menu-item
                  z-menu
                  [zMenuTriggerFor]="helpSubmenu"
                  zPlacement="rightTop"
                  class="justify-between"
                >
                  <div class="flex items-center"><z-icon zType="info" class="mr-2" /> Help & Support</div>
                  <z-icon zType="chevron-right" />
                </button>
 
                <z-divider zSpacing="sm" />
 
                <button z-menu-item (click)="log('Community')">
                  <z-icon zType="users" class="mr-2" />
                  Community
                </button>
              </div>
            </ng-template>
 
            <ng-template #helpSubmenu>
              <div z-menu-content class="w-48">
                <button z-menu-item (click)="log('Getting Started')">Getting Started</button>
                <button z-menu-item (click)="log('Tutorials')">Tutorials</button>
                <button z-menu-item (click)="log('FAQ')">FAQ</button>
 
                <z-divider zSpacing="sm" />
 
                <button z-menu-item (click)="log('Contact Support')">Contact Support</button>
                <button z-menu-item (click)="log('Live Chat')">Live Chat</button>
              </div>
            </ng-template>
          </div>
        </div>
      </div>
    </nav>
  `,
})
export class ZardDemoMenuDefaultComponent {
  log(item: string) {
    console.log('Navigate to:', item);
  }
}
 

Installation

1

Run the CLI

Use the CLI to add the component to your project.

npx @ngzard/ui@latest add menu
1

Add the component files

Create the component directory structure and add the following files to your project.

menu.directive.ts
menu.directive.ts
import type { BooleanInput } from '@angular/cdk/coercion';
import { CdkMenuTrigger } from '@angular/cdk/menu';
import type { ConnectedPosition } from '@angular/cdk/overlay';
import { isPlatformBrowser } from '@angular/common';
import {
  booleanAttribute,
  computed,
  Directive,
  effect,
  ElementRef,
  inject,
  input,
  type OnDestroy,
  type OnInit,
  PLATFORM_ID,
  untracked,
} from '@angular/core';
 
import { ZardMenuManagerService } from './menu-manager.service';
import { MENU_POSITIONS_MAP, type ZardMenuPlacement } from './menu-positions';
 
export type ZardMenuTrigger = 'click' | 'hover';
 
@Directive({
  selector: '[z-menu]',
  standalone: true,
  host: {
    role: 'button',
    '[attr.aria-haspopup]': "'menu'",
    '[attr.aria-expanded]': 'cdkTrigger.isOpen()',
    '[attr.data-state]': "cdkTrigger.isOpen() ? 'open': 'closed'",
    '[attr.data-disabled]': "zDisabled() ? '' : undefined",
    '[style.cursor]': "'pointer'",
  },
  hostDirectives: [
    {
      directive: CdkMenuTrigger,
      inputs: ['cdkMenuTriggerFor: zMenuTriggerFor'],
    },
  ],
})
export class ZardMenuDirective implements OnInit, OnDestroy {
  private static readonly MENU_CONTENT_SELECTOR = '.cdk-overlay-pane [z-menu-content]';
 
  protected readonly cdkTrigger = inject(CdkMenuTrigger, { host: true });
  private readonly elementRef = inject(ElementRef);
  private readonly menuManager = inject(ZardMenuManagerService);
  private readonly platformId = inject(PLATFORM_ID);
 
  private closeTimeout: ReturnType<typeof setTimeout> | null = null;
  private readonly cleanupFunctions: Array<() => void> = [];
 
  readonly zMenuTriggerFor = input.required();
  readonly zDisabled = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
  readonly zTrigger = input<ZardMenuTrigger>('click');
  readonly zHoverDelay = input<number>(100);
  readonly zPlacement = input<ZardMenuPlacement>('bottomLeft');
 
  private readonly menuPositions = computed(() => this.getPositionsByPlacement(this.zPlacement()));
 
  constructor() {
    effect(() => {
      const positions = this.menuPositions();
      untracked(() => {
        this.cdkTrigger.menuPosition = positions;
      });
    });
  }
 
  private getPositionsByPlacement(placement: ZardMenuPlacement): ConnectedPosition[] {
    return MENU_POSITIONS_MAP[placement] || MENU_POSITIONS_MAP['bottomLeft'];
  }
 
  ngOnInit(): void {
    const isMobile = this.isMobileDevice();
 
    // If trigger is hover but device is mobile, skip hover behavior
    // The CDK MenuTrigger will handle click by default
    if (this.zTrigger() === 'hover' && !isMobile) {
      this.initializeHoverBehavior();
    }
  }
 
  ngOnDestroy(): void {
    this.cancelScheduledClose();
    this.menuManager.unregisterHoverMenu(this);
    this.cleanupFunctions.forEach(cleanup => cleanup());
    this.cleanupFunctions.length = 0;
  }
 
  close(): void {
    this.cancelScheduledClose();
    this.cdkTrigger.close();
  }
 
  private initializeHoverBehavior(): void {
    this.setupTriggerListeners();
    this.setupMenuOpenListener();
  }
 
  private setupTriggerListeners(): void {
    const element = this.elementRef.nativeElement;
 
    this.addEventListenerWithCleanup(element, 'mouseenter', () => {
      if (this.zDisabled()) return;
 
      this.cancelScheduledClose();
      this.menuManager.registerHoverMenu(this);
      this.cdkTrigger.open();
    });
 
    this.addEventListenerWithCleanup(element, 'mouseleave', event => this.scheduleCloseIfNeeded(event as MouseEvent));
  }
 
  private setupMenuOpenListener(): void {
    const openSubscription = this.cdkTrigger.opened.subscribe(() => {
      setTimeout(() => this.setupMenuContentListeners(), 0);
    });
 
    const closeSubscription = this.cdkTrigger.closed.subscribe(() => {
      this.menuManager.unregisterHoverMenu(this);
    });
 
    this.cleanupFunctions.push(
      () => openSubscription.unsubscribe(),
      () => closeSubscription.unsubscribe(),
    );
  }
 
  private setupMenuContentListeners(): void {
    const menuContent = document.querySelector(ZardMenuDirective.MENU_CONTENT_SELECTOR);
    if (!menuContent) return;
 
    this.addEventListenerWithCleanup(menuContent, 'mouseenter', () => this.cancelScheduledClose());
    this.addEventListenerWithCleanup(menuContent, 'mouseleave', event =>
      this.scheduleCloseIfNeeded(event as MouseEvent),
    );
  }
 
  private cancelScheduledClose(): void {
    if (this.closeTimeout) {
      clearTimeout(this.closeTimeout);
      this.closeTimeout = null;
    }
  }
 
  private scheduleCloseIfNeeded(event: MouseEvent): void {
    if (this.shouldKeepMenuOpen(event.relatedTarget as Element)) {
      return;
    }
 
    this.scheduleMenuClose();
  }
 
  private shouldKeepMenuOpen(relatedTarget: Element | null): boolean {
    if (!relatedTarget) return false;
 
    const isMovingToTrigger = this.elementRef.nativeElement.contains(relatedTarget);
    const isMovingToMenu = relatedTarget.closest(ZardMenuDirective.MENU_CONTENT_SELECTOR);
    const isMovingToOtherTrigger =
      relatedTarget.matches('[z-menu]') && !this.elementRef.nativeElement.contains(relatedTarget);
 
    if (isMovingToOtherTrigger) {
      return false;
    }
 
    return isMovingToTrigger || !!isMovingToMenu;
  }
 
  private scheduleMenuClose(): void {
    this.closeTimeout = setTimeout(() => {
      this.cdkTrigger.close();
    }, this.zHoverDelay());
  }
 
  private addEventListenerWithCleanup(
    element: Element,
    eventType: string,
    handler: (event: MouseEvent | Event) => void,
    options?: AddEventListenerOptions,
  ): void {
    if (isPlatformBrowser(this.platformId)) {
      element.addEventListener(eventType, handler, options);
      this.cleanupFunctions.push(() => element.removeEventListener(eventType, handler, options));
    }
  }
 
  private isMobileDevice(): boolean {
    if (!isPlatformBrowser(this.platformId)) {
      return false; // Default to desktop behavior on server
    }
 
    // Check for touch support
    const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
 
    // Check for mobile user agent
    const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
    const isMobileUA = mobileRegex.test(navigator.userAgent);
 
    // Check viewport width for small screens
    const isSmallScreen = window.innerWidth <= 768;
 
    return hasTouch && (isMobileUA || isSmallScreen);
  }
}
 
menu.variants.ts
menu.variants.ts
import { cva, type VariantProps } from 'class-variance-authority';
 
export const menuContentVariants = cva(
  'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-2 text-popover-foreground shadow-lg animate-in data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
);
 
export const menuItemVariants = cva(
  'relative flex w-full cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-left [&>i]:mr-2 [&>z-icon]:mr-2',
  {
    variants: {
      inset: {
        true: 'pl-8',
        false: '',
      },
    },
    defaultVariants: {
      inset: false,
    },
  },
);
 
export type ZardMenuContentVariants = VariantProps<typeof menuContentVariants>;
export type ZardMenuItemVariants = VariantProps<typeof menuItemVariants>;
 
menu-content.directive.ts
menu-content.directive.ts
import { CdkMenu } from '@angular/cdk/menu';
import { computed, Directive, input } from '@angular/core';
 
import type { ClassValue } from 'clsx';
 
import { menuContentVariants } from './menu.variants';
import { mergeClasses } from '../../shared/utils/utils';
 
@Directive({
  selector: '[z-menu-content]',
  standalone: true,
  host: {
    '[class]': 'classes()',
  },
  hostDirectives: [CdkMenu],
})
export class ZardMenuContentDirective {
  readonly class = input<ClassValue>('');
 
  protected readonly classes = computed(() => mergeClasses(menuContentVariants(), this.class()));
}
 
menu-item.directive.ts
menu-item.directive.ts
import type { BooleanInput } from '@angular/cdk/coercion';
import { CdkMenuItem } from '@angular/cdk/menu';
import { booleanAttribute, computed, Directive, effect, inject, input, signal, untracked } from '@angular/core';
 
import type { ClassValue } from 'clsx';
 
import { menuItemVariants, type ZardMenuItemVariants } from './menu.variants';
import { mergeClasses } from '../../shared/utils/utils';
 
@Directive({
  selector: 'button[z-menu-item], [z-menu-item]',
  standalone: true,
  host: {
    '[class]': 'classes()',
    '[attr.data-orientation]': "'horizontal'",
    '[attr.data-state]': 'isOpenState()',
    '[attr.aria-disabled]': "disabledState() ? '' : undefined",
    '[attr.data-disabled]': "disabledState() ? '' : undefined",
    '[attr.data-highlighted]': "highlightedState() ? '' : undefined",
 
    '(focus)': 'onFocus()',
    '(blur)': 'onBlur()',
    '(pointermove)': 'onPointerMove($event)',
  },
  hostDirectives: [
    {
      directive: CdkMenuItem,
      outputs: ['cdkMenuItemTriggered: menuItemTriggered'],
    },
  ],
})
export class ZardMenuItemDirective {
  private readonly cdkMenuItem = inject(CdkMenuItem, { host: true });
 
  readonly zDisabled = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
  readonly zInset = input<ZardMenuItemVariants['inset']>(false);
  readonly class = input<ClassValue>('');
 
  private readonly isFocused = signal(false);
 
  protected readonly disabledState = computed(() => this.zDisabled());
 
  protected readonly isOpenState = computed(() => this.cdkMenuItem.isMenuOpen());
 
  protected readonly highlightedState = computed(() => this.isFocused());
 
  protected readonly classes = computed(() =>
    mergeClasses(
      menuItemVariants({
        inset: this.zInset(),
      }),
      this.class(),
    ),
  );
 
  constructor() {
    effect(() => {
      const disabled = this.zDisabled();
      untracked(() => {
        this.cdkMenuItem.disabled = disabled;
      });
    });
  }
 
  onFocus(): void {
    if (!this.zDisabled()) {
      this.isFocused.set(true);
    }
  }
 
  onBlur(): void {
    this.isFocused.set(false);
  }
 
  onPointerMove(event: PointerEvent) {
    if (event.defaultPrevented) return;
 
    if (!(event.pointerType === 'mouse')) return;
 
    if (!this.zDisabled()) {
      const item = event.currentTarget;
      (item as HTMLElement)?.focus({ preventScroll: true });
    }
  }
}
 
menu-manager.service.ts
menu-manager.service.ts
import { Injectable } from '@angular/core';
 
import type { ZardMenuDirective } from './menu.directive';
 
@Injectable({
  providedIn: 'root',
})
export class ZardMenuManagerService {
  private activeHoverMenu: ZardMenuDirective | null = null;
 
  registerHoverMenu(menu: ZardMenuDirective): void {
    if (this.activeHoverMenu && this.activeHoverMenu !== menu) {
      this.activeHoverMenu.close();
    }
    this.activeHoverMenu = menu;
  }
 
  unregisterHoverMenu(menu: ZardMenuDirective): void {
    if (this.activeHoverMenu === menu) {
      this.activeHoverMenu = null;
    }
  }
 
  closeActiveMenu(): void {
    if (this.activeHoverMenu) {
      this.activeHoverMenu.close();
      this.activeHoverMenu = null;
    }
  }
}
 
menu-positions.ts
menu-positions.ts
import type { ConnectedPosition } from '@angular/cdk/overlay';
 
export const MENU_POSITIONS_MAP: { [key: string]: ConnectedPosition[] } = {
  bottomLeft: [
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'top',
      offsetY: 8,
    },
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetY: -8,
    },
  ],
  bottomCenter: [
    {
      originX: 'center',
      originY: 'bottom',
      overlayX: 'center',
      overlayY: 'top',
      offsetY: 8,
    },
    {
      originX: 'center',
      originY: 'top',
      overlayX: 'center',
      overlayY: 'bottom',
      offsetY: -8,
    },
  ],
  bottomRight: [
    {
      originX: 'end',
      originY: 'bottom',
      overlayX: 'end',
      overlayY: 'top',
      offsetY: 8,
    },
    {
      originX: 'end',
      originY: 'top',
      overlayX: 'end',
      overlayY: 'bottom',
      offsetY: -8,
    },
  ],
  topLeft: [
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetY: -8,
    },
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'top',
      offsetY: 8,
    },
  ],
  topCenter: [
    {
      originX: 'center',
      originY: 'top',
      overlayX: 'center',
      overlayY: 'bottom',
      offsetY: -8,
    },
    {
      originX: 'center',
      originY: 'bottom',
      overlayX: 'center',
      overlayY: 'top',
      offsetY: 8,
    },
  ],
  topRight: [
    {
      originX: 'end',
      originY: 'top',
      overlayX: 'end',
      overlayY: 'bottom',
      offsetY: -8,
    },
    {
      originX: 'end',
      originY: 'bottom',
      overlayX: 'end',
      overlayY: 'top',
      offsetY: 8,
    },
  ],
  leftTop: [
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'end',
      overlayY: 'top',
      offsetX: -8,
    },
    {
      originX: 'end',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'top',
      offsetX: 8,
    },
  ],
  leftCenter: [
    {
      originX: 'start',
      originY: 'center',
      overlayX: 'end',
      overlayY: 'center',
      offsetX: -8,
    },
    {
      originX: 'end',
      originY: 'center',
      overlayX: 'start',
      overlayY: 'center',
      offsetX: 8,
    },
  ],
  leftBottom: [
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'end',
      overlayY: 'bottom',
      offsetX: -8,
    },
    {
      originX: 'end',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetX: 8,
    },
  ],
  rightTop: [
    {
      originX: 'end',
      originY: 'top',
      overlayX: 'start',
      overlayY: 'top',
      offsetX: 8,
    },
    {
      originX: 'start',
      originY: 'top',
      overlayX: 'end',
      overlayY: 'top',
      offsetX: -8,
    },
  ],
  rightCenter: [
    {
      originX: 'end',
      originY: 'center',
      overlayX: 'start',
      overlayY: 'center',
      offsetX: 8,
    },
    {
      originX: 'start',
      originY: 'center',
      overlayX: 'end',
      overlayY: 'center',
      offsetX: -8,
    },
  ],
  rightBottom: [
    {
      originX: 'end',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'bottom',
      offsetX: 8,
    },
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'end',
      overlayY: 'bottom',
      offsetX: -8,
    },
  ],
};
 
export type ZardMenuPlacement =
  | 'bottomLeft'
  | 'bottomCenter'
  | 'bottomRight'
  | 'topLeft'
  | 'topCenter'
  | 'topRight'
  | 'leftTop'
  | 'leftCenter'
  | 'leftBottom'
  | 'rightTop'
  | 'rightCenter'
  | 'rightBottom';
 
menu.module.ts
menu.module.ts
import { NgModule } from '@angular/core';
 
import { ZardMenuContentDirective } from './menu-content.directive';
import { ZardMenuItemDirective } from './menu-item.directive';
import { ZardMenuDirective } from './menu.directive';
 
const MENU_COMPONENTS = [ZardMenuContentDirective, ZardMenuItemDirective, ZardMenuDirective];
 
@NgModule({
  imports: [MENU_COMPONENTS],
  exports: [MENU_COMPONENTS],
})
export class ZardMenuModule {}
 

Examples

default

import { Component } from '@angular/core';
 
import { ZardButtonComponent } from '../../button/button.component';
import { ZardDividerComponent } from '../../divider/divider.component';
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardMenuModule } from '../menu.module';
 
@Component({
  selector: 'zard-demo-menu-default',
  imports: [ZardMenuModule, ZardButtonComponent, ZardDividerComponent, ZardIconComponent],
  standalone: true,
  template: `
    <nav class="flex items-center justify-between p-4">
      <div class="flex items-center space-x-6">
        <div class="flex items-center space-x-1">
          <div class="relative">
            <button z-button zType="ghost" z-menu zTrigger="hover" [zMenuTriggerFor]="productsMenu">
              Products
              <z-icon zType="chevron-down" class="ml-1" />
            </button>
 
            <ng-template #productsMenu>
              <div z-menu-content class="w-48">
                <button z-menu-item (click)="log('Analytics')">Analytics</button>
                <button z-menu-item (click)="log('Dashboard')">Dashboard</button>
                <button z-menu-item (click)="log('Reports')">Reports</button>
                <button z-menu-item zDisabled (click)="log('Insights')">Insights</button>
              </div>
            </ng-template>
          </div>
 
          <div class="relative">
            <button z-button zType="ghost" z-menu zTrigger="hover" [zMenuTriggerFor]="solutionsMenu">
              Solutions
              <z-icon zType="chevron-down" class="ml-1" />
            </button>
 
            <ng-template #solutionsMenu>
              <div z-menu-content class="w-80 p-2">
                <div class="grid gap-1">
                  <button z-menu-item (click)="log('For Startups')" class="flex h-auto flex-col items-start py-3">
                    <div class="text-sm font-medium">For Startups</div>
                    <div class="text-muted-foreground mt-1 text-xs">
                      Get started quickly with our startup-friendly tools
                    </div>
                  </button>
 
                  <button z-menu-item (click)="log('For Enterprise')" class="flex h-auto flex-col items-start py-3">
                    <div class="text-sm font-medium">For Enterprise</div>
                    <div class="text-muted-foreground mt-1 text-xs">
                      Scale your business with enterprise-grade features
                    </div>
                  </button>
 
                  <button z-menu-item (click)="log('For Agencies')" class="flex h-auto flex-col items-start py-3">
                    <div class="text-sm font-medium">For Agencies</div>
                    <div class="text-muted-foreground mt-1 text-xs">Manage multiple clients with our agency tools</div>
                  </button>
                </div>
              </div>
            </ng-template>
          </div>
 
          <div class="relative">
            <button z-button zType="ghost" z-menu zTrigger="hover" [zMenuTriggerFor]="resourcesMenu">
              Resources
              <z-icon zType="chevron-down" />
            </button>
 
            <ng-template #resourcesMenu>
              <div z-menu-content class="w-56">
                <button z-menu-item (click)="log('Blog')">
                  <z-icon zType="book-open" class="mr-2" />
                  Blog
                </button>
 
                <button z-menu-item (click)="log('Documentation')">
                  <z-icon zType="file-text" class="mr-2" />
                  Documentation
                </button>
 
                <button
                  z-menu-item
                  z-menu
                  [zMenuTriggerFor]="helpSubmenu"
                  zPlacement="rightTop"
                  class="justify-between"
                >
                  <div class="flex items-center"><z-icon zType="info" class="mr-2" /> Help & Support</div>
                  <z-icon zType="chevron-right" />
                </button>
 
                <z-divider zSpacing="sm" />
 
                <button z-menu-item (click)="log('Community')">
                  <z-icon zType="users" class="mr-2" />
                  Community
                </button>
              </div>
            </ng-template>
 
            <ng-template #helpSubmenu>
              <div z-menu-content class="w-48">
                <button z-menu-item (click)="log('Getting Started')">Getting Started</button>
                <button z-menu-item (click)="log('Tutorials')">Tutorials</button>
                <button z-menu-item (click)="log('FAQ')">FAQ</button>
 
                <z-divider zSpacing="sm" />
 
                <button z-menu-item (click)="log('Contact Support')">Contact Support</button>
                <button z-menu-item (click)="log('Live Chat')">Live Chat</button>
              </div>
            </ng-template>
          </div>
        </div>
      </div>
    </nav>
  `,
})
export class ZardDemoMenuDefaultComponent {
  log(item: string) {
    console.log('Navigate to:', item);
  }
}
 

Menu API Reference

Directives

z-menu

The trigger directive that opens the menu when interacted with.

Property Description Type Default
[zMenuTriggerFor] Reference to the menu template TemplateRef required
[zDisabled] Whether the trigger is disabled boolean false
[zTrigger] How the menu is triggered 'click' | 'hover' 'click'
[zHoverDelay] Delay in ms before closing on hover exit number 100
[zPlacement] Menu position relative to trigger ZardMenuPlacement 'bottomLeft'

z-menu-content

Container directive for menu items.

Property Description Type Default
[class] Additional CSS classes ClassValue ''

z-menu-item

Individual menu item directive.

Property Description Type Default
[zDisabled] Whether the item is disabled boolean false
[zInset] Add left padding for alignment boolean false
[class] Additional CSS classes ClassValue ''
[menuItemTriggered] Emits when item is clicked EventEmitter