How To Realign MatMenu Using Custom Directive In Angular.

If you’ve ever worked with Angular Material’s mat-menu and run into alignment issues—especially in dynamic layouts—you’re not alone. Sometimes the menu just doesn’t line up with the trigger element the way you want, or worse, it breaks on small screens or bubbles up unnecessary key events.

That’s exactly why I built the MatMenuRealignDirective: a small but mighty custom directive that gives you precise control over menu alignment and event handling.


🧩 Why Bother With a Custom Directive?

Angular Material does a solid job with auto-positioning out of the box—but in some edge cases, it falls short:

  • The menu appears misaligned due to nested or dynamic UI.
  • You need a specific maxWidth for better responsiveness.
  • Keyboard events inside the menu (like Enter, Escape) leak out and trigger unintended actions.
  • MDC-based updates (from Angular Material v15+) mess with class names and layout.

This directive was built to tackle those pain points with a clean and reusable solution.


🔧 The Directive in Action

Here’s what the directive looks like under the hood:

import { Directive, HostListener, Input } from '@angular/core';

@Directive({
selector: '[matMenuRealign]',
standalone: false,
})
export class MatMenuRealignDirective {
@Input() label: HTMLElement = null;
@Input() field: HTMLElement = null;
@Input() offsetLeft: number = 0;
@Input() offsetTop: number = 0;
@Input() maxWidth: number | 'none' = null;
@Input() stopKeyEventBubbling: boolean = false;

@HostListener('menuOpened') onClick() {
this.realignMenu(this.label, this.field);
}

private realignMenu(label: HTMLElement, field: HTMLElement): void {
if (!field) return;

const menuContainer = field.closest('.mat-mdc-menu-panel') as HTMLElement;
const menuContent = field.closest('.mat-mdc-menu-content') as HTMLElement;

if (menuContainer) {
menuContainer.style.position = 'relative';
menuContainer.style.visibility = 'hidden';

if (this.stopKeyEventBubbling) {
['onkeyup', 'onkeydown', 'onkeypress'].forEach(event => {
menuContent[event] = ev => ev?.stopPropagation();
});
}

if (this.maxWidth) {
menuContainer.style.maxWidth = this.maxWidth === 'none' ? 'none' : `${this.maxWidth}px`;
menuContainer.style.width = `100vw`;
}

setTimeout(() => {
if (label && field) {
const topDiff = label.getBoundingClientRect().top - field.getBoundingClientRect().top - 3 + this.offsetTop;
const leftDiff = label.getBoundingClientRect().left - field.getBoundingClientRect().left + this.offsetLeft;

menuContainer.style.top = `${topDiff}px`;
menuContainer.style.left = `${leftDiff}px`;
menuContainer.style.visibility = 'visible';
}
}, 150);
}
}
}

💡 What This Directive Solves

✅ Accurate Positioning

It adjusts the menu’s alignment relative to two anchor elements (label and field). You can tweak the positioning using offsetLeft and offsetTop.

✅ Keyboard Event Isolation

It prevents key events like Enter, ArrowDown, or Escape from escaping the menu when stopKeyEventBubbling is set to true.

✅ Responsive Width Control

Need a dynamic or fixed max width? Just pass in maxWidth (e.g., 300, or 'none') and the menu container will adapt accordingly.

✅ MDC Compatibility

It plays nicely with Material’s new MDC class names (mat-mdc-menu-panel, etc.), so you’re safe post Angular Material v15.


🧠 How to Use It

1. Import and Declare

If you’re not going fully standalone, add the directive to your SharedModule:

import { MatMenuRealignDirective } from './directives/mat-menu-realign.directive';

@NgModule({
declarations: [MatMenuRealignDirective],
exports: [MatMenuRealignDirective],
})
export class SharedModule {}

2. Apply in Your Template

<!-- Assume labelElement and fieldElement are references set in the component -->
<button
mat-button
[matMenuTriggerFor]="menu"
[matMenuRealign]="{
label: labelElement,
field: fieldElement,
offsetLeft: 10,
offsetTop: 5,
stopKeyEventBubbling: true
}"
>
Open Menu
</button>

<mat-menu #menu="matMenu">
<button mat-menu-item>Option 1</button>
<button mat-menu-item>Option 2</button>
</mat-menu>

3. Bind Your References in Component

export class ExampleComponent {
@ViewChild('labelElement', { static: true }) labelElement: ElementRef;
@ViewChild('fieldElement', { static: true }) fieldElement: ElementRef;
}

Tip: Use #labelElement and #fieldElement on the actual DOM elements in the template to make this work.


📌 Final Thoughts

If Angular Material’s menu alignment has been giving you grief, this custom MatMenuRealignDirective might just be the solution you need. It’s lightweight, flexible, and super easy to plug into any project.

Whether you’re fine-tuning positioning, fixing layout bugs, or preventing keyboard chaos—this directive has your back.


🚀 Try It Out!

Drop it into your project, tweak the settings, and take control of your menus like a pro. And if you come up with improvements, feel free to fork it or suggest changes—I’d love to see what you build.

Want a version of this as a plug-and-play NPM package? Just let me know!

Leave a Comment

Your email address will not be published. Required fields are marked *