diff --git a/app/Auth/Access/GroupSyncService.php b/app/Auth/Access/GroupSyncService.php
index db19b007a..74f0539d8 100644
--- a/app/Auth/Access/GroupSyncService.php
+++ b/app/Auth/Access/GroupSyncService.php
@@ -28,10 +28,8 @@ class GroupSyncService
      */
     protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
     {
-        $externalAuthIds = explode(',', strtolower($externalId));
-
-        foreach ($externalAuthIds as $externalAuthId) {
-            if (in_array(trim($externalAuthId), $groupNames)) {
+        foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) {
+            if (in_array($externalAuthId, $groupNames)) {
                 return true;
             }
         }
@@ -39,6 +37,18 @@ class GroupSyncService
         return false;
     }
 
+    protected function parseRoleExternalAuthId(string $externalId): array
+    {
+        $inputIds = preg_split('/(?<!\\\),/', $externalId);
+        $cleanIds = [];
+
+        foreach ($inputIds as $inputId) {
+            $cleanIds[] = str_replace('\,', ',', trim($inputId));
+        }
+
+        return $cleanIds;
+    }
+
     /**
      * Match an array of group names to BookStack system roles.
      * Formats group names to be lower-case and hyphenated.
diff --git a/database/factories/Auth/RoleFactory.php b/database/factories/Auth/RoleFactory.php
index a952e0487..6dbc1394b 100644
--- a/database/factories/Auth/RoleFactory.php
+++ b/database/factories/Auth/RoleFactory.php
@@ -23,6 +23,7 @@ class RoleFactory extends Factory
         return [
             'display_name' => $this->faker->sentence(3),
             'description'  => $this->faker->sentence(10),
+            'external_auth_id' => '',
         ];
     }
 }
diff --git a/package.json b/package.json
index 4b16f6f46..539e05057 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
   "private": true,
   "scripts": {
-    "build:css:dev": "sass ./resources/sass:./public/dist",
-    "build:css:watch": "sass ./resources/sass:./public/dist --watch",
+    "build:css:dev": "sass ./resources/sass:./public/dist --embed-sources",
+    "build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
     "build:css:production": "sass ./resources/sass:./public/dist -s compressed",
     "build:js:dev": "node dev/build/esbuild.js",
     "build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
diff --git a/resources/icons/download.svg b/resources/icons/download.svg
new file mode 100644
index 000000000..6299571d6
--- /dev/null
+++ b/resources/icons/download.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71zM5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1z"/></svg>
\ No newline at end of file
diff --git a/resources/js/code.mjs b/resources/js/code.mjs
index 8e2ed72c8..537b0d108 100644
--- a/resources/js/code.mjs
+++ b/resources/js/code.mjs
@@ -242,6 +242,21 @@ export function popupEditor(elem, modeSuggestion) {
     });
 }
 
+/**
+ * Create an inline editor to replace the given textarea.
+ * @param {HTMLTextAreaElement} textArea
+ * @param {String} mode
+ * @returns {CodeMirror3}
+ */
+export function inlineEditor(textArea, mode) {
+    return CodeMirror.fromTextArea(textArea, {
+        mode: getMode(mode, textArea.value),
+        lineNumbers: true,
+        lineWrapping: false,
+        theme: getTheme(),
+    });
+}
+
 /**
  * Set the mode of a codemirror instance.
  * @param cmInstance
diff --git a/resources/js/components/chapter-contents.js b/resources/js/components/chapter-contents.js
new file mode 100644
index 000000000..c824d0f78
--- /dev/null
+++ b/resources/js/components/chapter-contents.js
@@ -0,0 +1,37 @@
+import {slideUp, slideDown} from "../services/animations";
+
+/**
+ * @extends {Component}
+ */
+class ChapterContents {
+
+    setup() {
+        this.list = this.$refs.list;
+        this.toggle = this.$refs.toggle;
+
+        this.isOpen = this.toggle.classList.contains('open');
+        this.toggle.addEventListener('click', this.click.bind(this));
+    }
+
+    open() {
+        this.toggle.classList.add('open');
+        this.toggle.setAttribute('aria-expanded', 'true');
+        slideDown(this.list, 180);
+        this.isOpen = true;
+    }
+
+    close() {
+        this.toggle.classList.remove('open');
+        this.toggle.setAttribute('aria-expanded', 'false');
+        slideUp(this.list, 180);
+        this.isOpen = false;
+    }
+
+    click(event) {
+        event.preventDefault();
+        this.isOpen ?  this.close() : this.open();
+    }
+
+}
+
+export default ChapterContents;
diff --git a/resources/js/components/chapter-toggle.js b/resources/js/components/chapter-toggle.js
deleted file mode 100644
index bfd0ac729..000000000
--- a/resources/js/components/chapter-toggle.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import {slideUp, slideDown} from "../services/animations";
-
-class ChapterToggle {
-
-    constructor(elem) {
-        this.elem = elem;
-        this.isOpen = elem.classList.contains('open');
-        elem.addEventListener('click', this.click.bind(this));
-    }
-
-    open() {
-        const list = this.elem.parentNode.querySelector('.inset-list');
-        this.elem.classList.add('open');
-        this.elem.setAttribute('aria-expanded', 'true');
-        slideDown(list, 240);
-    }
-
-    close() {
-        const list = this.elem.parentNode.querySelector('.inset-list');
-        this.elem.classList.remove('open');
-        this.elem.setAttribute('aria-expanded', 'false');
-        slideUp(list, 240);
-    }
-
-    click(event) {
-        event.preventDefault();
-        this.isOpen ?  this.close() : this.open();
-        this.isOpen = !this.isOpen;
-    }
-
-}
-
-export default ChapterToggle;
diff --git a/resources/js/components/code-textarea.js b/resources/js/components/code-textarea.js
new file mode 100644
index 000000000..988e51f19
--- /dev/null
+++ b/resources/js/components/code-textarea.js
@@ -0,0 +1,16 @@
+/**
+ * A simple component to render a code editor within the textarea
+ * this exists upon.
+ * @extends {Component}
+ */
+class CodeTextarea {
+
+    async setup() {
+        const mode = this.$opts.mode;
+        const Code = await window.importVersioned('code');
+        Code.inlineEditor(this.$el, mode);
+    }
+
+}
+
+export default CodeTextarea;
\ No newline at end of file
diff --git a/resources/js/components/dropdown-search.js b/resources/js/components/dropdown-search.js
index e2d55f969..81fa940c2 100644
--- a/resources/js/components/dropdown-search.js
+++ b/resources/js/components/dropdown-search.js
@@ -1,4 +1,5 @@
 import {debounce} from "../services/util";
+import {transitionHeight} from "../services/animations";
 
 class DropdownSearch {
 
@@ -51,7 +52,9 @@ class DropdownSearch {
 
         try {
             const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
+            const animate = transitionHeight(this.listContainerElem, 80);
             this.listContainerElem.innerHTML = resp.data;
+            animate();
         } catch (err) {
             console.error(err);
         }
diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js
index f761ecf01..473db37d4 100644
--- a/resources/js/components/dropdown.js
+++ b/resources/js/components/dropdown.js
@@ -28,18 +28,31 @@ class DropDown {
         this.menu.classList.add('anim', 'menuIn');
         this.toggle.setAttribute('aria-expanded', 'true');
 
+        const menuOriginalRect = this.menu.getBoundingClientRect();
+        let heightOffset = 0;
+        const toggleHeight = this.toggle.getBoundingClientRect().height;
+        const dropUpwards = menuOriginalRect.bottom > window.innerHeight;
+
+        // If enabled, Move to body to prevent being trapped within scrollable sections
         if (this.moveMenu) {
-            // Move to body to prevent being trapped within scrollable sections
-            this.rect = this.menu.getBoundingClientRect();
             this.body.appendChild(this.menu);
             this.menu.style.position = 'fixed';
             if (this.direction === 'right') {
-                this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
+                this.menu.style.right = `${(menuOriginalRect.right - menuOriginalRect.width)}px`;
             } else {
-                this.menu.style.left = `${this.rect.left}px`;
+                this.menu.style.left = `${menuOriginalRect.left}px`;
             }
-            this.menu.style.top = `${this.rect.top}px`;
-            this.menu.style.width = `${this.rect.width}px`;
+            this.menu.style.width = `${menuOriginalRect.width}px`;
+            heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top  - toggleHeight / 2) : menuOriginalRect.top;
+        }
+
+        // Adjust menu to display upwards if near the bottom of the screen
+        if (dropUpwards) {
+            this.menu.style.top = 'initial';
+            this.menu.style.bottom = `${heightOffset}px`;
+        } else {
+            this.menu.style.top = `${heightOffset}px`;
+            this.menu.style.bottom = 'initial';
         }
 
         // Set listener to hide on mouse leave or window click
@@ -74,18 +87,21 @@ class DropDown {
         this.menu.style.display = 'none';
         this.menu.classList.remove('anim', 'menuIn');
         this.toggle.setAttribute('aria-expanded', 'false');
+        this.menu.style.top = '';
+        this.menu.style.bottom = '';
+
         if (this.moveMenu) {
             this.menu.style.position = '';
             this.menu.style[this.direction] = '';
-            this.menu.style.top = '';
             this.menu.style.width = '';
             this.container.appendChild(this.menu);
         }
+
         this.showing = false;
     }
 
     getFocusable() {
-        return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
+        return Array.from(this.menu.querySelectorAll('[tabindex]:not([tabindex="-1"]),[href],button,input:not([type=hidden])'));
     }
 
     focusNext() {
diff --git a/resources/js/components/index.js b/resources/js/components/index.js
index 6a4a8c2b0..f360e2b0c 100644
--- a/resources/js/components/index.js
+++ b/resources/js/components/index.js
@@ -6,9 +6,10 @@ import attachmentsList from "./attachments-list.js"
 import autoSuggest from "./auto-suggest.js"
 import backToTop from "./back-to-top.js"
 import bookSort from "./book-sort.js"
-import chapterToggle from "./chapter-toggle.js"
+import chapterContents from "./chapter-contents.js"
 import codeEditor from "./code-editor.js"
 import codeHighlighter from "./code-highlighter.js"
+import codeTextarea from "./code-textarea.js"
 import collapsible from "./collapsible.js"
 import confirmDialog from "./confirm-dialog"
 import customCheckbox from "./custom-checkbox.js"
@@ -62,9 +63,10 @@ const componentMapping = {
     "auto-suggest": autoSuggest,
     "back-to-top": backToTop,
     "book-sort": bookSort,
-    "chapter-toggle": chapterToggle,
+    "chapter-contents": chapterContents,
     "code-editor": codeEditor,
     "code-highlighter": codeHighlighter,
+    "code-textarea": codeTextarea,
     "collapsible": collapsible,
     "confirm-dialog": confirmDialog,
     "custom-checkbox": customCheckbox,
diff --git a/resources/js/services/animations.js b/resources/js/services/animations.js
index 278a765d5..12b8077cf 100644
--- a/resources/js/services/animations.js
+++ b/resources/js/services/animations.js
@@ -49,7 +49,7 @@ export function slideUp(element, animTime = 400) {
     const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
     const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
     const animStyles = {
-        height: [`${currentHeight}px`, '0px'],
+        maxHeight: [`${currentHeight}px`, '0px'],
         overflow: ['hidden', 'hidden'],
         paddingTop: [currentPaddingTop, '0px'],
         paddingBottom: [currentPaddingBottom, '0px'],
@@ -73,7 +73,7 @@ export function slideDown(element, animTime = 400) {
     const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
     const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
     const animStyles = {
-        height: ['0px', `${targetHeight}px`],
+        maxHeight: ['0px', `${targetHeight}px`],
         overflow: ['hidden', 'hidden'],
         paddingTop: ['0px', targetPaddingTop],
         paddingBottom: ['0px', targetPaddingBottom],
@@ -82,6 +82,38 @@ export function slideDown(element, animTime = 400) {
     animateStyles(element, animStyles, animTime);
 }
 
+/**
+ * Transition the height of the given element between two states.
+ * Call with first state, and you'll receive a function in return.
+ * Call the returned function in the second state to animate between those two states.
+ * If animating to/from 0-height use the slide-up/slide down as easier alternatives.
+ * @param {Element} element - Element to animate
+ * @param {Number} animTime - Animation time in ms
+ * @returns {function} - Function to run in second state to trigger animation.
+ */
+export function transitionHeight(element, animTime = 400) {
+    const startHeight = element.getBoundingClientRect().height;
+    const initialComputedStyles = getComputedStyle(element);
+    const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
+    const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom');
+
+    return () => {
+        cleanupExistingElementAnimation(element);
+        const targetHeight = element.getBoundingClientRect().height;
+        const computedStyles = getComputedStyle(element);
+        const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
+        const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
+        const animStyles = {
+            height: [`${startHeight}px`, `${targetHeight}px`],
+            overflow: ['hidden', 'hidden'],
+            paddingTop: [startPaddingTop, targetPaddingTop],
+            paddingBottom: [startPaddingBottom, targetPaddingBottom],
+        };
+
+        animateStyles(element, animStyles, animTime);
+    };
+}
+
 /**
  * Animate the css styles of an element using FLIP animation techniques.
  * Styles must be an object where the keys are style properties, camelcase, and the values
diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php
index 2f09e53d1..703a70c7e 100644
--- a/resources/lang/en/common.php
+++ b/resources/lang/en/common.php
@@ -47,6 +47,8 @@ return [
     'previous' => 'Previous',
     'filter_active' => 'Active Filter:',
     'filter_clear' => 'Clear Filter',
+    'download' => 'Download',
+    'open_in_tab' => 'Open in Tab',
 
     // Sort Options
     'sort_options' => 'Sort Options',
diff --git a/resources/sass/_blocks.scss b/resources/sass/_blocks.scss
index 7d408cd1b..0398224ca 100644
--- a/resources/sass/_blocks.scss
+++ b/resources/sass/_blocks.scss
@@ -66,7 +66,6 @@
   @include lightDark(background-color, #FFF, #222);
   box-shadow: $bs-card;
   border-radius: 3px;
-  border: 1px solid transparent;
   .body, p.empty-text {
     padding: $-m;
   }
diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss
index bce456cf2..e3c9d5eea 100644
--- a/resources/sass/_components.scss
+++ b/resources/sass/_components.scss
@@ -61,7 +61,7 @@
   }
 }
 
-[chapter-toggle] {
+.chapter-contents-toggle {
   cursor: pointer;
   margin: 0;
   transition: all ease-in-out 180ms;
@@ -77,7 +77,7 @@
     transform: rotate(90deg);
   }
   svg[data-icon="caret-right"] + * {
-    margin-inline-start: $-xs;
+    margin-inline-start: $-xxs;
   }
 }
 
@@ -731,6 +731,55 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   }
 }
 
+
+.dropdown-search {
+  position: relative;
+}
+.dropdown-search-toggle-breadcrumb {
+  border: 1px solid transparent;
+  border-radius: 4px;
+  line-height: normal;
+  padding: $-xs;
+  &:hover {
+    border-color: #DDD;
+  }
+  .svg-icon {
+    margin-inline-end: 0;
+  }
+}
+.dropdown-search-toggle-select {
+  display: flex;
+  gap: $-s;
+  line-height: normal;
+  .svg-icon {
+    height: 16px;
+    margin: 0;
+  }
+  .avatar {
+    height: 22px;
+    width: 22px;
+  }
+  .avatar + span {
+    max-width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dropdown-search-toggle-caret {
+    font-size: 1.15rem;
+  }
+}
+.dropdown-search-toggle-select-label {
+  min-width: 0;
+  white-space: nowrap;
+}
+.dropdown-search-toggle-select-caret {
+  font-size: 1.5rem;
+  line-height: 0;
+  margin-left: auto;
+  margin-top: -2px;
+}
+
 .dropdown-search-dropdown {
   box-shadow: $bs-med;
   overflow: hidden;
@@ -739,7 +788,9 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   display: none;
   position: absolute;
   z-index: 80;
-  right: -$-m;
+  right: 0;
+  top: 0;
+  margin-top: $-m;
   @include rtl {
     right: auto;
     left: -$-m;
@@ -767,12 +818,15 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
       text-decoration: none;
     }
   }
-  input {
+  input, input:focus {
     padding-inline-start: $-xl;
     border-radius: 0;
     border: 0;
     border-bottom: 1px solid #DDD;
   }
+  input:focus {
+    outline: 0;
+  }
 }
 
 @include smaller-than($m) {
@@ -784,10 +838,4 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   .dropdown-search-dropdown .dropdown-search-list {
     max-height: 240px;
   }
-}
-
-.custom-select-input {
-  max-width: 280px;
-  border: 1px solid #D4D4D4;
-  border-radius: 3px;
 }
\ No newline at end of file
diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss
index 665b1213b..73799f0a0 100644
--- a/resources/sass/_forms.scss
+++ b/resources/sass/_forms.scss
@@ -7,7 +7,8 @@
   @include lightDark(color, #666, #AAA);
   display: inline-block;
   font-size: $fs-m;
-  padding: $-xs*1.5;
+  padding: $-xs*1.8;
+  height: 40px;
   width: 250px;
   max-width: 100%;
 
@@ -350,16 +351,13 @@ input[type=color] {
   }
 }
 
-.inline-input-style {
+.title-input input[type="text"] {
   display: block;
   width: 100%;
   padding: $-s;
-}
-
-.title-input input[type="text"] {
-  @extend .inline-input-style;
   margin-top: 0;
   font-size: 2em;
+  height: auto;
 }
 
 .title-input.page-title {
@@ -373,6 +371,7 @@ input[type=color] {
     max-width: 840px;
     margin: 0 auto;
     border: none;
+    height: auto;
   }
 }
 
@@ -383,10 +382,12 @@ input[type=color] {
 }
 
 .description-input textarea {
-  @extend .inline-input-style;
+  display: block;
+  width: 100%;
+  padding: $-s;
   font-size: $fs-m;
   color: #666;
-  width: 100%;
+  height: auto;
 }
 
 div[editor-type="markdown"] .title-input.page-title input[type="text"] {
@@ -413,9 +414,11 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
   }
   input {
     display: block;
+    padding: $-xs * 1.5;
     padding-inline-start: $-l + 4px;
     width: 300px;
     max-width: 100%;
+    height: auto;
   }
   &.flexible input {
     width: 100%;
diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss
index f070f5a18..923f026c2 100644
--- a/resources/sass/_header.scss
+++ b/resources/sass/_header.scss
@@ -21,19 +21,28 @@ header {
   color: rgb(250, 250, 250);
   border-bottom: 1px solid #DDD;
   box-shadow: $bs-card;
-  padding: $-xxs 0;
   @include lightDark(border-bottom-color, #DDD, #000);
   @include whenDark {
     filter: saturate(0.8) brightness(0.8);
   }
+  .header-links {
+    display: flex;
+    align-items: center;
+    justify-content: end;
+  }
   .links {
     display: inline-block;
     vertical-align: top;
   }
   .links a {
     display: inline-block;
-    padding: $-m;
+    padding: 10px $-m;
     color: #FFF;
+    border-radius: 3px;
+  }
+  .links a:hover {
+    text-decoration: none;
+    background-color: rgba(255, 255, 255, .15);
   }
   .dropdown-container {
     padding-inline-start: $-m;
@@ -49,19 +58,25 @@ header {
   .user-name {
     vertical-align: top;
     position: relative;
-    display: inline-block;
+    display: inline-flex;
+    align-items: center;
     cursor: pointer;
-    > * {
-      vertical-align: top;
-    }
+    padding: $-s;
+    margin: 0 (-$-s);
+    border-radius: 3px;
+    gap: $-xs;
     > span {
       padding-inline-start: $-xs;
       display: inline-block;
-      padding-top: $-xxs;
+      line-height: 1;
     }
     > svg {
-      padding-top: 4px;
       font-size: 18px;
+      margin-top: -2px;
+      margin-inline-end: 0;
+    }
+    &:hover {
+      background-color: rgba(255, 255, 255, 0.15);
     }
     @include between($l, $xl) {
       padding-inline-start: $-xs;
@@ -79,22 +94,26 @@ header {
 
 header .search-box {
   display: inline-block;
-  margin-top: 10px;
   input {
     background-color: rgba(0, 0, 0, 0.2);
     border: 1px solid rgba(255, 255, 255, 0.2);
     border-radius: 40px;
     color: #EEE;
     z-index: 2;
+    height: auto;
+    padding: $-xs*1.5;
     padding-inline-start: 40px;
     &:focus {
       outline: none;
-      border: 1px solid rgba(255, 255, 255, 0.6);
+      border: 1px solid rgba(255, 255, 255, 0.4);
     }
   }
   button {
     z-index: 1;
     left: 16px;
+    top: 10px;
+    color: #FFF;
+    opacity: 0.6;
     @include lightDark(color, rgba(255, 255, 255, 0.8), #AAA);
     @include rtl {
       left: auto;
@@ -104,36 +123,39 @@ header .search-box {
       margin-block-end: 0;
     }
   }
-  ::-webkit-input-placeholder { /* Chrome/Opera/Safari */
-    color: #DDD;
-  }
-  ::-moz-placeholder { /* Firefox 19+ */
-    color: #DDD;
+  input::placeholder {
+    color: #FFF;
+    opacity: 0.6;
   }
   @include between($l, $xl) {
     max-width: 200px;
   }
+  &:focus-within button {
+    opacity: 1;
+  }
 }
 
 .logo {
-  display: inline-block;
+  display: inline-flex;
+  padding: ($-s - 6px) $-s;
+  margin: 6px (-$-s);
+  gap: $-s;
+  align-items: center;
+  border-radius: 4px;
   &:hover {
     color: #FFF;
     text-decoration: none;
+    background-color: rgba(255, 255, 255, .15);
   }
 }
+
 .logo-text {
-  display: inline-block;
   font-size: 1.8em;
   color: #fff;
   font-weight: 400;
-  @include padding(14px, $-l, 14px, 0);
-  vertical-align: top;
   line-height: 1;
 }
 .logo-image {
-  @include margin($-xs, $-s, $-xs, 0);
-  vertical-align: top;
   height: 43px;
 }
 
@@ -172,23 +194,29 @@ header .search-box {
     overflow: hidden;
     position: absolute;
     box-shadow: $bs-hover;
-    margin-top: -$-xs;
+    margin-top: $-m;
+    padding: $-xs 0;
     &.show {
       display: block;
     }
   }
   header .links a, header .dropdown-container ul li a, header .dropdown-container ul li button {
     text-align: start;
-    display: block;
-    padding: $-s $-m;
+    display: grid;
+    align-items: center;
+    padding: 8px $-m;
+    gap: $-m;
     color: $text-dark;
+    grid-template-columns: 16px auto;
+    line-height: 1.4;
     @include lightDark(color, $text-dark, #eee);
     svg {
       margin-inline-end: $-s;
+      width: 16px;
     }
     &:hover {
-      @include lightDark(background-color, #eee, #333);
-      @include lightDark(color, #000, #fff);
+      background-color: var(--color-primary-light);
+      color: var(--color-primary);
       text-decoration: none;
     }
     &:focus {
@@ -279,29 +307,6 @@ header .search-box {
   }
 }
 
-.dropdown-search {
-  position: relative;
-  .dropdown-search-toggle {
-    padding: $-xs;
-    border: 1px solid transparent;
-    border-radius: 4px;
-    &:hover {
-      border-color: #DDD;
-    }
-  }
-  .svg-icon {
-    margin-inline-end: 0;
-  }
-}
-
-.dropdown-search-toggle.compact {
-  padding: $-xxs $-xs;
-  .avatar {
-    height: 22px;
-    width: 22px;
-  }
-}
-
 .faded {
   a, button, span, span > div {
     color: #666;
diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss
index b1c80cb53..14a37dd4a 100644
--- a/resources/sass/_layout.scss
+++ b/resources/sass/_layout.scss
@@ -155,6 +155,13 @@ body.flexbox {
   }
 }
 
+.gap-m {
+  gap: $-m;
+}
+
+.justify-flex-start {
+  justify-content: flex-start;
+}
 .justify-flex-end {
   justify-content: flex-end;
 }
@@ -295,9 +302,9 @@ body.flexbox {
 }
 @include larger-than($xxl) {
   .tri-layout-left-contents, .tri-layout-right-contents {
-    padding: $-m;
+    padding: $-xl $-m;
     position: sticky;
-    top: $-m;
+    top: 0;
     max-height: 100vh;
     min-height: 50vh;
     overflow-y: scroll;
diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss
index 26d12a25d..19060fbbf 100644
--- a/resources/sass/_lists.scss
+++ b/resources/sass/_lists.scss
@@ -6,7 +6,7 @@
     justify-self: stretch;
     align-self: stretch;
     height: auto;
-    margin-inline-end: $-l;
+    margin-inline-end: $-xs;
   }
   .icon:after {
     opacity: 0.5;
@@ -56,13 +56,13 @@
   > .content {
     flex: 1;
   }
-  .chapter-expansion-toggle {
+  .chapter-contents-toggle {
     border-radius: 0 4px 4px 0;
-    padding: $-xs $-m;
+    padding: $-xs ($-m + $-xxs);
     width: 100%;
     text-align: start;
   }
-  .chapter-expansion-toggle:hover {
+  .chapter-contents-toggle:hover {
     background-color: rgba(0, 0, 0, 0.06);
   }
 }
@@ -157,22 +157,6 @@
   @include margin($-xs, -$-s, 0, -$-s);
   padding-inline-start: 0;
   padding-inline-end: 0;
-  position: relative;
-
-  &:after, .sub-menu:after {
-    content: '';
-    display: block;
-    position: absolute;
-    left: $-m;
-    top: 1rem;
-    bottom: 1rem;
-    border-inline-start: 4px solid rgba(0, 0, 0, 0.1);
-    z-index: 0;
-    @include rtl {
-      left: auto;
-      right: $-m;
-    }
-  }
 
   ul {
     list-style: none;
@@ -181,19 +165,20 @@
   }
 
   .entity-list-item {
-    padding-top: $-xxs;
-    padding-bottom: $-xxs;
+    padding-top: 2px;
+    padding-bottom: 2px;
     background-clip: content-box;
     border-radius: 0 3px 3px 0;
     padding-inline-end: 0;
     .content {
+      width: 100%;
       padding-top: $-xs;
       padding-bottom: $-xs;
       max-width: calc(100% - 20px);
     }
   }
   .entity-list-item.selected {
-    @include lightDark(background-color, rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08));
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
   }
   .entity-list-item.no-hover {
     margin-top: -$-xs;
@@ -209,9 +194,18 @@
     margin-top: -.2rem;
     margin-inline-start: -1rem;
   }
-  [chapter-toggle] {
-    padding-inline-start: .7rem;
-    padding-bottom: .2rem;
+  .chapter-contents-toggle {
+    display: block;
+    width: 100%;
+    text-align: left;
+    padding: $-xxs $-s ($-xxs * 2) $-s;
+    border-radius: 0 3px 3px 0;
+    line-height: 1;
+    margin-top: -$-xxs;
+    margin-bottom: -$-xxs;
+    &:hover {
+      @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
+    }
   }
   .entity-list-item .icon {
     z-index: 2;
@@ -220,7 +214,7 @@
     align-self: stretch;
     flex-shrink: 0;
     border-radius: 1px;
-    opacity: 0.6;
+    opacity: 0.8;
   }
   .entity-list-item .icon:after {
     opacity: 1;
@@ -230,15 +224,11 @@
   }
 }
 
-.chapter-child-menu {
-  ul.sub-menu {
-    display: none;
-    padding-inline-start: 0;
-    position: relative;
-  }
-  [chapter-toggle].open + .sub-menu {
-    display: block;
-  }
+.chapter-child-menu ul.sub-menu {
+  display: none;
+  padding-inline-start: 0;
+  position: relative;
+  margin-bottom: 0;
 }
 
 // Sortable Lists
@@ -415,6 +405,7 @@ ul.pagination {
   padding: $-s $-m;
   display: flex;
   align-items: center;
+  gap: $-m;
   background-color: transparent;
   border: 0;
   width: 100%;
@@ -424,7 +415,6 @@ ul.pagination {
     color: #666;
   }
   > span:first-child {
-    margin-inline-end: $-m;
     flex-basis: 1.88em;
     flex: none;
   }
@@ -439,8 +429,8 @@ ul.pagination {
     cursor: pointer;
   }
   &:not(.no-hover):hover {
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
     text-decoration: none;
-    background-color: rgba(0, 0, 0, 0.1);
     border-radius: 4px;
   }
   &.outline-hover:hover {
@@ -463,19 +453,74 @@ ul.pagination {
   }
 }
 
-.card .entity-list-item:not(.no-hover):hover {
-  @include lightDark(background-color, #F2F2F2, #2d2d2d)
+.split-icon-list-item {
+  display: flex;
+  align-items: center;
+  gap: $-m;
+  background-color: transparent;
+  border: 0;
+  width: 100%;
+  position: relative;
+  word-break: break-word;
+  border-radius: 4px;
+  > a {
+    padding: $-s $-m;
+    display: flex;
+    align-items: center;
+    gap: $-m;
+    flex: 1;
+  }
+  > a:hover {
+    text-decoration: none;
+  }
+  .icon {
+    flex-basis: 1.88em;
+    flex: none;
+  }
+  &:hover {
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
+  }
+}
+
+.icon-list-item-dropdown {
+  margin-inline-start: auto;
+  align-self: stretch;
+  display: flex;
+  align-items: stretch;
+  border-inline-start: 1px solid rgba(0, 0, 0, .1);
+  visibility: hidden;
+}
+.split-icon-list-item:hover .icon-list-item-dropdown,
+.split-icon-list-item:focus-within .icon-list-item-dropdown {
+  visibility: visible;
+}
+.icon-list-item-dropdown-toggle {
+  padding: $-xs;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  @include lightDark(color, #888, #999);
+  svg {
+    margin: 0;
+  }
+  &:hover {
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
+  }
+}
+
+.card .entity-list-item:not(.no-hover, .book-contents .entity-list-item):hover {
+  @include lightDark(background-color, #F2F2F2, #2d2d2d);
+  border-radius: 0;
 }
 .card .entity-list-item .entity-list-item:hover {
   background-color: #EEEEEE;
 }
 
 .entity-list-item-children {
-  padding: $-m;
+  padding: $-m $-l;
   > div {
     overflow: hidden;
-    padding: $-xs 0;
-    margin-top: -$-xs;
+    padding: 0 0 $-xs 0;
   }
   .entity-chip {
     text-overflow: ellipsis;
@@ -485,6 +530,9 @@ ul.pagination {
     display: block;
     white-space: nowrap;
   }
+  > .entity-list > .entity-list-item:last-child {
+    margin-bottom: -$-xs;
+  }
 }
 
 .entity-list-item-image {
@@ -531,6 +579,9 @@ ul.pagination {
     font-size: $fs-m * 0.8;
     padding-top: $-xs;
   }
+  .entity-list-item p:empty {
+    padding-top: 0;
+  }
   p {
     margin: 0;
   }
@@ -574,8 +625,8 @@ ul.pagination {
   right: 0;
   margin: $-m 0;
   @include lightDark(background-color, #fff, #333);
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
-  border-radius: 1px;
+  box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.18);
+  border-radius: 3px;
   min-width: 180px;
   padding: $-xs 0;
   @include lightDark(color, #555, #eee);
@@ -652,6 +703,13 @@ ul.pagination {
   }
 }
 
+// Shift in sidebar dropdown menus to prevent shadows
+// being cut by scrollable container.
+.tri-layout-right .dropdown-menu,
+.tri-layout-left .dropdown-menu {
+  right: $-xs;
+}
+
 // Books grid view
 .featured-image-container {
   position: relative;
@@ -719,3 +777,19 @@ ul.pagination {
     }
   }
 }
+
+.entity-meta-item {
+  display: flex;
+  line-height: 1.2;
+  margin: 0.6em 0;
+  align-content: start;
+  gap: $-s;
+  a {
+    line-height: 1.2;
+  }
+  svg {
+    flex-shrink: 0;
+    width: 1em;
+    margin: 0;
+  }
+}
diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss
index 73819975f..3ceec61d0 100755
--- a/resources/sass/_pages.scss
+++ b/resources/sass/_pages.scss
@@ -396,10 +396,14 @@ body.tox-fullscreen, body.markdown-fullscreen {
   }
 }
 
-.entity-list-item > span:first-child, .icon-list-item > span:first-child, .chapter-expansion > .icon {
+.entity-list-item > span:first-child,
+.icon-list-item > span:first-child,
+.split-icon-list-item > a > .icon,
+.chapter-expansion > .icon {
   font-size: 0.8rem;
   width: 1.88em;
   height: 1.88em;
+  flex-shrink: 0;
   display: flex;
   align-items: center;
   justify-content: center;
diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss
index 582bf7c75..ee99d7668 100644
--- a/resources/sass/styles.scss
+++ b/resources/sass/styles.scss
@@ -79,17 +79,17 @@ $loadingSize: 10px;
     animation-timing-function: cubic-bezier(.62, .28, .23, .99);
     margin-inline-end: 4px;
     background-color: var(--color-page);
-    animation-delay: 0.3s;
+    animation-delay: -300ms;
   }
   > div:first-child {
       left: -($loadingSize+$-xs);
       background-color: var(--color-book);
-      animation-delay: 0s;
+      animation-delay: -600ms;
   }
   > div:last-of-type {
     left: $loadingSize+$-xs;
     background-color: var(--color-chapter);
-    animation-delay: 0.6s;
+    animation-delay: 0ms;
   }
   > span {
     margin-inline-start: $-s;
@@ -138,7 +138,7 @@ $btt-size: 40px;
 
 .skip-to-content-link {
   position: fixed;
-  top: -$-xxl;
+  top: -52px;
   left: 0;
   background-color: #FFF;
   z-index: 15;
diff --git a/resources/views/attachments/list.blade.php b/resources/views/attachments/list.blade.php
index f0a1354ea..a6ffb709b 100644
--- a/resources/views/attachments/list.blade.php
+++ b/resources/views/attachments/list.blade.php
@@ -1,10 +1,27 @@
 <div component="attachments-list">
     @foreach($attachments as $attachment)
         <div class="attachment icon-list">
-            <a class="icon-list-item py-xs attachment-{{ $attachment->external ? 'link' : 'file' }}" href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
-                <span class="icon">@icon($attachment->external ? 'export' : 'file')</span>
-                <span>{{ $attachment->name }}</span>
-            </a>
+            <div class="split-icon-list-item attachment-{{ $attachment->external ? 'link' : 'file' }}">
+                <a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
+                    <div class="icon">@icon($attachment->external ? 'export' : 'file')</div>
+                    <div class="label">{{ $attachment->name }}</div>
+                </a>
+                @if(!$attachment->external)
+                    <div component="dropdown" class="icon-list-item-dropdown">
+                        <button refs="dropdown@toggle" type="button" class="icon-list-item-dropdown-toggle">@icon('caret-down')</button>
+                        <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+                            <a href="{{ $attachment->getUrl(false) }}" class="icon-item">
+                                @icon('download')
+                                <div>{{ trans('common.download') }}</div>
+                            </a>
+                            <a href="{{ $attachment->getUrl(true) }}" target="_blank" class="icon-item">
+                                @icon('export')
+                                <div>{{ trans('common.open_in_tab') }}</div>
+                            </a>
+                        </ul>
+                    </div>
+                @endif
+            </div>
         </div>
     @endforeach
 </div>
\ No newline at end of file
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php
index 5263bc810..e0cb4b862 100644
--- a/resources/views/books/show.blade.php
+++ b/resources/views/books/show.blade.php
@@ -67,14 +67,20 @@
 @section('right')
     <div class="mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="text-small text-muted blended-links">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $book])
             @if($book->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
-                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                        <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
diff --git a/resources/views/chapters/parts/child-menu.blade.php b/resources/views/chapters/parts/child-menu.blade.php
index a00f0f7e1..8fdd09143 100644
--- a/resources/views/chapters/parts/child-menu.blade.php
+++ b/resources/views/chapters/parts/child-menu.blade.php
@@ -1,9 +1,14 @@
-<div class="chapter-child-menu">
-    <button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
-            class="text-muted @if($isOpen) open @endif">
+<div component="chapter-contents" class="chapter-child-menu">
+    <button type="button"
+            refs="chapter-contents@toggle"
+            aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
+            class="text-muted chapter-contents-toggle @if($isOpen) open @endif">
         @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->visible_pages->count()) }}</span>
     </button>
-    <ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu">
+    <ul refs="chapter-contents@list"
+        class="chapter-contents-list sub-menu inset-list @if($isOpen) open @endif" @if($isOpen)
+        style="display: block;" @endif
+        role="menu">
         @foreach($bookChild->visible_pages as $childPage)
             <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
                 @include('entities.list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
diff --git a/resources/views/chapters/parts/list-item.blade.php b/resources/views/chapters/parts/list-item.blade.php
index 285e34893..c3e735e2b 100644
--- a/resources/views/chapters/parts/list-item.blade.php
+++ b/resources/views/chapters/parts/list-item.blade.php
@@ -5,18 +5,19 @@
     <div class="content">
         <h4 class="entity-list-item-name break-text">{{ $chapter->name }}</h4>
         <div class="entity-item-snippet">
-            <p class="text-muted break-text mb-s">{{ $chapter->getExcerpt() }}</p>
+            <p class="text-muted break-text">{{ $chapter->getExcerpt() }}</p>
         </div>
     </div>
 </a>
 @if ($chapter->visible_pages->count() > 0)
     <div class="chapter chapter-expansion">
         <span class="icon text-chapter">@icon('page')</span>
-        <div class="content">
-            <button type="button" chapter-toggle
+        <div component="chapter-contents" class="content">
+            <button type="button"
+                    refs="chapter-contents@toggle"
                     aria-expanded="false"
-                    class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
-            <div class="inset-list">
+                    class="text-muted chapter-contents-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
+            <div refs="chapter-contents@list" class="inset-list chapter-contents-list">
                 <div class="entity-list-item-children">
                     @include('entities.list', ['entities' => $chapter->visible_pages])
                 </div>
diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php
index edd39edde..3e015616a 100644
--- a/resources/views/chapters/show.blade.php
+++ b/resources/views/chapters/show.blade.php
@@ -64,15 +64,21 @@
 
     <div class="mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="blended-links text-small text-muted">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $chapter])
 
             @if($book->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
-                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                        <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
@@ -80,9 +86,15 @@
             @if($chapter->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $chapter))
-                        <a href="{{ $chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
+                        <a href="{{ $chapter->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.chapters_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php
index b5ac520c1..197b80c27 100644
--- a/resources/views/common/header.blade.php
+++ b/resources/views/common/header.blade.php
@@ -17,7 +17,7 @@
                     class="mobile-menu-toggle hide-over-l">@icon('more')</button>
         </div>
 
-        <div class="flex-container-row justify-center hide-under-l">
+        <div class="flex-container-column items-center justify-center hide-under-l">
             @if (hasAppAccess())
             <form action="{{ url('/search') }}" method="GET" class="search-box" role="search">
                 <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
@@ -28,76 +28,74 @@
             @endif
         </div>
 
-        <div class="text-right">
-            <nav refs="header-mobile-toggle@menu" class="header-links">
-                <div class="links text-center">
-                    @if (hasAppAccess())
-                        <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
-                        @if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
-                            <a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
-                        @endif
-                        <a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a>
-                        @if(signedInUser() && userCan('settings-manage'))
-                            <a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
-                        @endif
-                        @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
-                            <a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
-                        @endif
+        <nav refs="header-mobile-toggle@menu" class="header-links">
+            <div class="links text-center">
+                @if (hasAppAccess())
+                    <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
+                    @if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
+                        <a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
                     @endif
+                    <a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a>
+                    @if(signedInUser() && userCan('settings-manage'))
+                        <a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
+                    @endif
+                    @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
+                        <a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
+                    @endif
+                @endif
 
-                    @if(!signedInUser())
-                        @if(setting('registration-enabled') && config('auth.method') === 'standard')
-                            <a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
-                        @endif
-                        <a href="{{ url('/login')  }}">@icon('login'){{ trans('auth.log_in') }}</a>
+                @if(!signedInUser())
+                    @if(setting('registration-enabled') && config('auth.method') === 'standard')
+                        <a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
                     @endif
-                </div>
-                @if(signedInUser())
-                    <?php $currentUser = user(); ?>
-                    <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
+                    <a href="{{ url('/login')  }}">@icon('login'){{ trans('auth.log_in') }}</a>
+                @endif
+            </div>
+            @if(signedInUser())
+                <?php $currentUser = user(); ?>
+                <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
                         <span class="user-name py-s hide-under-l" refs="dropdown@toggle"
                               aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
                             <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
                             <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
                         </span>
-                        <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
-                            <li>
-                                <a href="{{ url('/favourites') }}" class="icon-item">
-                                    @icon('star')
-                                    <div>{{ trans('entities.my_favourites') }}</div>
-                                </a>
-                            </li>
-                            <li>
-                                <a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
-                                    @icon('user')
-                                    <div>{{ trans('common.view_profile') }}</div>
-                                </a>
-                            </li>
-                            <li>
-                                <a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
-                                    @icon('edit')
-                                    <div>{{ trans('common.edit_profile') }}</div>
-                                </a>
-                            </li>
-                            <li>
-                                <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
-                                      method="post">
-                                    {{ csrf_field() }}
-                                    <button class="icon-item">
-                                        @icon('logout')
-                                        <div>{{ trans('auth.logout') }}</div>
-                                    </button>
-                                </form>
-                            </li>
-                            <li><hr></li>
-                            <li>
-                                @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
-                            </li>
-                        </ul>
-                    </div>
-                @endif
-            </nav>
-        </div>
+                    <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+                        <li>
+                            <a href="{{ url('/favourites') }}" class="icon-item">
+                                @icon('star')
+                                <div>{{ trans('entities.my_favourites') }}</div>
+                            </a>
+                        </li>
+                        <li>
+                            <a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
+                                @icon('user')
+                                <div>{{ trans('common.view_profile') }}</div>
+                            </a>
+                        </li>
+                        <li>
+                            <a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
+                                @icon('edit')
+                                <div>{{ trans('common.edit_profile') }}</div>
+                            </a>
+                        </li>
+                        <li>
+                            <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
+                                  method="post">
+                                {{ csrf_field() }}
+                                <button class="icon-item">
+                                    @icon('logout')
+                                    <div>{{ trans('auth.logout') }}</div>
+                                </button>
+                            </form>
+                        </li>
+                        <li><hr></li>
+                        <li>
+                            @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
+                        </li>
+                    </ul>
+                </div>
+            @endif
+        </nav>
 
     </div>
 </header>
diff --git a/resources/views/entities/breadcrumb-listing.blade.php b/resources/views/entities/breadcrumb-listing.blade.php
index 929f56ed3..1efe3ba34 100644
--- a/resources/views/entities/breadcrumb-listing.blade.php
+++ b/resources/views/entities/breadcrumb-listing.blade.php
@@ -2,7 +2,7 @@
      option:dropdown-search:url="/search/entity/siblings?entity_type={{$entity->getType()}}&entity_id={{ $entity->id }}"
      option:dropdown-search:local-search-selector=".entity-list-item"
 >
-    <div class="dropdown-search-toggle" refs="dropdown@toggle"
+    <div class="dropdown-search-toggle-breadcrumb" refs="dropdown@toggle"
          aria-haspopup="true" aria-expanded="false" tabindex="0">
         <div class="separator">@icon('chevron-right')</div>
     </div>
@@ -18,6 +18,6 @@
         <div refs="dropdown-search@loading">
             @include('common.loading-icon')
         </div>
-        <div refs="dropdown-search@listContainer" class="dropdown-search-list px-m"></div>
+        <div refs="dropdown-search@listContainer" class="dropdown-search-list px-m" tabindex="-1"></div>
     </div>
 </div>
\ No newline at end of file
diff --git a/resources/views/entities/export-menu.blade.php b/resources/views/entities/export-menu.blade.php
index dd7231095..bac240b1e 100644
--- a/resources/views/entities/export-menu.blade.php
+++ b/resources/views/entities/export-menu.blade.php
@@ -1,13 +1,18 @@
-<div component="dropdown" class="dropdown-container" id="export-menu">
+<div component="dropdown"
+     class="dropdown-container"
+     id="export-menu">
+
     <div refs="dropdown@toggle" class="icon-list-item"
          aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
         <span>@icon('export')</span>
         <span>{{ trans('entities.export') }}</span>
     </div>
+
     <ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
         <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li>
         <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li>
         <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li>
         <li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
     </ul>
+
 </div>
diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php
index 298cc7c3e..83ff23762 100644
--- a/resources/views/entities/meta.blade.php
+++ b/resources/views/entities/meta.blade.php
@@ -1,50 +1,62 @@
 <div class="entity-meta">
     @if($entity->isA('revision'))
-        <div>
-            @icon('history'){{ trans('entities.pages_revision') }}
-            {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
+        <div class="entity-meta-item">
+            @icon('history')
+            <div>
+                {{ trans('entities.pages_revision') }}
+                {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
+            </div>
         </div>
     @endif
 
     @if ($entity->isA('page'))
-        <div>
-            @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
-            @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
-            @if (userCan('page-update', $entity))</a>@endif
-        </div>
+        @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}" class="entity-meta-item"> @else <div class="entity-meta-item"> @endif
+        @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
+        @if (userCan('page-update', $entity))</a> @else </div> @endif
     @endif
 
     @if ($entity->ownedBy && $entity->owned_by !== $entity->created_by)
-        <div>
-            @icon('user'){!! trans('entities.meta_owned_name', [
-            'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
-        ]) !!}
+        <div class="entity-meta-item">
+            @icon('user')
+            <div>
+                {!! trans('entities.meta_owned_name', [
+                    'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
+                ]) !!}
+            </div>
         </div>
     @endif
 
     @if ($entity->createdBy)
-        <div>
-            @icon('star'){!! trans('entities.meta_created_name', [
-            'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
-            'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
-            ]) !!}
+        <div class="entity-meta-item">
+            @icon('star')
+            <div>
+                {!! trans('entities.meta_created_name', [
+                    'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
+                    'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
+                ]) !!}
+            </div>
         </div>
     @else
-        <div>
-            @icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
+        <div class="entity-meta-item">
+            @icon('star')
+            <span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
         </div>
     @endif
 
     @if ($entity->updatedBy)
-        <div>
-            @icon('edit'){!! trans('entities.meta_updated_name', [
-                'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
-                'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
-            ]) !!}
+        <div class="entity-meta-item">
+            @icon('edit')
+            <div>
+                {!! trans('entities.meta_updated_name', [
+                    'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
+                    'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
+                ]) !!}
+            </div>
         </div>
     @elseif (!$entity->isA('revision'))
-        <div>
-            @icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
+        <div class="entity-meta-item">
+            @icon('edit')
+            <span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
         </div>
     @endif
 </div>
\ No newline at end of file
diff --git a/resources/views/form/entity-permissions.blade.php b/resources/views/form/entity-permissions.blade.php
index ed04bc041..206955fe9 100644
--- a/resources/views/form/entity-permissions.blade.php
+++ b/resources/views/form/entity-permissions.blade.php
@@ -15,7 +15,7 @@
         <div>
             <div class="form-group">
                 <label for="owner">{{ trans('entities.permissions_owner') }}</label>
-                @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false])
+                @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by'])
             </div>
         </div>
     </div>
diff --git a/resources/views/form/user-select.blade.php b/resources/views/form/user-select.blade.php
index 8823bb075..743795a6d 100644
--- a/resources/views/form/user-select.blade.php
+++ b/resources/views/form/user-select.blade.php
@@ -1,19 +1,19 @@
-<div class="dropdown-search custom-select-input" components="dropdown dropdown-search user-select"
+<div class="dropdown-search" components="dropdown dropdown-search user-select"
      option:dropdown-search:url="/search/users/select"
 >
     <input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}">
     <div refs="dropdown@toggle"
-         class="dropdown-search-toggle {{ $compact ? 'compact' : '' }} flex-container-row items-center"
+         class="dropdown-search-toggle-select  input-base"
          aria-haspopup="true" aria-expanded="false" tabindex="0">
-        <div refs="user-select@user-info" class="flex-container-row items-center px-s">
+        <div refs="user-select@user-info" class="dropdown-search-toggle-select-label flex-container-row items-center">
             @if($user)
-                <img class="avatar small mr-m" src="{{ $user->getAvatar($compact ? 22 : 30) }}" alt="{{ $user->name }}">
+                <img class="avatar small mr-m" src="{{ $user->getAvatar(30) }}" width="30" height="30" alt="{{ $user->name }}">
                 <span>{{ $user->name }}</span>
             @else
                 <span>{{ trans('settings.users_none_selected') }}</span>
             @endif
         </div>
-        <span style="font-size: {{ $compact ? '1.15rem' : '1.5rem' }}; margin-left: auto;">
+        <span class="dropdown-search-toggle-select-caret">
             @icon('caret-down')
         </span>
     </div>
diff --git a/resources/views/layouts/tri.blade.php b/resources/views/layouts/tri.blade.php
index e95b21445..4571f4471 100644
--- a/resources/views/layouts/tri.blade.php
+++ b/resources/views/layouts/tri.blade.php
@@ -27,7 +27,7 @@
 
     <div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
 
-        <div class="tri-layout-left print-hidden pt-m" id="sidebar">
+        <div class="tri-layout-left print-hidden" id="sidebar">
             <aside class="tri-layout-left-contents">
                 @yield('left')
             </aside>
@@ -39,7 +39,7 @@
             </div>
         </div>
 
-        <div class="tri-layout-right print-hidden pt-m">
+        <div class="tri-layout-right print-hidden">
             <aside class="tri-layout-right-contents">
                 @yield('right')
             </aside>
diff --git a/resources/views/pages/parts/editor-toolbar.blade.php b/resources/views/pages/parts/editor-toolbar.blade.php
index 4846f4b76..fa5cb7374 100644
--- a/resources/views/pages/parts/editor-toolbar.blade.php
+++ b/resources/views/pages/parts/editor-toolbar.blade.php
@@ -65,7 +65,9 @@
         </div>
 
         <div class="action-buttons px-m py-xs">
-            <div component="dropdown" dropdown-move-menu class="dropdown-container">
+            <div component="dropdown"
+                 option:dropdown:move-menu="true"
+                 class="dropdown-container">
                 <button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button>
                 <ul refs="dropdown@menu" class="wide dropdown-menu">
                     <li class="px-l py-m">
diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php
index 0111047c6..2a71c6021 100644
--- a/resources/views/pages/show.blade.php
+++ b/resources/views/pages/show.blade.php
@@ -76,15 +76,21 @@
 @section('right')
     <div id="page-details" class="entity-details mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="body text-small blended-links">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $page])
 
             @if($book->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
-                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                        <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
@@ -92,9 +98,15 @@
             @if($page->chapter && $page->chapter->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $page->chapter))
-                        <a href="{{ $page->chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
+                        <a href="{{ $page->chapter->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.chapters_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
@@ -102,16 +114,23 @@
             @if($page->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $page))
-                        <a href="{{ $page->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.pages_permissions_active') }}</a>
+                        <a href="{{ $page->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.pages_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.pages_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.pages_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
 
             @if($page->template)
-                <div>
-                    @icon('template'){{ trans('entities.pages_is_template') }}
+                <div class="entity-meta-item">
+                    @icon('template')
+                    <div>{{ trans('entities.pages_is_template') }}</div>
                 </div>
             @endif
         </div>
diff --git a/resources/views/settings/audit.blade.php b/resources/views/settings/audit.blade.php
index 506a735a2..b856d1150 100644
--- a/resources/views/settings/audit.blade.php
+++ b/resources/views/settings/audit.blade.php
@@ -9,8 +9,9 @@
         <h1 class="list-heading">{{ trans('settings.audit') }}</h1>
         <p class="text-muted">{{ trans('settings.audit_desc') }}</p>
 
-        <div class="flex-container-row">
-            <div component="dropdown" class="list-sort-type dropdown-container mr-m">
+        <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-m">
+
+            <div component="dropdown" class="list-sort-type dropdown-container">
                 <label for="">{{ trans('settings.audit_event_filter') }}</label>
                 <button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
                 <ul refs="dropdown@menu" class="dropdown-menu">
@@ -21,37 +22,35 @@
                 </ul>
             </div>
 
-            <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row mr-m">
-                @if(!empty($listDetails['event']))
-                    <input type="hidden" name="event" value="{{ $listDetails['event'] }}">
-                @endif
+            @if(!empty($listDetails['event']))
+                <input type="hidden" name="event" value="{{ $listDetails['event'] }}">
+            @endif
 
-                @foreach(['date_from', 'date_to'] as $filterKey)
-                    <div class="mr-m">
-                        <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
-                        <input id="audit_filter_{{ $filterKey }}"
-                               component="submit-on-change"
-                               type="date"
-                               name="{{ $filterKey }}"
-                               value="{{ $listDetails[$filterKey] ?? '' }}">
-                    </div>
-                @endforeach
-
-                <div class="form-group ml-auto mr-m"
-                     component="submit-on-change"
-                     option:submit-on-change:filter='[name="user"]'>
-                    <label for="owner">{{ trans('settings.audit_table_user') }}</label>
-                    @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' =>  true])
+            @foreach(['date_from', 'date_to'] as $filterKey)
+                <div class=>
+                    <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
+                    <input id="audit_filter_{{ $filterKey }}"
+                           component="submit-on-change"
+                           type="date"
+                           name="{{ $filterKey }}"
+                           value="{{ $listDetails[$filterKey] ?? '' }}">
                 </div>
+            @endforeach
+
+            <div class="form-group"
+                 component="submit-on-change"
+                 option:submit-on-change:filter='[name="user"]'>
+                <label for="owner">{{ trans('settings.audit_table_user') }}</label>
+                @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user'])
+            </div>
 
 
-                <div class="form-group ml-auto">
-                    <label for="ip">{{ trans('settings.audit_table_ip') }}</label>
-                    @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
-                    <input type="submit" style="display: none">
-                </div>
-            </form>
-        </div>
+            <div class="form-group">
+                <label for="ip">{{ trans('settings.audit_table_ip') }}</label>
+                @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
+                <input type="submit" style="display: none">
+            </div>
+        </form>
 
         <hr class="mt-l mb-s">
 
diff --git a/resources/views/settings/customization.blade.php b/resources/views/settings/customization.blade.php
index b7be95b4a..a7392196b 100644
--- a/resources/views/settings/customization.blade.php
+++ b/resources/views/settings/customization.blade.php
@@ -119,7 +119,13 @@
             <div>
                 <label for="setting-app-custom-head" class="setting-list-label">{{ trans('settings.app_custom_html') }}</label>
                 <p class="small">{{ trans('settings.app_custom_html_desc') }}</p>
-                <textarea name="setting-app-custom-head" id="setting-app-custom-head" class="simple-code-input mt-m">{{ setting('app-custom-head', '') }}</textarea>
+                <div class="mt-m">
+                    <textarea component="code-textarea"
+                              option:code-textarea:mode="html"
+                              name="setting-app-custom-head"
+                              id="setting-app-custom-head"
+                              class="simple-code-input">{{ setting('app-custom-head', '') }}</textarea>
+                </div>
                 <p class="small text-right">{{ trans('settings.app_custom_html_disabled_notice') }}</p>
             </div>
 
diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php
index 0d592468d..4d440b635 100644
--- a/resources/views/shelves/show.blade.php
+++ b/resources/views/shelves/show.blade.php
@@ -81,14 +81,20 @@
 
     <div id="details" class="mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="text-small text-muted blended-links">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $shelf])
             @if($shelf->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $shelf))
-                        <a href="{{ $shelf->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.shelves_permissions_active') }}</a>
+                        <a href="{{ $shelf->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.shelves_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.shelves_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.shelves_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
diff --git a/resources/views/users/delete.blade.php b/resources/views/users/delete.blade.php
index 9ee5d4c05..b18c182eb 100644
--- a/resources/views/users/delete.blade.php
+++ b/resources/views/users/delete.blade.php
@@ -19,7 +19,7 @@
                         <p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
                     </div>
                     <div>
-                        @include('form.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false])
+                        @include('form.user-select', ['name' => 'new_owner_id', 'user' => null])
                     </div>
                 </div>
             @endif
diff --git a/tests/Auth/GroupSyncServiceTest.php b/tests/Auth/GroupSyncServiceTest.php
new file mode 100644
index 000000000..ee8dee008
--- /dev/null
+++ b/tests/Auth/GroupSyncServiceTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Tests\Auth;
+
+use BookStack\Auth\Access\GroupSyncService;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+use Tests\TestCase;
+
+class GroupSyncServiceTest extends TestCase
+{
+
+    public function test_user_is_assigned_to_matching_roles()
+    {
+        $user = $this->getViewer();
+
+        $roleA = Role::factory()->create(['display_name' => 'Wizards']);
+        $roleB = Role::factory()->create(['display_name' => 'Gremlins']);
+        $roleC = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales']);
+        $roleD = Role::factory()->create(['display_name' => 'DEF456', 'external_auth_id' => 'admin-team']);
+
+        foreach([$roleA, $roleB, $roleC, $roleD] as $role) {
+            $this->assertFalse($user->hasRole($role->id));
+        }
+
+        (new GroupSyncService())->syncUserWithFoundGroups($user, ['Wizards', 'Gremlinz', 'Sales', 'Admin Team'], false);
+
+        $user = User::query()->find($user->id);
+        $this->assertTrue($user->hasRole($roleA->id));
+        $this->assertFalse($user->hasRole($roleB->id));
+        $this->assertTrue($user->hasRole($roleC->id));
+        $this->assertTrue($user->hasRole($roleD->id));
+    }
+
+    public function test_multiple_values_in_role_external_auth_id_handled()
+    {
+        $user = $this->getViewer();
+        $role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales, engineering, developers, marketers']);
+        $this->assertFalse($user->hasRole($role->id));
+
+        (new GroupSyncService())->syncUserWithFoundGroups($user, ['Developers'], false);
+
+        $user = User::query()->find($user->id);
+        $this->assertTrue($user->hasRole($role->id));
+    }
+
+    public function test_commas_can_be_used_in_external_auth_id_if_escaped()
+    {
+        $user = $this->getViewer();
+        $role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales\,-developers, marketers']);
+        $this->assertFalse($user->hasRole($role->id));
+
+        (new GroupSyncService())->syncUserWithFoundGroups($user, ['Sales, Developers'], false);
+
+        $user = User::query()->find($user->id);
+        $this->assertTrue($user->hasRole($role->id));
+    }
+
+}
\ No newline at end of file