diff --git a/client/js/app.js b/client/js/app.js
index f2a1e8f5..397a399c 100644
--- a/client/js/app.js
+++ b/client/js/app.js
@@ -17,37 +17,6 @@ import 'jquery-simple-upload'
 import 'jquery-smooth-scroll'
 import 'jquery-sticky'
 
-// ====================================
-// Load minimal lodash
-// ====================================
-
-import cloneDeep from 'lodash/cloneDeep'
-import concat from 'lodash/concat'
-import debounce from 'lodash/debounce'
-import deburr from 'lodash/deburr'
-import delay from 'lodash/delay'
-import filter from 'lodash/filter'
-import find from 'lodash/find'
-import findKey from 'lodash/findKey'
-import forEach from 'lodash/forEach'
-import includes from 'lodash/includes'
-import isBoolean from 'lodash/isBoolean'
-import isEmpty from 'lodash/isEmpty'
-import isNil from 'lodash/isNil'
-import join from 'lodash/join'
-import kebabCase from 'lodash/kebabCase'
-import last from 'lodash/last'
-import map from 'lodash/map'
-import nth from 'lodash/nth'
-import pullAt from 'lodash/pullAt'
-import reject from 'lodash/reject'
-import slice from 'lodash/slice'
-import split from 'lodash/split'
-import startCase from 'lodash/startCase'
-import toString from 'lodash/toString'
-import toUpper from 'lodash/toUpper'
-import trim from 'lodash/trim'
-
 // ====================================
 // Load Helpers
 // ====================================
@@ -81,43 +50,11 @@ import treeComponent from './components/tree.vue'
 import adminEditUserComponent from './pages/admin-edit-user.component.js'
 import adminProfileComponent from './pages/admin-profile.component.js'
 import adminSettingsComponent from './pages/admin-settings.component.js'
+import adminThemeComponent from './pages/admin-theme.component.js'
 import contentViewComponent from './pages/content-view.component.js'
 import editorComponent from './components/editor.component.js'
 import sourceViewComponent from './pages/source-view.component.js'
 
-// ====================================
-// Build lodash object
-// ====================================
-
-const _ = {
-  deburr,
-  concat,
-  cloneDeep,
-  debounce,
-  delay,
-  filter,
-  find,
-  findKey,
-  forEach,
-  includes,
-  isBoolean,
-  isEmpty,
-  isNil,
-  join,
-  kebabCase,
-  last,
-  map,
-  nth,
-  pullAt,
-  reject,
-  slice,
-  split,
-  startCase,
-  toString,
-  toUpper,
-  trim
-}
-
 // ====================================
 // Initialize Vue Modules
 // ====================================
@@ -125,9 +62,44 @@ const _ = {
 Vue.use(VueResource)
 Vue.use(VueClipboards)
 Vue.use(VueI18Next)
-Vue.use(VueLodash, _)
+Vue.use(VueLodash, helpers._)
 Vue.use(helpers)
 
+// ====================================
+// Register Vue Components
+// ====================================
+
+Vue.component('alert', alertComponent)
+Vue.component('adminEditUser', adminEditUserComponent)
+Vue.component('adminProfile', adminProfileComponent)
+Vue.component('adminSettings', adminSettingsComponent)
+Vue.component('adminTheme', adminThemeComponent)
+Vue.component('anchor', anchorComponent)
+Vue.component('colorPicker', colorPickerComponent)
+Vue.component('contentView', contentViewComponent)
+Vue.component('editor', editorComponent)
+Vue.component('editorCodeblock', editorCodeblockComponent)
+Vue.component('editorFile', editorFileComponent)
+Vue.component('editorVideo', editorVideoComponent)
+Vue.component('history', historyComponent)
+Vue.component('loadingSpinner', loadingSpinnerComponent)
+Vue.component('modalCreatePage', modalCreatePageComponent)
+Vue.component('modalCreateUser', modalCreateUserComponent)
+Vue.component('modalDeleteUser', modalDeleteUserComponent)
+Vue.component('modalDiscardPage', modalDiscardPageComponent)
+Vue.component('modalMovePage', modalMovePageComponent)
+Vue.component('modalProfile2fa', modalProfile2faComponent)
+Vue.component('modalUpgradeSystem', modalUpgradeSystemComponent)
+Vue.component('pageLoader', pageLoaderComponent)
+Vue.component('search', searchComponent)
+Vue.component('sourceView', sourceViewComponent)
+Vue.component('toggle', toggleComponent)
+Vue.component('tree', treeComponent)
+
+// ====================================
+// Load Localization strings
+// ====================================
+
 i18next
   .use(i18nextXHR)
   .init({
@@ -166,33 +138,7 @@ $(() => {
   const i18n = new VueI18Next(i18next)
   window.wikijs = new Vue({
     mixins: [helpers],
-    components: {
-      alert: alertComponent,
-      adminEditUser: adminEditUserComponent,
-      adminProfile: adminProfileComponent,
-      adminSettings: adminSettingsComponent,
-      anchor: anchorComponent,
-      colorPicker: colorPickerComponent,
-      contentView: contentViewComponent,
-      editor: editorComponent,
-      editorCodeblock: editorCodeblockComponent,
-      editorFile: editorFileComponent,
-      editorVideo: editorVideoComponent,
-      history: historyComponent,
-      loadingSpinner: loadingSpinnerComponent,
-      modalCreatePage: modalCreatePageComponent,
-      modalCreateUser: modalCreateUserComponent,
-      modalDeleteUser: modalDeleteUserComponent,
-      modalDiscardPage: modalDiscardPageComponent,
-      modalMovePage: modalMovePageComponent,
-      modalProfile2fa: modalProfile2faComponent,
-      modalUpgradeSystem: modalUpgradeSystemComponent,
-      pageLoader: pageLoaderComponent,
-      search: searchComponent,
-      sourceView: sourceViewComponent,
-      toggle: toggleComponent,
-      tree: treeComponent
-    },
+    components: {},
     store,
     i18n,
     el: '#root',
diff --git a/client/js/components/color-picker.vue b/client/js/components/color-picker.vue
index 3f180e63..6abdc445 100644
--- a/client/js/components/color-picker.vue
+++ b/client/js/components/color-picker.vue
@@ -1,12 +1,12 @@
 <template lang="pug">
   .colorpicker
-    .colorpicker-choice(v-for='color in colors', :class='["is-" + color, color === currentColor ? "is-active" : ""]', @click='setColor(color)')
+    .colorpicker-choice(v-for='color in colors', :class='["is-" + color, color === value ? "is-active" : ""]', @click='setColor(color)')
 </template>
 
 <script>
   export default {
     name: 'color-picker',
-    props: ['currentColor'],
+    props: ['value'],
     data () {
       return {
         colors: [
@@ -34,7 +34,7 @@
     },
     methods: {
       setColor(color) {
-        this.currentColor = color
+        this.$emit('input', color)
       }
     }
   }
diff --git a/client/js/components/toggle.vue b/client/js/components/toggle.vue
index f64fe9d7..15436dbe 100644
--- a/client/js/components/toggle.vue
+++ b/client/js/components/toggle.vue
@@ -1,5 +1,5 @@
 <template lang="pug">
-  .toggle(:class='{ "is-active": currentValue }', @click='changeToggle')
+  .toggle(:class='{ "is-active": value }', @click='changeToggle')
     .toggle-container
       .toggle-pin
     .toggle-text {{ desc }}
@@ -8,13 +8,13 @@
 <script>
   export default {
     name: 'toggle',
-    props: ['currentValue', 'desc'],
+    props: ['value', 'desc'],
     data () {
       return { }
     },
     methods: {
       changeToggle() {
-        this.currentValue = !this.currentValue
+        this.$emit('input', !this.value)
       }
     }
   }
diff --git a/client/js/helpers/index.js b/client/js/helpers/index.js
index 6f802e8b..6335809b 100644
--- a/client/js/helpers/index.js
+++ b/client/js/helpers/index.js
@@ -1,6 +1,7 @@
 'use strict'
 
 const helpers = {
+  _: require('./lodash'),
   common: require('./common'),
   form: require('./form'),
   pages: require('./pages')
diff --git a/client/js/helpers/lodash.js b/client/js/helpers/lodash.js
new file mode 100644
index 00000000..5373a77b
--- /dev/null
+++ b/client/js/helpers/lodash.js
@@ -0,0 +1,65 @@
+'use strict'
+
+// ====================================
+// Load minimal lodash
+// ====================================
+
+import cloneDeep from 'lodash/cloneDeep'
+import concat from 'lodash/concat'
+import debounce from 'lodash/debounce'
+import deburr from 'lodash/deburr'
+import delay from 'lodash/delay'
+import filter from 'lodash/filter'
+import find from 'lodash/find'
+import findKey from 'lodash/findKey'
+import forEach from 'lodash/forEach'
+import includes from 'lodash/includes'
+import isBoolean from 'lodash/isBoolean'
+import isEmpty from 'lodash/isEmpty'
+import isNil from 'lodash/isNil'
+import join from 'lodash/join'
+import kebabCase from 'lodash/kebabCase'
+import last from 'lodash/last'
+import map from 'lodash/map'
+import nth from 'lodash/nth'
+import pullAt from 'lodash/pullAt'
+import reject from 'lodash/reject'
+import slice from 'lodash/slice'
+import split from 'lodash/split'
+import startCase from 'lodash/startCase'
+import toString from 'lodash/toString'
+import toUpper from 'lodash/toUpper'
+import trim from 'lodash/trim'
+
+// ====================================
+// Build lodash object
+// ====================================
+
+export default {
+  deburr,
+  concat,
+  cloneDeep,
+  debounce,
+  delay,
+  filter,
+  find,
+  findKey,
+  forEach,
+  includes,
+  isBoolean,
+  isEmpty,
+  isNil,
+  join,
+  kebabCase,
+  last,
+  map,
+  nth,
+  pullAt,
+  reject,
+  slice,
+  split,
+  startCase,
+  toString,
+  toUpper,
+  trim
+}
diff --git a/client/js/pages/admin-theme.component.js b/client/js/pages/admin-theme.component.js
new file mode 100644
index 00000000..4cbafc2e
--- /dev/null
+++ b/client/js/pages/admin-theme.component.js
@@ -0,0 +1,48 @@
+'use strict'
+
+export default {
+  name: 'admin-theme',
+  props: ['themedata'],
+  data() {
+    return {
+      primary: 'indigo',
+      alt: 'blue-grey',
+      footer: 'blue-grey',
+      codedark: 'true',
+      codecolorize: 'true'
+    }
+  },
+  methods: {
+    saveTheme() {
+      let self = this
+      this.$http.post(window.location.href, self.$data).then(resp => {
+        self.$store.dispatch('alert', {
+          style: 'green',
+          icon: 'check',
+          msg: 'Theme settings have been applied successfully.'
+        })
+      }).catch(err => {
+        self.$store.dispatch('alert', {
+          style: 'red',
+          icon: 'square-cross',
+          msg: 'Error: ' + err.body.msg
+        })
+      })
+    },
+    resetTheme() {
+      this.primary = 'indigo'
+      this.alt = 'blue-grey'
+      this.footer = 'blue-grey'
+      this.codedark = 'true'
+      this.codecolorize = 'true'
+    }
+  },
+  mounted() {
+    let theme = JSON.parse(this.themedata)
+    this.primary = theme.primary
+    this.alt = theme.alt
+    this.footer = theme.footer
+    this.codedark = theme.code.dark.toString()
+    this.codecolorize = theme.code.colorize.toString()
+  }
+}
diff --git a/server/locales/en/admin.json b/server/locales/en/admin.json
index 7a46d992..1df4ed38 100644
--- a/server/locales/en/admin.json
+++ b/server/locales/en/admin.json
@@ -9,7 +9,12 @@
     "passwordverify": "Verify Password",
     "provider": "Provider",
     "savechanges": "Save Changes",
-    "subtitle": "Profile and authentication info"
+    "subtitle": "Profile and authentication info",
+    "tfa": "Two-Factor Authentication",
+    "tfadisable": "Disable 2FA",
+    "tfadisabled": "Disabled",
+    "tfaenable": "Enable 2FA",
+    "tfaenabled": "Enabled"
   },
   "stats": {
     "subtitle": "General site-wide statistics",
@@ -36,6 +41,25 @@
     "flushsessionstext": "All users will be logged out and forced to login again. Your current session will also be affected!",
     "flushsessionsbtn": "Flush Sessions"
   },
+  "system": {
+    "subtitle": "Information and utilities for your wiki"
+  },
+  "theme": {
+    "subtitle": "Change the look and feel of your wiki",
+    "primarycolor": "Primary Color",
+    "primarycolordesc": "Used for top navigation bar, headers, links, etc.",
+    "altcolor": "Alternate Color",
+    "altcolordesc": "Used for the sidebar (in a darker tone)",
+    "footercolor": "Footer Color",
+    "footercolordesc": "Used for the foter (in a lighter tone)",
+    "codeblock": {
+      "title": "Code Blocks",
+      "dark": "Use Dark Theme",
+      "colorize": "Colorize code syntax"
+    },
+    "savechanges": "Save Changes",
+    "reset": "Revert to Defaults"
+  },
   "users": {
     "createauthorize": "Create / Authorize User",
     "subtitle": "Manage users and access rights",
@@ -48,4 +72,4 @@
     "edituser": "Edit User",
     "uniqueid": "Unique ID"
   }
-}
\ No newline at end of file
+}
diff --git a/server/locales/en/common.json b/server/locales/en/common.json
index 18ce4087..32dd6930 100644
--- a/server/locales/en/common.json
+++ b/server/locales/en/common.json
@@ -33,6 +33,7 @@
     "settings": "Settings",
     "source": "Source",
     "stats": "Stats",
+    "sysinfo": "System Info",
     "syssettings": "System Settings",
     "theme": "Color Theme",
     "users": "Users",
@@ -47,4 +48,4 @@
     "source": "Loading source...",
     "editor": "Loading editor..."
   }
-}
\ No newline at end of file
+}
diff --git a/server/views/pages/admin/_layout.pug b/server/views/pages/admin/_layout.pug
index d2fe6190..2776c8b9 100644
--- a/server/views/pages/admin/_layout.pug
+++ b/server/views/pages/admin/_layout.pug
@@ -46,7 +46,7 @@ block content
                     span= t('nav.users')
                 li
                   a(href='/admin/theme')
-                    i.nc-icon-outline.design_paint-37
+                    i.nc-icon-outline.ui-1_drop
                     span= t('nav.theme')
                 li
                   a(href='/admin/settings')
diff --git a/server/views/pages/admin/theme.pug b/server/views/pages/admin/theme.pug
index dcd330d5..df4e2dce 100644
--- a/server/views/pages/admin/theme.pug
+++ b/server/views/pages/admin/theme.pug
@@ -4,25 +4,29 @@ block adminContent
   .hero
     h1.title#title= t('nav.theme')
     h2.subtitle= t('admin:theme.subtitle')
-    i.pageicon.nc-icon-outline.design_paint-37
-  .form-sections
-    section
-      label.label= t('admin:theme.primarycolor')
-      color-picker(current-color=appconfig.theme.primary)
-      span.desc Used for top navigation bar, headers, links, etc.
-    section
-      label.label= t('admin:theme.altcolor')
-      color-picker(current-color=appconfig.theme.alt)
-      span.desc Used for the sidebar (in a darker tone)
-    section
-      label.label= t('admin:theme.footercolor')
-      color-picker(current-color=appconfig.theme.footer)
-      span.desc Used for the foter (in a lighter tone)
-    section
-      label.label= t('admin:theme.codeblock')
-      toggle(:current-value=appconfig.theme.code.dark.toString(), desc='Use Dark Theme')
-      toggle(:current-value=appconfig.theme.code.colorize.toString(), desc='Colorize code syntax')
-    section
-      button.button.is-green(@click='saveUser')
-        i.nc-icon-outline.ui-1_check
-        span= t('admin:profile.savechanges')
+    i.pageicon.nc-icon-outline.ui-1_drop
+  admin-theme(inline-template, themedata=JSON.stringify(appconfig.theme))
+    .form-sections
+      section
+        label.label= t('admin:theme.primarycolor')
+        color-picker(v-model='primary')
+        span.desc= t('admin:theme.primarycolordesc')
+      section
+        label.label= t('admin:theme.altcolor')
+        color-picker(v-model='alt')
+        span.desc= t('admin:theme.altcolordesc')
+      section
+        label.label= t('admin:theme.footercolor')
+        color-picker(v-model='footer')
+        span.desc= t('admin:theme.footercolordesc')
+      section
+        label.label= t('admin:theme.codeblock.title')
+        toggle(v-model='codedark', desc=t('admin:theme.codeblock.dark'))
+        toggle(v-model='codecolorize', desc=t('admin:theme.codeblock.colorize'))
+      section
+        button.button.is-grey(@click='resetTheme')
+          i.nc-icon-outline.design_paint-37
+          span= t('admin:theme.reset')
+        button.button.is-green(@click='saveTheme')
+          i.nc-icon-outline.ui-1_check
+          span= t('admin:theme.savechanges')