Angular animations turn state changes into timed transitions and keyframes so UI motion feels deliberate instead of abrupt.
Focus on animation triggers, states, transitions, and the browser behaviors that make motion appear smooth or fail silently.
A strong understanding of animations should include how triggers map to states and how timing and style changes are combined.
Angular Animations animate.enter animate.leave CSS should be studied as a practical Angular lesson, not as a label. Start by naming the input, the rule that changes the input, and the result a learner should be able to predict after reading the page.
In the angular > animations page, the notes should connect the definition with a working scenario, a mistake that beginners actually make, and the exact check that proves the fix. That makes the topic useful for coding, debugging, and interview revision.
Animations make an Angular application feel smoother and easier to understand. A good animation explains that something has entered the screen, left the screen, changed state, expanded, collapsed, loaded, or moved because of the user's action. The goal is not to decorate every element. The goal is to guide attention and make interface changes feel intentional.
Modern Angular recommends using CSS-based animations with animate.enter and animate.leave for new code. These APIs let Angular add animation classes at the right moment while CSS handles the actual motion. Older Angular projects may still use the @angular/animations package with trigger(), state(), and transition(), so this tutorial covers both approaches.
Animations should be short, purposeful, and predictable. Most UI animations work best between 120ms and 300ms. Slower animations are useful for large layout transitions, but they can make everyday actions feel delayed if overused.
For new Angular applications, prefer animate.enter and animate.leave. They are compiler-supported animation bindings that work with CSS transitions or CSS keyframes. You do not need to import BrowserAnimationsModule or call provideAnimationsAsync() just to use these modern enter and leave animations.
The mental model is simple: Angular controls when the class is applied, and CSS controls how the element moves. When the animation finishes, Angular removes the temporary animation class.
This example uses modern control flow with @if. When show() becomes true, Angular adds the element to the DOM and applies the notice-enter class while the animation runs.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-notice',
standalone: true,
template: `
<button type="button" (click)="show.set(!show())">
Toggle notice
</button>
@if (show()) {
<div class="notice" animate.enter="notice-enter">
Profile saved successfully.
</div>
}
`,
styleUrl: './notice.component.css'
})
export class NoticeComponent {
show = signal(false);
}
.notice {
margin-top: 12px;
padding: 12px 14px;
border-left: 4px solid #16a34a;
background: #f0fdf4;
color: #166534;
}
.notice-enter {
animation: slide-fade-in 220ms ease-out;
}
@keyframes slide-fade-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Most real UI elements need both an entrance and an exit. Use animate.enter for the class that runs when the element is inserted, and animate.leave for the class that runs before Angular removes the element.
The forwards value in the leave animation keeps the final hidden style while Angular waits for the animation to finish and then removes the element from the DOM.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-toast-demo',
standalone: true,
template: `
<button type="button" (click)="visible.set(true)">Show toast</button>
<button type="button" (click)="visible.set(false)">Hide toast</button>
@if (visible()) {
<div
class="toast"
animate.enter="toast-enter"
animate.leave="toast-leave"
>
New message received.
</div>
}
`,
styleUrl: './toast.component.css'
})
export class ToastDemoComponent {
visible = signal(false);
}
.toast {
width: fit-content;
margin-top: 12px;
padding: 10px 14px;
border-radius: 6px;
background: #111827;
color: white;
box-shadow: 0 8px 24px rgba(15, 23, 42, .18);
}
.toast-enter {
animation: toast-in 180ms ease-out;
}
.toast-leave {
animation: toast-out 150ms ease-in forwards;
}
@keyframes toast-in {
from { opacity: 0; transform: translateY(10px) scale(.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes toast-out {
from { opacity: 1; transform: translateY(0) scale(1); }
to { opacity: 0; transform: translateY(8px) scale(.98); }
}
Keyframes are good for precise multi-step motion. CSS transitions are better for simple changes between two states. With animate.enter, the animation class can define the final state and a starting style can be provided with @starting-style.
@if (open()) {
<section class="panel" animate.enter="panel-enter">
<h4>Account details</h4>
<p>This panel fades and slides into place.</p>
</section>
}
.panel {
padding: 16px;
border: 1px solid #dbeafe;
background: #eff6ff;
}
.panel-enter {
opacity: 1;
transform: translateY(0);
transition: opacity 180ms ease-out, transform 180ms ease-out;
@starting-style {
opacity: 0;
transform: translateY(10px);
}
}
animate.enter and animate.leave can accept a string or an expression. This is useful when a component supports different motion styles, such as sliding from the left or right depending on the action.
The matching CSS classes would define different transforms, for example translateX(-16px) for a left drawer and translateX(16px) for a right drawer.
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-drawer',
standalone: true,
template: `
<button type="button" (click)="side.set('left'); open.set(true)">Left</button>
<button type="button" (click)="side.set('right'); open.set(true)">Right</button>
<button type="button" (click)="open.set(false)">Close</button>
@if (open()) {
<aside class="drawer" [animate.enter]="enterClass()" animate.leave="drawer-leave">
Drawer from the {{ side() }} side.
</aside>
}
`
})
export class DrawerComponent {
open = signal(false);
side = signal<'left' | 'right'>('left');
enterClass = computed(() => this.side() === 'left' ? 'drawer-left' : 'drawer-right');
}
Lists are one of the best places to use animations because adding and removing rows can otherwise feel abrupt. With modern Angular, use @for for rendering and animate.enter / animate.leave on each repeated item.
import { Component, signal } from '@angular/core';
type Task = { id: number; title: string };
@Component({
selector: 'app-tasks',
standalone: true,
template: `
<button type="button" (click)="addTask()">Add task</button>
<ul class="task-list">
@for (task of tasks(); track task.id) {
<li class="task" animate.enter="task-enter" animate.leave="task-leave">
<span>{{ task.title }}</span>
<button type="button" (click)="removeTask(task.id)">Done</button>
</li>
} @empty {
<li class="empty">No tasks left.</li>
}
</ul>
`,
styleUrl: './tasks.component.css'
})
export class TasksComponent {
private nextId = 3;
tasks = signal<Task[]>([
{ id: 1, title: 'Create animation classes' },
{ id: 2, title: 'Keep transitions short' }
]);
addTask() {
this.tasks.update(items => [
...items,
{ id: this.nextId, title: `Task ${this.nextId++}` }
]);
}
removeTask(id: number) {
this.tasks.update(items => items.filter(task => task.id !== id));
}
}
.task-list {
display: grid;
gap: 8px;
padding: 0;
list-style: none;
}
.task {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid #e5e7eb;
border-radius: 6px;
}
.task-enter {
animation: task-in 180ms ease-out;
}
.task-leave {
animation: task-out 140ms ease-in forwards;
}
@keyframes task-in {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes task-out {
from { opacity: 1; transform: translateX(0); }
to { opacity: 0; transform: translateX(10px); }
}
Not every animation needs animate.enter or animate.leave. If an element remains in the DOM and only changes state, use class binding, style binding, or CSS transitions.
This is often the cleanest approach for accordions, menus, badges, and validation states because Angular only needs to toggle a class.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-accordion',
standalone: true,
template: `
<button type="button" (click)="open.update(value => !value)">
Toggle details
</button>
<div class="details" [class.details-open]="open()">
<p>This content expands with a CSS transition.</p>
</div>
`,
styleUrl: './accordion.component.css'
})
export class AccordionComponent {
open = signal(false);
}
.details {
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 240ms ease, opacity 180ms ease;
}
.details-open {
max-height: 160px;
opacity: 1;
}
Animations should respect users who prefer reduced motion. CSS makes this straightforward with the prefers-reduced-motion media query. In reduced-motion mode, keep important visual state changes but remove large movement and long animation durations.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
transition-duration: 1ms !important;
scroll-behavior: auto !important;
}
}
Older Angular tutorials and many existing projects use the @angular/animations package. In this model, animations are written in TypeScript using trigger(), state(), style(), animate(), and transition(). Angular has deprecated this package for new animation work, but understanding it is still useful when maintaining older apps.
If an existing app still uses this package, standalone applications typically enable it with provideAnimationsAsync() in application configuration. NgModule-based applications often use BrowserAnimationsModule.
import { ApplicationConfig } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAnimationsAsync()
]
};
import { Component, signal } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'app-expand-card',
standalone: true,
animations: [
trigger('expandCollapse', [
state('open', style({
height: '*',
opacity: 1
})),
state('closed', style({
height: '0',
opacity: 0,
overflow: 'hidden'
})),
transition('open <=> closed', [
animate('220ms ease-in-out')
])
])
],
template: `
<button type="button" (click)="open.update(value => !value)">
Toggle card
</button>
<section [@expandCollapse]="open() ? 'open' : 'closed'">
<p>This uses the legacy Angular animations package.</p>
</section>
`
})
export class ExpandCardComponent {
open = signal(false);
}
The legacy package uses state expressions to decide which transition should run. These are common expressions you will see in existing Angular code:
| Need | Recommended approach |
|---|---|
| Element appears or disappears | animate.enter and animate.leave |
| Simple hover, focus, selected, or expanded state | CSS transitions with Angular class binding |
| Repeated list item insertion/removal | @for with animate.enter / animate.leave on each item |
| Maintaining older trigger/state code | Legacy @angular/animations APIs |
| Complex timeline or physics animation | CSS keyframes or a dedicated animation library |
In modern Angular, start with CSS and use animate.enter and animate.leave when Angular needs to coordinate DOM insertion or removal. Use normal class bindings and CSS transitions for elements that remain on the page and only change state. Learn the older @angular/animations trigger syntax so you can maintain existing code, but prefer the modern CSS-based approach for new Angular applications.
const state = { topic: "Angular Animations animate.enter animate.leave CSS", ready: true };
if (state.ready) {
console.log(state.topic + ": render or run the normal path");
}
const response = null;
const message = response?.message ?? "Angular Animations animate.enter animate.leave CSS: show a clear fallback";
console.log(message);
Memorizing Angular Animations animate.enter animate.leave CSS without the situation where it is useful.
Connect Angular Animations animate.enter animate.leave CSS to a concrete Angular task.
Testing Angular Animations animate.enter animate.leave CSS only with the perfect input.
Include empty, missing, duplicate, incompatible, or failed cases when relevant.
Changing code before reading the visible symptom or error message.
Inspect the output, state, configuration, or stack trace connected to Angular Animations animate.enter animate.leave CSS.
Memorizing Angular Animations animate.enter animate.leave CSS without the situation where it is useful.
Connect Angular Animations animate.enter animate.leave CSS to a concrete Angular task.
No. For modern <code>animate.enter</code> and <code>animate.leave</code> animations, write CSS classes and bind them in the template. <code>BrowserAnimationsModule</code> and <code>provideAnimationsAsync()</code> are used for the older <code>@angular/animations</code> package.
<code>animate.enter</code> applies an animation class when Angular inserts an element into the DOM. <code>animate.leave</code> applies an animation class before Angular removes the element from the DOM.
Use CSS transitions for simple before-and-after state changes. Use keyframes when the motion needs named stages, a custom path, or more precise timing.
Yes, if you maintain older Angular applications. Many real projects still use <code>trigger()</code>, <code>state()</code>, and <code>transition()</code>, so recognizing the syntax is practical even though new code should usually prefer the modern CSS-based approach.
Explore 500+ free tutorials across 20+ languages and frameworks.