diff --git a/resources/js/components/attachments-list.js b/resources/js/components/attachments-list.js
index 5004f9357..dfefd9b7f 100644
--- a/resources/js/components/attachments-list.js
+++ b/resources/js/components/attachments-list.js
@@ -43,6 +43,4 @@ export class AttachmentsList extends Component {
             link.removeAttribute('target');
         }
     }
-}
-
-export default AttachmentsList;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/attachments.js b/resources/js/components/attachments.js
index a01147aa2..b373e1d47 100644
--- a/resources/js/components/attachments.js
+++ b/resources/js/components/attachments.js
@@ -71,6 +71,4 @@ export class Attachments extends Component {
         this.listContainer.classList.remove('hidden');
     }
 
-}
-
-export default Attachments;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/back-to-top.js b/resources/js/components/back-to-top.js
index 7a3719493..4f0a46f00 100644
--- a/resources/js/components/back-to-top.js
+++ b/resources/js/components/back-to-top.js
@@ -55,6 +55,4 @@ export class BackToTop extends Component {
         requestAnimationFrame(setPos.bind(this));
     }
 
-}
-
-export default BackToTop;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/code-editor.js b/resources/js/components/code-editor.js
index d0c6c432a..241cdece9 100644
--- a/resources/js/components/code-editor.js
+++ b/resources/js/components/code-editor.js
@@ -1,10 +1,8 @@
 import {onChildEvent, onEnterPress, onSelect} from "../services/dom";
+import {Component} from "./component";
 
-/**
- * Code Editor
- * @extends {Component}
- */
-class CodeEditor {
+
+export class CodeEditor extends Component {
 
     setup() {
         this.container = this.$refs.container;
@@ -184,6 +182,4 @@ class CodeEditor {
         window.sessionStorage.setItem(this.historyKey, historyString);
     }
 
-}
-
-export default CodeEditor;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/code-textarea.js b/resources/js/components/code-textarea.js
index fe16279e7..0e49aec17 100644
--- a/resources/js/components/code-textarea.js
+++ b/resources/js/components/code-textarea.js
@@ -12,6 +12,4 @@ export class CodeTextarea extends Component {
         Code.inlineEditor(this.$el, mode);
     }
 
-}
-
-export default CodeTextarea;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/confirm-dialog.js b/resources/js/components/confirm-dialog.js
index 858be1b85..215c0b94e 100644
--- a/resources/js/components/confirm-dialog.js
+++ b/resources/js/components/confirm-dialog.js
@@ -1,12 +1,12 @@
 import {onSelect} from "../services/dom";
+import {Component} from "./component";
 
 /**
  * Custom equivalent of window.confirm() using our popup component.
  * Is promise based so can be used like so:
  * `const result = await dialog.show()`
- * @extends {Component}
  */
-class ConfirmDialog {
+export class ConfirmDialog extends Component {
 
     setup() {
         this.container = this.$el;
@@ -47,6 +47,4 @@ class ConfirmDialog {
         }
     }
 
-}
-
-export default ConfirmDialog;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/dropzone.js b/resources/js/components/dropzone.js
index 44fdf2d0d..911a033c7 100644
--- a/resources/js/components/dropzone.js
+++ b/resources/js/components/dropzone.js
@@ -1,11 +1,8 @@
 import DropZoneLib from "dropzone";
 import {fadeOut} from "../services/animations";
+import {Component} from "./component";
 
-/**
- * Dropzone
- * @extends {Component}
- */
-class Dropzone {
+export class Dropzone extends Component {
     setup() {
         this.container = this.$el;
         this.url = this.$opts.url;
@@ -73,6 +70,4 @@ class Dropzone {
     removeAll() {
         this.dz.removeAllFiles(true);
     }
-}
-
-export default Dropzone;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/editor-toolbox.js b/resources/js/components/editor-toolbox.js
index 3a1442d75..a581ae7b4 100644
--- a/resources/js/components/editor-toolbox.js
+++ b/resources/js/components/editor-toolbox.js
@@ -1,51 +1,58 @@
-class EditorToolbox {
+import {Component} from "./component";
 
-    constructor(elem) {
+export class EditorToolbox extends Component {
+
+    setup() {
         // Elements
-        this.elem = elem;
-        this.buttons = elem.querySelectorAll('[toolbox-tab-button]');
-        this.contentElements = elem.querySelectorAll('[toolbox-tab-content]');
-        this.toggleButton = elem.querySelector('[toolbox-toggle]');
+        this.container = this.$el;
+        this.buttons = this.$manyRefs.tabButton;
+        this.contentElements = this.$manyRefs.tabContent;
+        this.toggleButton = this.$refs.toggle;
 
-        // Toolbox toggle button click
-        this.toggleButton.addEventListener('click', this.toggle.bind(this));
-        // Tab button click
-        this.elem.addEventListener('click', event => {
-            let button = event.target.closest('[toolbox-tab-button]');
-            if (button === null) return;
-            let name = button.getAttribute('toolbox-tab-button');
-            this.setActiveTab(name, true);
-        });
+        this.setupListeners();
 
         // Set the first tab as active on load
-        this.setActiveTab(this.contentElements[0].getAttribute('toolbox-tab-content'));
+        this.setActiveTab(this.contentElements[0].dataset.tabContent);
+    }
+
+    setupListeners() {
+        // Toolbox toggle button click
+        this.toggleButton.addEventListener('click', () => this.toggle());
+        // Tab button click
+        this.container.addEventListener('click', event => {
+            const button = event.target.closest('button');
+            if (this.buttons.includes(button)) {
+                const name = button.dataset.tab;
+                this.setActiveTab(name, true);
+            }
+        });
     }
 
     toggle() {
-        this.elem.classList.toggle('open');
-        const expanded = this.elem.classList.contains('open') ? 'true' : 'false';
+        this.container.classList.toggle('open');
+        const expanded = this.container.classList.contains('open') ? 'true' : 'false';
         this.toggleButton.setAttribute('aria-expanded', expanded);
     }
 
     setActiveTab(tabName, openToolbox = false) {
+
         // Set button visibility
-        for (let i = 0, len = this.buttons.length; i < len; i++) {
-            this.buttons[i].classList.remove('active');
-            let bName =  this.buttons[i].getAttribute('toolbox-tab-button');
-            if (bName === tabName) this.buttons[i].classList.add('active');
-        }
-        // Set content visibility
-        for (let i = 0, len = this.contentElements.length; i < len; i++) {
-            this.contentElements[i].style.display = 'none';
-            let cName = this.contentElements[i].getAttribute('toolbox-tab-content');
-            if (cName === tabName) this.contentElements[i].style.display = 'block';
+        for (const button of this.buttons) {
+            button.classList.remove('active');
+            const bName =  button.dataset.tab;
+            if (bName === tabName) button.classList.add('active');
         }
 
-        if (openToolbox && !this.elem.classList.contains('open')) {
+        // Set content visibility
+        for (const contentEl of this.contentElements) {
+            contentEl.style.display = 'none';
+            const cName = contentEl.dataset.tabContent;
+            if (cName === tabName) contentEl.style.display = 'block';
+        }
+
+        if (openToolbox && !this.container.classList.contains('open')) {
             this.toggle();
         }
     }
 
-}
-
-export default EditorToolbox;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/entity-search.js b/resources/js/components/entity-search.js
index 8b1861ecf..b0e42401d 100644
--- a/resources/js/components/entity-search.js
+++ b/resources/js/components/entity-search.js
@@ -1,10 +1,7 @@
 import {onSelect} from "../services/dom";
+import {Component} from "./component";
 
-/**
- * Class EntitySearch
- * @extends {Component}
- */
-class EntitySearch {
+export class EntitySearch extends Component {
     setup() {
         this.entityId = this.$opts.entityId;
         this.entityType = this.$opts.entityType;
@@ -54,6 +51,4 @@ class EntitySearch {
         this.loadingBlock.classList.add('hidden');
         this.searchInput.value = '';
     }
-}
-
-export default EntitySearch;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/event-emit-select.js b/resources/js/components/event-emit-select.js
index cf0215850..2e6fd5fdb 100644
--- a/resources/js/components/event-emit-select.js
+++ b/resources/js/components/event-emit-select.js
@@ -1,4 +1,5 @@
 import {onSelect} from "../services/dom";
+import {Component} from "./component";
 
 /**
  * EventEmitSelect
@@ -10,10 +11,8 @@ import {onSelect} from "../services/dom";
  *
  * All options will be set as the "detail" of the event with
  * their values included.
- *
- * @extends {Component}
  */
-class EventEmitSelect {
+export class EventEmitSelect extends Component{
     setup() {
         this.container = this.$el;
         this.name = this.$opts.name;
@@ -24,6 +23,4 @@ class EventEmitSelect {
         });
     }
 
-}
-
-export default EventEmitSelect;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/image-manager.js b/resources/js/components/image-manager.js
index 1222096d4..da842849d 100644
--- a/resources/js/components/image-manager.js
+++ b/resources/js/components/image-manager.js
@@ -1,10 +1,7 @@
 import {onChildEvent, onSelect, removeLoading, showLoading} from "../services/dom";
+import {Component} from "./component";
 
-/**
- * ImageManager
- * @extends {Component}
- */
-class ImageManager {
+export class ImageManager extends Component {
 
     setup() {
 
@@ -210,6 +207,4 @@ class ImageManager {
         window.$components.init(this.formContainer);
     }
 
-}
-
-export default ImageManager;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/index.js b/resources/js/components/index.js
index 0106e82d4..2aac33f7f 100644
--- a/resources/js/components/index.js
+++ b/resources/js/components/index.js
@@ -8,34 +8,34 @@ export {AutoSubmit} from "./auto-submit.js"
 export {BackToTop} from "./back-to-top.js"
 export {BookSort} from "./book-sort.js"
 export {ChapterContents} from "./chapter-contents.js"
-// export {CodeEditor} from "./code-editor.js"
+export {CodeEditor} from "./code-editor.js"
 export {CodeHighlighter} from "./code-highlighter.js"
 export {CodeTextarea} from "./code-textarea.js"
 export {Collapsible} from "./collapsible.js"
-// export {ConfirmDialog} from "./confirm-dialog"
+export {ConfirmDialog} from "./confirm-dialog"
 export {CustomCheckbox} from "./custom-checkbox.js"
 export {DetailsHighlighter} from "./details-highlighter.js"
 export {Dropdown} from "./dropdown.js"
 export {DropdownSearch} from "./dropdown-search.js"
-// export {Dropzone} from "./dropzone.js"
-// export {EditorToolbox} from "./editor-toolbox.js"
+export {Dropzone} from "./dropzone.js"
+export {EditorToolbox} from "./editor-toolbox.js"
 export {EntityPermissions} from "./entity-permissions"
-// export {EntitySearch} from "./entity-search.js"
+export {EntitySearch} from "./entity-search.js"
 export {EntitySelector} from "./entity-selector.js"
 export {EntitySelectorPopup} from "./entity-selector-popup.js"
-// export {EventEmitSelect} from "./event-emit-select.js"
+export {EventEmitSelect} from "./event-emit-select.js"
 export {ExpandToggle} from "./expand-toggle.js"
 export {HeaderMobileToggle} from "./header-mobile-toggle.js"
-// export {ImageManager} from "./image-manager.js"
+export {ImageManager} from "./image-manager.js"
 export {ImagePicker} from "./image-picker.js"
 export {ListSortControl} from "./list-sort-control.js"
-// export {MarkdownEditor} from "./markdown-editor.js"
+export {MarkdownEditor} from "./markdown-editor.js"
 export {NewUserPassword} from "./new-user-password.js"
 export {Notification} from "./notification.js"
 export {OptionalInput} from "./optional-input.js"
 export {PageComments} from "./page-comments.js"
 export {PageDisplay} from "./page-display.js"
-// export {PageEditor} from "./page-editor.js"
+export {PageEditor} from "./page-editor.js"
 export {PagePicker} from "./page-picker.js"
 export {PermissionsTable} from "./permissions-table.js"
 export {Pointer} from "./pointer.js";
@@ -46,13 +46,13 @@ export {SettingHomepageControl} from "./setting-homepage-control.js"
 export {ShelfSort} from "./shelf-sort.js"
 export {Shortcuts} from "./shortcuts"
 export {ShortcutInput} from "./shortcut-input"
-// export {SortableList} from "./sortable-list.js"
+export {SortableList} from "./sortable-list.js"
 export {SubmitOnChange} from "./submit-on-change.js"
-// export {Tabs} from "./tabs.js"
-// export {TagManager} from "./tag-manager.js"
-// export {TemplateManager} from "./template-manager.js"
+export {Tabs} from "./tabs.js"
+export {TagManager} from "./tag-manager.js"
+export {TemplateManager} from "./template-manager.js"
 export {ToggleSwitch} from "./toggle-switch.js"
 export {TriLayout} from "./tri-layout.js"
 export {UserSelect} from "./user-select.js"
 export {WebhookEvents} from "./webhook-events";
-// export {WysiwygEditor} from "./wysiwyg-editor.js"
\ No newline at end of file
+export {WysiwygEditor} from "./wysiwyg-editor.js"
\ No newline at end of file
diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js
index 3290fc300..ee2f0ced2 100644
--- a/resources/js/components/markdown-editor.js
+++ b/resources/js/components/markdown-editor.js
@@ -4,8 +4,9 @@ import Clipboard from "../services/clipboard";
 import {debounce} from "../services/util";
 import {patchDomFromHtmlString} from "../services/vdom";
 import DrawIO from "../services/drawio";
+import {Component} from "./component";
 
-class MarkdownEditor {
+export class MarkdownEditor extends Component {
 
     setup() {
         this.elem = this.$el;
@@ -619,5 +620,3 @@ class MarkdownEditor {
         });
     }
 }
-
-export default MarkdownEditor ;
diff --git a/resources/js/components/page-editor.js b/resources/js/components/page-editor.js
index ce123e987..41e070b9d 100644
--- a/resources/js/components/page-editor.js
+++ b/resources/js/components/page-editor.js
@@ -1,11 +1,8 @@
 import * as Dates from "../services/dates";
 import {onSelect} from "../services/dom";
+import {Component} from "./component";
 
-/**
- * Page Editor
- * @extends {Component}
- */
-class PageEditor {
+export class PageEditor extends Component {
     setup() {
         // Options
         this.draftsEnabled = this.$opts.draftsEnabled === 'true';
@@ -207,6 +204,4 @@ class PageEditor {
         }
     }
 
-}
-
-export default PageEditor;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/shelf-sort.js b/resources/js/components/shelf-sort.js
index aa244086e..d10470bd7 100644
--- a/resources/js/components/shelf-sort.js
+++ b/resources/js/components/shelf-sort.js
@@ -79,6 +79,4 @@ export class ShelfSort extends Component {
         this.input.value = shelfBookElems.map(elem => elem.getAttribute('data-id')).join(',');
     }
 
-}
-
-export default ShelfSort;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/sortable-list.js b/resources/js/components/sortable-list.js
index 0af0e11c9..bbbd92088 100644
--- a/resources/js/components/sortable-list.js
+++ b/resources/js/components/sortable-list.js
@@ -1,4 +1,5 @@
 import Sortable from "sortablejs";
+import {Component} from "./component";
 
 /**
  * SortableList
@@ -6,10 +7,8 @@ import Sortable from "sortablejs";
  * Can have data set on the dragged items by setting a 'data-drag-content' attribute.
  * This attribute must contain JSON where the keys are content types and the values are
  * the data to set on the data-transfer.
- *
- * @extends {Component}
  */
-class SortableList {
+export class SortableList extends Component {
     setup() {
         this.container = this.$el;
         this.handleSelector = this.$opts.handleSelector;
@@ -34,6 +33,4 @@ class SortableList {
             dragoverBubble: false,
         });
     }
-}
-
-export default SortableList;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/tabs.js b/resources/js/components/tabs.js
index 7121d7044..46063d240 100644
--- a/resources/js/components/tabs.js
+++ b/resources/js/components/tabs.js
@@ -1,11 +1,11 @@
+import {onSelect} from "../services/dom";
+import {Component} from "./component";
+
 /**
  * Tabs
  * Works by matching 'tabToggle<Key>' with 'tabContent<Key>' sections.
- * @extends {Component}
  */
-import {onSelect} from "../services/dom";
-
-class Tabs {
+export class Tabs extends Component {
 
     setup() {
         this.tabContentsByName = {};
@@ -46,6 +46,4 @@ class Tabs {
         }
     }
 
-}
-
-export default Tabs;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/tag-manager.js b/resources/js/components/tag-manager.js
index 99302b6c0..b51cfe9b2 100644
--- a/resources/js/components/tag-manager.js
+++ b/resources/js/components/tag-manager.js
@@ -1,8 +1,6 @@
-/**
- * TagManager
- * @extends {Component}
- */
-class TagManager {
+import {Component} from "./component";
+
+export class TagManager extends Component {
     setup() {
         this.addRemoveComponentEl = this.$refs.addRemove;
         this.container = this.$el;
@@ -27,6 +25,4 @@ class TagManager {
         });
         return firstEmpty !== undefined;
     }
-}
-
-export default TagManager;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/template-manager.js b/resources/js/components/template-manager.js
index f8b19a40c..774336471 100644
--- a/resources/js/components/template-manager.js
+++ b/resources/js/components/template-manager.js
@@ -1,25 +1,48 @@
 import * as DOM from "../services/dom";
+import {Component} from "./component";
 
-class TemplateManager {
+export class TemplateManager extends Component {
 
-    constructor(elem) {
-        this.elem = elem;
-        this.list = elem.querySelector('[template-manager-list]');
-        this.searching = false;
+    setup() {
+        this.container = this.$el;
+        this.list = this.$refs.list;
 
+        this.searchInput = this.$refs.searchInput;
+        this.searchButton = this.$refs.searchButton;
+        this.searchCancel = this.$refs.searchCancel;
+
+        this.setupListeners();
+    }
+
+    setupListeners() {
         // Template insert action buttons
-        DOM.onChildEvent(this.elem, '[template-action]', 'click', this.handleTemplateActionClick.bind(this));
+        DOM.onChildEvent(this.container, '[template-action]', 'click', this.handleTemplateActionClick.bind(this));
 
         // Template list pagination click
-        DOM.onChildEvent(this.elem, '.pagination a', 'click', this.handlePaginationClick.bind(this));
+        DOM.onChildEvent(this.container, '.pagination a', 'click', this.handlePaginationClick.bind(this));
 
         // Template list item content click
-        DOM.onChildEvent(this.elem, '.template-item-content', 'click', this.handleTemplateItemClick.bind(this));
+        DOM.onChildEvent(this.container, '.template-item-content', 'click', this.handleTemplateItemClick.bind(this));
 
         // Template list item drag start
-        DOM.onChildEvent(this.elem, '.template-item', 'dragstart', this.handleTemplateItemDragStart.bind(this));
+        DOM.onChildEvent(this.container, '.template-item', 'dragstart', this.handleTemplateItemDragStart.bind(this));
 
-        this.setupSearchBox();
+        // Search box enter press
+        this.searchInput.addEventListener('keypress', event => {
+            if (event.key === 'Enter') {
+                event.preventDefault();
+                this.performSearch();
+            }
+        });
+
+        // Search submit button press
+        this.searchButton.addEventListener('click', event => this.performSearch());
+
+        // Search cancel button press
+        this.searchCancel.addEventListener('click', event => {
+            this.searchInput.value = '';
+            this.performSearch();
+        });
     }
 
     handleTemplateItemClick(event, templateItem) {
@@ -54,45 +77,12 @@ class TemplateManager {
         this.list.innerHTML = resp.data;
     }
 
-    setupSearchBox() {
-        const searchBox = this.elem.querySelector('.search-box');
-
-        // Search box may not exist if there are no existing templates in the system.
-        if (!searchBox) return;
-
-        const input = searchBox.querySelector('input');
-        const submitButton = searchBox.querySelector('button');
-        const cancelButton = searchBox.querySelector('button.search-box-cancel');
-
-        async function performSearch() {
-            const searchTerm = input.value;
-            const resp = await window.$http.get(`/templates`, {
-                search: searchTerm
-            });
-            cancelButton.style.display = searchTerm ? 'block' : 'none';
-            this.list.innerHTML = resp.data;
-        }
-        performSearch = performSearch.bind(this);
-
-        // Search box enter press
-        searchBox.addEventListener('keypress', event => {
-            if (event.key === 'Enter') {
-                event.preventDefault();
-                performSearch();
-            }
-        });
-
-        // Submit button press
-        submitButton.addEventListener('click', event => {
-            performSearch();
-        });
-
-        // Cancel button press
-        cancelButton.addEventListener('click', event => {
-            input.value = '';
-            performSearch();
+    async performSearch() {
+        const searchTerm = this.searchInput.value;
+        const resp = await window.$http.get(`/templates`, {
+            search: searchTerm
         });
+        this.searchCancel.style.display = searchTerm ? 'block' : 'none';
+        this.list.innerHTML = resp.data;
     }
-}
-
-export default TemplateManager;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/resources/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js
index 446f2ca49..976dba68f 100644
--- a/resources/js/components/wysiwyg-editor.js
+++ b/resources/js/components/wysiwyg-editor.js
@@ -1,6 +1,7 @@
 import {build as buildEditorConfig} from "../wysiwyg/config";
+import {Component} from "./component";
 
-class WysiwygEditor {
+export class WysiwygEditor extends Component {
 
     setup() {
         this.elem = this.$el;
@@ -35,6 +36,4 @@ class WysiwygEditor {
         return '';
     }
 
-}
-
-export default WysiwygEditor;
+}
\ No newline at end of file
diff --git a/resources/js/wysiwyg/shortcuts.js b/resources/js/wysiwyg/shortcuts.js
index b42851a46..ef364ddad 100644
--- a/resources/js/wysiwyg/shortcuts.js
+++ b/resources/js/wysiwyg/shortcuts.js
@@ -44,7 +44,9 @@ export function register(editor) {
 
     // Link selector shortcut
     editor.shortcuts.add('meta+shift+K', '', function() {
-        window.EntitySelectorPopup.show(function(entity) {
+        /** @var {EntitySelectorPopup} **/
+        const selectorPopup = window.$components.first('entity-selector-popup');
+        selectorPopup.show(function(entity) {
 
             if (editor.selection.isCollapsed()) {
                 editor.insertContent(editor.dom.createHTML('a', {href: entity.link}, editor.dom.encode(entity.name)));
diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss
index eeb51ebb5..720203a42 100755
--- a/resources/sass/_pages.scss
+++ b/resources/sass/_pages.scss
@@ -278,16 +278,16 @@ body.tox-fullscreen, body.markdown-fullscreen {
   &.open {
     width: 480px;
   }
-  [toolbox-toggle] svg {
+  .toolbox-toggle svg {
     transition: transform ease-in-out 180ms;
   }
-  [toolbox-toggle] {
+  .toolbox-toggle {
     transition: background-color ease-in-out 180ms;
   }
-  &.open [toolbox-toggle] {
+  &.open .toolbox-toggle {
     background-color: rgba(255, 0, 0, 0.29);
   }
-  &.open [toolbox-toggle] svg {
+  &.open .toolbox-toggle svg {
     transform: rotate(180deg);
   }
   > div {
@@ -357,7 +357,7 @@ body.tox-fullscreen, body.markdown-fullscreen {
   }
 }
 
-[toolbox-tab-content] {
+.toolbox-tab-content {
   display: none;
 }
 
diff --git a/resources/views/attachments/manager.blade.php b/resources/views/attachments/manager.blade.php
index 024cb583c..724ca9c8e 100644
--- a/resources/views/attachments/manager.blade.php
+++ b/resources/views/attachments/manager.blade.php
@@ -1,6 +1,9 @@
-<div style="display: block;" toolbox-tab-content="files"
+<div style="display: block;"
+     refs="editor-toolbox@tab-content"
+     data-tab-content="files"
      component="attachments"
-     option:attachments:page-id="{{ $page->id ?? 0 }}">
+     option:attachments:page-id="{{ $page->id ?? 0 }}"
+     class="toolbox-tab-content">
 
     <h4>{{ trans('entities.attachments') }}</h4>
     <div class="px-l files">
diff --git a/resources/views/pages/parts/editor-toolbox.blade.php b/resources/views/pages/parts/editor-toolbox.blade.php
index f3b54ddcd..8f3327024 100644
--- a/resources/views/pages/parts/editor-toolbox.blade.php
+++ b/resources/views/pages/parts/editor-toolbox.blade.php
@@ -1,15 +1,15 @@
-<div editor-toolbox class="floating-toolbox">
+<div component="editor-toolbox" class="floating-toolbox">
 
     <div class="tabs primary-background-light">
-        <button type="button" toolbox-toggle aria-expanded="false">@icon('caret-left-circle')</button>
-        <button type="button" toolbox-tab-button="tags" title="{{ trans('entities.page_tags') }}" class="active">@icon('tag')</button>
+        <button type="button" refs="editor-toolbox@toggle" aria-expanded="false" class="toolbox-toggle">@icon('caret-left-circle')</button>
+        <button type="button" refs="editor-toolbox@tab-button" data-tab="tags" title="{{ trans('entities.page_tags') }}" class="active">@icon('tag')</button>
         @if(userCan('attachment-create-all'))
-            <button type="button" toolbox-tab-button="files" title="{{ trans('entities.attachments') }}">@icon('attach')</button>
+            <button type="button" refs="editor-toolbox@tab-button" data-tab="files" title="{{ trans('entities.attachments') }}">@icon('attach')</button>
         @endif
-        <button type="button" toolbox-tab-button="templates" title="{{ trans('entities.templates') }}">@icon('template')</button>
+        <button type="button" refs="editor-toolbox@tab-button" data-tab="templates" title="{{ trans('entities.templates') }}">@icon('template')</button>
     </div>
 
-    <div toolbox-tab-content="tags">
+    <div refs="editor-toolbox@tab-content" data-tab-content="tags" class="toolbox-tab-content">
         <h4>{{ trans('entities.page_tags') }}</h4>
         <div class="px-l">
             @include('entities.tag-manager', ['entity' => $page])
@@ -20,7 +20,7 @@
         @include('attachments.manager', ['page' => $page])
     @endif
 
-    <div toolbox-tab-content="templates">
+    <div refs="editor-toolbox@tab-content" data-tab-content="templates" class="toolbox-tab-content">
         <h4>{{ trans('entities.templates') }}</h4>
 
         <div class="px-l">
diff --git a/resources/views/pages/parts/template-manager.blade.php b/resources/views/pages/parts/template-manager.blade.php
index 66d53ae7e..43c161ad2 100644
--- a/resources/views/pages/parts/template-manager.blade.php
+++ b/resources/views/pages/parts/template-manager.blade.php
@@ -1,4 +1,4 @@
-<div template-manager>
+<div component="template-manager">
     @if(userCan('templates-manage'))
         <p class="text-muted small mb-none">
             {{ trans('entities.templates_explain_set_as_template') }}
@@ -11,15 +11,13 @@
         <hr>
     @endif
 
-    @if(count($templates) > 0)
-        <div class="search-box flexible mb-m">
-            <input type="text" name="template-search" placeholder="{{ trans('common.search') }}">
-            <button type="button">@icon('search')</button>
-            <button class="search-box-cancel text-neg hidden" type="button">@icon('close')</button>
-        </div>
-    @endif
+    <div class="search-box flexible mb-m" style="display: {{ count($templates) > 0 ? 'block' : 'none' }}">
+        <input refs="template-manager@searchInput" type="text" name="template-search" placeholder="{{ trans('common.search') }}">
+        <button refs="template-manager@searchButton" type="button">@icon('search')</button>
+        <button refs="template-manager@searchCancel" class="search-box-cancel text-neg" type="button" style="display: none">@icon('close')</button>
+    </div>
 
-    <div template-manager-list>
+    <div refs="template-manager@list">
         @include('pages.parts.template-manager-list', ['templates' => $templates])
     </div>
 </div>
\ No newline at end of file