diff --git a/package-lock.json b/package-lock.json
index 8183fe042..229012d24 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -390,6 +390,14 @@
         }
       }
     },
+    "@angular/animations": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.0.1.tgz",
+      "integrity": "sha512-RS2ZsO3yidn/dMAllR+V0EX5BOQLQDi5s2kvd4wANHYAkU/yVXWKl09nbe8LTwLVH+iOYX7AAcAUUokQPEEHxQ==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
     "@angular/cli": {
       "version": "10.0.8",
       "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-10.0.8.tgz",
@@ -6514,7 +6522,7 @@
       "integrity": "sha512-EYC5eQFVkoYXq39l7tYKE6lEjHJ04mvTmKXxGL7quHLdFPfJMNzru/UYpn92AOfpl3PQaZmou78C7EgmFOwFQQ=="
     },
     "cordova-plugin-wkuserscript": {
-      "version": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git#aa77d0f98a3fb106f2e798e5adf5882f01a2c947",
+      "version": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git#6413f4bb3c2565f353e690b5c1450b69ad9e860e",
       "from": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git"
     },
     "cordova-plugin-wkwebview-cookies": {
diff --git a/package.json b/package.json
index 22bee0290..d85aa2a26 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
     "ionic:build:before": "npx gulp"
   },
   "dependencies": {
+    "@angular/animations": "^11.0.1",
     "@angular/common": "~10.0.0",
     "@angular/core": "~10.0.0",
     "@angular/forms": "~10.0.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 0206d2dde..b21e7da51 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -14,6 +14,7 @@
 
 import { NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouteReuseStrategy } from '@angular/router';
 import { HttpClient, HttpClientModule } from '@angular/common/http';
 
@@ -38,6 +39,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
     entryComponents: [],
     imports: [
         BrowserModule,
+        BrowserAnimationsModule,
         IonicModule.forRoot(),
         HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
         TranslateModule.forRoot({
diff --git a/src/core/classes/animations.ts b/src/core/classes/animations.ts
new file mode 100644
index 000000000..510ef6d88
--- /dev/null
+++ b/src/core/classes/animations.ts
@@ -0,0 +1,65 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { trigger, style, transition, animate, keyframes } from '@angular/animations';
+
+export const coreShowHideAnimation = trigger('coreShowHideAnimation', [
+    transition(':enter', [
+        style({ opacity: 0 }),
+        animate('500ms ease-in-out', style({ opacity: 1 })),
+    ]),
+    transition(':leave', [
+        style({ opacity: 1 }),
+        animate('500ms ease-in-out', style({ opacity: 0 })),
+    ]),
+]);
+
+export const coreSlideInOut = trigger('coreSlideInOut', [
+    // Enter animation.
+    transition('void => fromLeft', [
+        style({ transform: 'translateX(0)', opacity: 1 }),
+        animate(300, keyframes([
+            style({ opacity: 0, transform: 'translateX(-100%)', offset: 0 }),
+            style({ opacity: 1, transform: 'translateX(5%)',  offset: 0.7 }),
+            style({ opacity: 1, transform: 'translateX(0)',     offset: 1.0 }),
+        ])),
+    ]),
+    // Leave animation.
+    transition('fromLeft => void', [
+        style({ transform: 'translateX(-100%)', opacity: 0 }),
+        animate(300, keyframes([
+            style({ opacity: 1, transform: 'translateX(0)', offset: 0 }),
+            style({ opacity: 1, transform: 'translateX(5%)',  offset: 0.3 }),
+            style({ opacity: 0, transform: 'translateX(-100%)',     offset: 1.0 }),
+        ])),
+    ]),
+    // Enter animation.
+    transition('void => fromRight', [
+        style({ transform: 'translateX(0)', opacity: 1 }),
+        animate(300, keyframes([
+            style({ opacity: 0, transform: 'translateX(100%)',     offset: 0 }),
+            style({ opacity: 1, transform: 'translateX(-5%)', offset: 0.7 }),
+            style({ opacity: 1, transform: 'translateX(0)',  offset: 1.0 }),
+        ])),
+    ]),
+    // Leave animation.
+    transition('fromRight => void', [
+        style({ transform: 'translateX(-100%)', opacity: 0 }),
+        animate(300, keyframes([
+            style({ opacity: 1, transform: 'translateX(0)', offset: 0 }),
+            style({ opacity: 1, transform: 'translateX(-5%)',  offset: 0.3 }),
+            style({ opacity: 0, transform: 'translateX(100%)',     offset: 1.0 }),
+        ])),
+    ]),
+]);
diff --git a/src/core/components/download-refresh/core-download-refresh.html b/src/core/components/download-refresh/core-download-refresh.html
index a58e42784..c70f87a0e 100644
--- a/src/core/components/download-refresh/core-download-refresh.html
+++ b/src/core/components/download-refresh/core-download-refresh.html
@@ -1,13 +1,13 @@