diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js
index 781f90860..514a6c138 100644
--- a/resources/js/components/dropdown.js
+++ b/resources/js/components/dropdown.js
@@ -1,4 +1,5 @@
 import {onSelect} from "../services/dom";
+import {KeyboardNavigationHandler} from "../services/keyboard-navigation";
 
 /**
  * Dropdown
@@ -17,8 +18,9 @@ class DropDown {
         this.direction = (document.dir === 'rtl') ? 'right' : 'left';
         this.body = document.body;
         this.showing = false;
-        this.setupListeners();
+
         this.hide = this.hide.bind(this);
+        this.setupListeners();
     }
 
     show(event = null) {
@@ -52,7 +54,7 @@ class DropDown {
         }
 
         // Set listener to hide on mouse leave or window click
-        this.menu.addEventListener('mouseleave', this.hide.bind(this));
+        this.menu.addEventListener('mouseleave', this.hide);
         window.addEventListener('click', event => {
             if (!this.menu.contains(event.target)) {
                 this.hide();
@@ -97,33 +99,25 @@ class DropDown {
         this.showing = false;
     }
 
-    getFocusable() {
-        return Array.from(this.menu.querySelectorAll('[tabindex]:not([tabindex="-1"]),[href],button,input:not([type=hidden])'));
-    }
-
-    focusNext() {
-        const focusable = this.getFocusable();
-        const currentIndex = focusable.indexOf(document.activeElement);
-        let newIndex = currentIndex + 1;
-        if (newIndex >= focusable.length) {
-            newIndex = 0;
-        }
-
-        focusable[newIndex].focus();
-    }
-
-    focusPrevious() {
-        const focusable = this.getFocusable();
-        const currentIndex = focusable.indexOf(document.activeElement);
-        let newIndex = currentIndex - 1;
-        if (newIndex < 0) {
-            newIndex = focusable.length - 1;
-        }
-
-        focusable[newIndex].focus();
-    }
-
     setupListeners() {
+        const keyboardNavHandler = new KeyboardNavigationHandler(this.container, (event) => {
+            this.hide();
+            this.toggle.focus();
+            if (!this.bubbleEscapes) {
+                event.stopPropagation();
+            }
+        }, (event) => {
+            if (event.target.nodeName === 'INPUT') {
+                event.preventDefault();
+                event.stopPropagation();
+            }
+            this.hide();
+        });
+
+        if (this.moveMenu) {
+            keyboardNavHandler.shareHandlingToEl(this.menu);
+        }
+
         // Hide menu on option click
         this.container.addEventListener('click', event => {
              const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
@@ -136,37 +130,7 @@ class DropDown {
             event.stopPropagation();
             this.show(event);
             if (event instanceof KeyboardEvent) {
-                this.focusNext();
-            }
-        });
-
-        // Keyboard navigation
-        const keyboardNavigation = event => {
-            if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
-                this.focusNext();
-                event.preventDefault();
-            } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
-                this.focusPrevious();
-                event.preventDefault();
-            } else if (event.key === 'Escape') {
-                this.hide();
-                this.toggle.focus();
-                if (!this.bubbleEscapes) {
-                    event.stopPropagation();
-                }
-            }
-        };
-        this.container.addEventListener('keydown', keyboardNavigation);
-        if (this.moveMenu) {
-            this.menu.addEventListener('keydown', keyboardNavigation);
-        }
-
-        // Hide menu on enter press or escape
-        this.menu.addEventListener('keydown ', event => {
-            if (event.key === 'Enter') {
-                event.preventDefault();
-                event.stopPropagation();
-                this.hide();
+                keyboardNavHandler.focusNext();
             }
         });
     }
diff --git a/resources/js/components/global-search.js b/resources/js/components/global-search.js
index c940ad983..9f063f398 100644
--- a/resources/js/components/global-search.js
+++ b/resources/js/components/global-search.js
@@ -1,5 +1,6 @@
 import {htmlToDom} from "../services/dom";
 import {debounce} from "../services/util";
+import {KeyboardNavigationHandler} from "../services/keyboard-navigation";
 
 /**
  * @extends {Component}
@@ -37,7 +38,11 @@ class GlobalSearch {
             this.input.setAttribute('autocomplete', 'on');
             this.button.focus();
             this.input.focus();
-        })
+        });
+
+        new KeyboardNavigationHandler(this.container, () => {
+            this.hideSuggestions();
+        });
     }
 
     /**
diff --git a/resources/js/components/page-editor.js b/resources/js/components/page-editor.js
index ce123e987..14f6b5b3e 100644
--- a/resources/js/components/page-editor.js
+++ b/resources/js/components/page-editor.js
@@ -1,5 +1,6 @@
 import * as Dates from "../services/dates";
 import {onSelect} from "../services/dom";
+import {debounce} from "../services/util";
 
 /**
  * Page Editor
@@ -69,7 +70,8 @@ class PageEditor {
         });
 
         // Changelog controls
-        this.changelogInput.addEventListener('change', this.updateChangelogDisplay.bind(this));
+        const updateChangelogDebounced = debounce(this.updateChangelogDisplay.bind(this), 300, false);
+        this.changelogInput.addEventListener('input', updateChangelogDebounced);
 
         // Draft Controls
         onSelect(this.saveDraftButton, this.saveDraft.bind(this));
diff --git a/resources/js/services/keyboard-navigation.js b/resources/js/services/keyboard-navigation.js
new file mode 100644
index 000000000..9e05ef528
--- /dev/null
+++ b/resources/js/services/keyboard-navigation.js
@@ -0,0 +1,89 @@
+/**
+ * Handle common keyboard navigation events within a given container.
+ */
+export class KeyboardNavigationHandler {
+
+    /**
+     * @param {Element} container
+     * @param {Function|null} onEscape
+     * @param {Function|null} onEnter
+     */
+    constructor(container, onEscape = null, onEnter = null) {
+        this.containers = [container];
+        this.onEscape = onEscape;
+        this.onEnter = onEnter;
+        container.addEventListener('keydown', this.#keydownHandler.bind(this));
+    }
+
+    /**
+     * Also share the keyboard event handling to the given element.
+     * Only elements within the original container are considered focusable though.
+     * @param {Element} element
+     */
+    shareHandlingToEl(element) {
+        this.containers.push(element);
+        element.addEventListener('keydown', this.#keydownHandler.bind(this));
+    }
+
+    /**
+     * Focus on the next focusable element within the current containers.
+     */
+    focusNext() {
+        const focusable = this.#getFocusable();
+        const currentIndex = focusable.indexOf(document.activeElement);
+        let newIndex = currentIndex + 1;
+        if (newIndex >= focusable.length) {
+            newIndex = 0;
+        }
+
+        focusable[newIndex].focus();
+    }
+
+    /**
+     * Focus on the previous existing focusable element within the current containers.
+     */
+    focusPrevious() {
+        const focusable = this.#getFocusable();
+        const currentIndex = focusable.indexOf(document.activeElement);
+        let newIndex = currentIndex - 1;
+        if (newIndex < 0) {
+            newIndex = focusable.length - 1;
+        }
+
+        focusable[newIndex].focus();
+    }
+
+    /**
+     * @param {KeyboardEvent} event
+     */
+    #keydownHandler(event) {
+        if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
+            this.focusNext();
+            event.preventDefault();
+        } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
+            this.focusPrevious();
+            event.preventDefault();
+        } else if (event.key === 'Escape') {
+            if (this.onEscape) {
+                this.onEscape(event);
+            } else if  (document.activeElement) {
+                document.activeElement.blur();
+            }
+        } else if (event.key === 'Enter' && this.onEnter) {
+            this.onEnter(event);
+        }
+    }
+
+    /**
+     * Get an array of focusable elements within the current containers.
+     * @returns {Element[]}
+     */
+    #getFocusable() {
+        const focusable = [];
+        const selector = '[tabindex]:not([tabindex="-1"]),[href],button:not([tabindex="-1"]),input:not([type=hidden])';
+        for (const container of this.containers) {
+            focusable.push(...container.querySelectorAll(selector))
+        }
+        return focusable;
+    }
+}
\ No newline at end of file
diff --git a/resources/sass/_blocks.scss b/resources/sass/_blocks.scss
index 302e7ed4e..37b7b403b 100644
--- a/resources/sass/_blocks.scss
+++ b/resources/sass/_blocks.scss
@@ -101,6 +101,11 @@
     text-decoration: none;
     @include lightDark(background-color, #f2f2f2, #2d2d2d);
   }
+  &:focus {
+    @include lightDark(background-color, #eee, #222);
+    outline: 1px dotted #666;
+    outline-offset: -2px;
+  }
 }
 
 .card.border-card {