Angular CDK's Overlay module provides a powerful way to create floating panels that appear over your application's content. These overlays are commonly used for dropdowns, tooltips, modals, and context menus. While overlays work great out of the box, adding smooth enter and exit animations can significantly enhance the user experience.
In this article, we'll explore how to implement sophisticated enter/exit animations with Angular CDK Overlay using signals, dynamic animation classes and understand the true power of new Angular Animations's APIs.
What is Angular CDK Overlay?
The Angular CDK (Component Dev Kit) Overlay is a module that allows you to open floating panels on top of the current page. Unlike traditional CSS positioning, the Overlay module handles:
- Positioning: Automatically positions the overlay relative to a trigger element
- Repositioning: Dynamically adjusts position when the overlay doesn't fit in the viewport
- Backdrop Management: Provides optional backdrop with click-outside handling
- Z-Index Management: Manages stacking context for multiple overlays
- Scroll Handling: Repositions or closes overlays on scroll
Prerequisites
Before we begin, ensure you have Angular CDK installed:
ng add @angular/cdk
And the basic styles for the overlay are imported correctly in your application:
@import '@angular/cdk/overlay-prebuilt.css';
Creating the Overlay Wrapper Component
Let's build a reusable overlay wrapper component that handles animations automatically based on the overlay's position.
Component Structure
First, let's create the component with signals for reactive state management:
import {
CdkConnectedOverlay,
CdkOverlayOrigin,
STANDARD_DROPDOWN_BELOW_POSITIONS,
ConnectedOverlayPositionChange,
ConnectedPosition,
} from '@angular/cdk/overlay';
import {
Component,
ChangeDetectionStrategy,
input,
computed,
signal,
} from '@angular/core';
@Component({
selector: 'app-overlay-wrapper',
templateUrl: './overlay-wrapper.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CdkConnectedOverlay],
})
export class OverlayWrapperComponent {
// Input signals for component configuration
readonly trigger = input.required<CdkOverlayOrigin>();
readonly positions = input<ConnectedPosition[]>([]);
readonly panelClass = input<string>('');
// State signals
readonly isOpen = signal(false);
readonly animationDirection = signal<'up' | 'down'>('down');
// Computed animation classes based on direction
readonly enterAnimationClass = computed(() => {
const direction = this.animationDirection();
return direction === 'up' ? 'animate-fade-in-up' : 'animate-fade-in-down';
});
readonly leaveAnimationClass = computed(() => {
const direction = this.animationDirection();
return direction === 'up' ? 'animate-fade-out-down' : 'animate-fade-out-up';
});
// Computed overlay positions with offset
readonly overlayPositions = computed(() =>
STANDARD_DROPDOWN_BELOW_POSITIONS.concat(this.positions()).map(item => ({
...item,
offsetY: item.overlayY === 'bottom' ? -8 : 8,
}))
);
toggleOverlay(): void {
this.isOpen.update(value => !value);
}
close(): void {
this.isOpen.set(false);
}
onPositionChange(event: ConnectedOverlayPositionChange): void {
this.animationDirection.set(
event.connectionPair.overlayY === 'bottom' ? 'up' : 'down'
);
}
}
Key Features Explained
- Signals for Reactive State: We use Angular signals for state management, making the component reactive and efficient.
- Dynamic Animation Classes: The component automatically determines whether the overlay opens upward or downward and applies appropriate animation classes.
-
Position Tracking: The
onPositionChangemethod tracks the overlay's position, allowing us to adjust animations accordingly. - Flexible Configuration: Input signals allow customization of trigger element, positions, and panel classes.
Instead of
STANDARD_DROPDOWN_BELOW_POSITIONS, you can also useSTANDARD_DROPDOWN_ADJACENT_POSITIONSto position the overlay adjacent to the trigger element. To learn more about how positioning works, see the official Angular CDK Overlay documentation.
Template Implementation
Now let's create the template that uses the CDK Connected Overlay:
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger()"
[cdkConnectedOverlayOpen]="isOpen()"
[cdkConnectedOverlayPositions]="overlayPositions()"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
[cdkConnectedOverlayPanelClass]="panelClass()"
(backdropClick)="close()"
(positionChange)="onPositionChange($event)">
<div
[animate.enter]="enterAnimationClass()"
[animate.leave]="leaveAnimationClass()">
<ng-content />
</div>
</ng-template>
Template Features
- cdkConnectedOverlay: The main directive that creates the overlay
- cdkConnectedOverlayOrigin: References the trigger element
- cdkConnectedOverlayOpen: Controls overlay visibility
- cdkConnectedOverlayPositions: Array of preferred positions
- cdkConnectedOverlayHasBackdrop: Enables backdrop for click-outside detection
-
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'": Sets the backdrop class to transparent. Note that
cdk-overlay-transparent-backdropis a class that is provided by the Angular CDK library.. -
Content Projection:
<ng-content />allows flexible content insertion
The real power of new Angular Animations APIs
animate.enter and animate.leave are Angular's built-in animation APIs that apply CSS animation classes to elements during enter and exit transitions. These APIs are designed to apply animation classes only when elements are being added to or removed from the DOM.
The key benefit is that Angular automatically manages the element's lifecycle during animations. When using animate.leave, Angular waits for the animation to complete before removing the element from the DOM. In our case, this means the overlay content remains visible throughout the exit animation, and the framework handles the timing automatically—we don't need to write any extra code to delay the destruction or manually remove elements. This is all managed by Angular's animation system.
For more details, see the official Angular animations guide.
CSS Animation Classes
Now, let's implement the CSS animation classes. These animations provide smooth fade and slide effects:
/* Base animation configuration */
.animate-fade-in-up,
.animate-fade-in-down,
.animate-fade-out-up,
.animate-fade-out-down {
animation-duration: 200ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
animation-fill-mode: both;
}
/* Fade in from bottom (sliding up) */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation-name: fadeInUp;
}
/* Fade in from top (sliding down) */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-down {
animation-name: fadeInDown;
}
/* Fade out to bottom (sliding down) */
@keyframes fadeOutDown {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(10px);
}
}
.animate-fade-out-down {
animation-name: fadeOutDown;
}
/* Fade out to top (sliding up) */
@keyframes fadeOutUp {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-10px);
}
}
.animate-fade-out-up {
animation-name: fadeOutUp;
}
Animation Details
- Duration: 200ms provides a snappy but smooth animation
-
Timing Function:
cubic-bezier(0.4, 0, 0.2, 1)creates a natural ease-out effect - Transform Distance: 10px vertical movement is subtle yet noticeable
- Opacity Transition: Fades from 0 to 1 (enter) or 1 to 0 (exit)
Using the Overlay Wrapper
Here's how to use the overlay wrapper component in your application:
import { Component, viewChild } from '@angular/core';
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
import { OverlayWrapperComponent } from './overlay-wrapper.component';
@Component({
selector: 'app-dropdown-example',
template: `
<button
#trigger="cdkOverlayOrigin"
cdkOverlayOrigin
(click)="overlay.toggleOverlay()">
Open Menu
</button>
<app-overlay-wrapper
[trigger]="trigger"
#overlay>
<div class="menu-content">
<button (click)="overlay.close()">Option 1</button>
<button (click)="overlay.close()">Option 2</button>
<button (click)="overlay.close()">Option 3</button>
</div>
</app-overlay-wrapper>
`,
imports: [CdkOverlayOrigin, OverlayWrapperComponent],
})
export class DropdownExampleComponent {
overlay = viewChild.required(OverlayWrapperComponent);
}
Additional Styling
Add some basic styling for your overlay content:
.dropdown-panel {
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 200px;
}
.menu-content {
padding: 8px 0;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.menu-content button {
display: block;
width: 100%;
padding: 12px 16px;
text-align: left;
border: none;
background: none;
cursor: pointer;
transition: background-color 150ms;
}
.menu-content button:hover {
background-color: rgba(0, 0, 0, 0.04);
}
Looking for more?
Head out to Flyout Menus by Angular Material Blocks for a more advanced implementation of flyout menus with Angular CDK Overlay.
Conclusion
Implementing enter/exit animations with Angular CDK Overlay enhances the user experience by providing visual feedback and smooth transitions. By combining Angular's signals with CDK's positioning system, we've created a reusable component that:
- Automatically adjusts animations based on overlay position
- Provides a clean, reactive API using signals
- Supports flexible configuration through input signals
- Maintains performance with OnPush change detection
The approach demonstrated here can be adapted for various use cases including dropdowns, tooltips, context menus, and custom floating panels. The key is to keep animations subtle, performant, and purposeful.
Happy coding! 🚀
Live Playground

Top comments (0)