diff --git a/bundles/org.openhab.ui/web/package-lock.json b/bundles/org.openhab.ui/web/package-lock.json
index 2ac13b3c5..7c16306d4 100644
--- a/bundles/org.openhab.ui/web/package-lock.json
+++ b/bundles/org.openhab.ui/web/package-lock.json
@@ -11,17 +11,21 @@
       "dependencies": {
         "@blockly/field-slider": "^2.1.10",
         "@blockly/zoom-to-fit": "^2.0.24",
+        "@jsep-plugin/arrow": "^1.0.5",
+        "@jsep-plugin/object": "^1.2.1",
+        "@jsep-plugin/regex": "^1.0.3",
+        "@jsep-plugin/template": "^1.0.2",
         "blockly": "^6.20210701.0",
         "cronstrue": "^1.100.0",
         "dayjs": "^1.9.6",
         "dom7": "^2.1.5",
         "echarts": "^5.1.2",
         "event-source-polyfill": "^1.0.22",
-        "expression-eval": "^2.1.0",
         "fast-deep-equal": "^3.1.3",
         "framework7": "^5.7.12",
         "framework7-icons": "^3.0.1",
         "framework7-vue": "^5.7.12",
+        "jse-eval": "^1.5.1",
         "jssip": "^3.9.1",
         "leaflet": "^1.7.1",
         "leaflet-providers": "^1.11.0",
@@ -3407,6 +3411,50 @@
         "node": ">= 6"
       }
     },
+    "node_modules/@jsep-plugin/arrow": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/arrow/-/arrow-1.0.5.tgz",
+      "integrity": "sha512-4Q9/6nETEf79DQdyynPk9G5CvYGw/TyRAw6IpkiIBm1z6eyDyjhcLjYxmBCqlKIUvjS8h8hfU8MzSjQRSntK5Q==",
+      "engines": {
+        "node": ">= 10.16.0"
+      },
+      "peerDependencies": {
+        "jsep": "^0.4.0||^1.0.0"
+      }
+    },
+    "node_modules/@jsep-plugin/object": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/object/-/object-1.2.1.tgz",
+      "integrity": "sha512-6YoZP80h2QFCuxyqj+OvoqEnTu2r5cSRpgpvGauWlvnevFP/F/dibpvXDpnHeqwT2FIzzvg47YOe3QD/UT8vJw==",
+      "engines": {
+        "node": ">= 10.16.0"
+      },
+      "peerDependencies": {
+        "jsep": "^0.4.0||^1.0.0"
+      }
+    },
+    "node_modules/@jsep-plugin/regex": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.3.tgz",
+      "integrity": "sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug==",
+      "engines": {
+        "node": ">= 10.16.0"
+      },
+      "peerDependencies": {
+        "jsep": "^0.4.0||^1.0.0"
+      }
+    },
+    "node_modules/@jsep-plugin/template": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/template/-/template-1.0.2.tgz",
+      "integrity": "sha512-fGIPHL4W/YZ3YShDByfWlLEIMCLRUTZDUDii4Xat4sJ+DTYeS21RWrZkvbprICyiwmO/fRY7xdHZh7K/ng6xLg==",
+      "engines": {
+        "node": ">= 10.16.0"
+      },
+      "peerDependencies": {
+        "jsep": "^0.4.0||^1.0.0"
+      }
+    },
     "node_modules/@relative-ci/agent": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@relative-ci/agent/-/agent-1.5.0.tgz",
@@ -9963,14 +10011,6 @@
         "node": ">=0.6"
       }
     },
-    "node_modules/expression-eval": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/expression-eval/-/expression-eval-2.1.0.tgz",
-      "integrity": "sha512-FUJO/Akvl/JOWkvlqZaqbkhsEWlCJWDeZG4tzX96UH68D9FeRgYgtb55C2qtqbORC0Q6x5419EDjWu4IT9kQfg==",
-      "dependencies": {
-        "jsep": "^0.3.0"
-      }
-    },
     "node_modules/extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -12942,12 +12982,20 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/jse-eval": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/jse-eval/-/jse-eval-1.5.1.tgz",
+      "integrity": "sha512-wnMRaxvZUuBMxMNZd5Xs5z30c9Glb8p7hFERxrzdNKcWnqSAdvQM7+c+NCloaTniV/NtYpuQSsqEF+Twc6T71Q==",
+      "dependencies": {
+        "jsep": "^1.2.0"
+      }
+    },
     "node_modules/jsep": {
-      "version": "0.3.4",
-      "resolved": "https://registry.npmjs.org/jsep/-/jsep-0.3.4.tgz",
-      "integrity": "sha512-ovGD9wE+wvudIIYxZGrRcZCxNyZ3Cl1N7Bzyp7/j4d/tA0BaUwcVM9bu0oZaSrefMiNwv6TwZ9X15gvZosteCQ==",
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.7.tgz",
+      "integrity": "sha512-NFbZTr1t13fPKw53swmZFKwBkEDWDnno7uLJk+a+Rw9tGDTkGgnGdZJ8A/o3gR1+XaAXmSsbpfIBIBgqRBZWDA==",
       "engines": {
-        "node": ">= 0.10.0"
+        "node": ">= 10.16.0"
       }
     },
     "node_modules/jsesc": {
@@ -25910,6 +25958,30 @@
         "@types/yargs": "^13.0.0"
       }
     },
+    "@jsep-plugin/arrow": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/arrow/-/arrow-1.0.5.tgz",
+      "integrity": "sha512-4Q9/6nETEf79DQdyynPk9G5CvYGw/TyRAw6IpkiIBm1z6eyDyjhcLjYxmBCqlKIUvjS8h8hfU8MzSjQRSntK5Q==",
+      "requires": {}
+    },
+    "@jsep-plugin/object": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/object/-/object-1.2.1.tgz",
+      "integrity": "sha512-6YoZP80h2QFCuxyqj+OvoqEnTu2r5cSRpgpvGauWlvnevFP/F/dibpvXDpnHeqwT2FIzzvg47YOe3QD/UT8vJw==",
+      "requires": {}
+    },
+    "@jsep-plugin/regex": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.3.tgz",
+      "integrity": "sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug==",
+      "requires": {}
+    },
+    "@jsep-plugin/template": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@jsep-plugin/template/-/template-1.0.2.tgz",
+      "integrity": "sha512-fGIPHL4W/YZ3YShDByfWlLEIMCLRUTZDUDii4Xat4sJ+DTYeS21RWrZkvbprICyiwmO/fRY7xdHZh7K/ng6xLg==",
+      "requires": {}
+    },
     "@relative-ci/agent": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@relative-ci/agent/-/agent-1.5.0.tgz",
@@ -31455,14 +31527,6 @@
         }
       }
     },
-    "expression-eval": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/expression-eval/-/expression-eval-2.1.0.tgz",
-      "integrity": "sha512-FUJO/Akvl/JOWkvlqZaqbkhsEWlCJWDeZG4tzX96UH68D9FeRgYgtb55C2qtqbORC0Q6x5419EDjWu4IT9kQfg==",
-      "requires": {
-        "jsep": "^0.3.0"
-      }
-    },
     "extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -33890,10 +33954,18 @@
         }
       }
     },
+    "jse-eval": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/jse-eval/-/jse-eval-1.5.1.tgz",
+      "integrity": "sha512-wnMRaxvZUuBMxMNZd5Xs5z30c9Glb8p7hFERxrzdNKcWnqSAdvQM7+c+NCloaTniV/NtYpuQSsqEF+Twc6T71Q==",
+      "requires": {
+        "jsep": "^1.2.0"
+      }
+    },
     "jsep": {
-      "version": "0.3.4",
-      "resolved": "https://registry.npmjs.org/jsep/-/jsep-0.3.4.tgz",
-      "integrity": "sha512-ovGD9wE+wvudIIYxZGrRcZCxNyZ3Cl1N7Bzyp7/j4d/tA0BaUwcVM9bu0oZaSrefMiNwv6TwZ9X15gvZosteCQ=="
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.7.tgz",
+      "integrity": "sha512-NFbZTr1t13fPKw53swmZFKwBkEDWDnno7uLJk+a+Rw9tGDTkGgnGdZJ8A/o3gR1+XaAXmSsbpfIBIBgqRBZWDA=="
     },
     "jsesc": {
       "version": "2.5.2",
diff --git a/bundles/org.openhab.ui/web/package.json b/bundles/org.openhab.ui/web/package.json
index c9e6078fa..3cab8ba56 100644
--- a/bundles/org.openhab.ui/web/package.json
+++ b/bundles/org.openhab.ui/web/package.json
@@ -63,17 +63,21 @@
   "dependencies": {
     "@blockly/field-slider": "^2.1.10",
     "@blockly/zoom-to-fit": "^2.0.24",
+    "@jsep-plugin/arrow": "^1.0.5",
+    "@jsep-plugin/object": "^1.2.1",
+    "@jsep-plugin/regex": "^1.0.3",
+    "@jsep-plugin/template": "^1.0.2",
     "blockly": "^6.20210701.0",
     "cronstrue": "^1.100.0",
     "dayjs": "^1.9.6",
     "dom7": "^2.1.5",
     "echarts": "^5.1.2",
     "event-source-polyfill": "^1.0.22",
-    "expression-eval": "^2.1.0",
     "fast-deep-equal": "^3.1.3",
     "framework7": "^5.7.12",
     "framework7-icons": "^3.0.1",
     "framework7-vue": "^5.7.12",
+    "jse-eval": "^1.5.1",
     "jssip": "^3.9.1",
     "leaflet": "^1.7.1",
     "leaflet-providers": "^1.11.0",
diff --git a/bundles/org.openhab.ui/web/src/components/cards/glance/location/status-badge.vue b/bundles/org.openhab.ui/web/src/components/cards/glance/location/status-badge.vue
index 393392308..0933236fe 100644
--- a/bundles/org.openhab.ui/web/src/components/cards/glance/location/status-badge.vue
+++ b/bundles/org.openhab.ui/web/src/components/cards/glance/location/status-badge.vue
@@ -34,7 +34,7 @@
 
 <script>
 import { findEquipment, allEquipmentPoints, findPoints } from '../glance-helpers'
-import expr from 'expression-eval'
+import expr from 'jse-eval'
 
 export default {
   props: ['element', 'type', 'badgeOverrides', 'invertColor', 'store'],
@@ -187,7 +187,7 @@ export default {
     reduce () {
       const ast = this.overrideExpression()
       if (ast) {
-        return this.map.filter((state) => expr.eval(ast, { state: state, Number: Number })).length
+        return this.map.filter((state) => expr.evaluate(ast, { state: state, Number: Number })).length
       }
       switch (this.type) {
         case 'blinds':
diff --git a/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue b/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue
index 331032234..e026461ae 100644
--- a/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue
+++ b/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue
@@ -255,7 +255,8 @@ export default {
           'Ctrl-Space': 'autocomplete',
           '\'.\'': autocomplete,
           '\'=\'': autocomplete,
-          'Space': autocomplete
+          'Space': autocomplete,
+          '\'@\'': autocomplete
         }
         cm.state.$oh = this.$oh
         cm.state.originalMode = this.mode
diff --git a/bundles/org.openhab.ui/web/src/components/config/editor/hint-components.js b/bundles/org.openhab.ui/web/src/components/config/editor/hint-components.js
index d19cbd9ff..e8551cd7f 100644
--- a/bundles/org.openhab.ui/web/src/components/config/editor/hint-components.js
+++ b/bundles/org.openhab.ui/web/src/components/config/editor/hint-components.js
@@ -50,7 +50,7 @@ function getWidgetDefinitions (cm) {
   }
 }
 
-function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix) {
+function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix, addQuotes) {
   const cursor = cm.getCursor()
   const promise = (itemsCache) ? Promise.resolve(itemsCache) : cm.state.$oh.api.get('/rest/items')
   return promise.then((data) => {
@@ -58,7 +58,7 @@ function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix) {
     let ret = {
       list: data.map((item) => {
         return {
-          text: item.name + ((addStatePropertySuffix ? '.state' : '')),
+          text: (addQuotes ? '\'' : '') + item.name + ((addStatePropertySuffix ? '.state' : '')) + (addQuotes ? '\'' : ''),
           displayText: item.name,
           description: `${(item.label) ? item.label + ' ' : ''}(${item.type})<br />${item.state}`
         }
@@ -69,6 +69,9 @@ function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix) {
       const colonPos = line.indexOf(':')
       ret.from = { line: cursor.line, ch: colonPos + 2 }
       ret.to = { line: cursor.line, ch: line.length }
+    } else if (addQuotes) {
+      const lastAtOp = line.substring(0, cursor.ch).replace(/@[A-Za-z0-9_-]*$/, '@')
+      ret.to = { line: cursor.line, ch: lastAtOp.length }
     } else {
       const lastDot = line.substring(0, cursor.ch).replace(/\.[A-Za-z0-9_-]*$/, '.')
       ret.to = { line: cursor.line, ch: lastDot.length }
@@ -110,11 +113,16 @@ function hintExpression (cm, line) {
         { text: 'theme', displayText: 'theme', description: 'The current theme: aurora, ios, or md' },
         { text: 'themeOptions', displayText: 'themeOptions', description: 'Object with current theme options' },
         { text: 'device', displayText: 'device', description: 'Object with information about the current device & browser' },
+        { text: 'user', displayText: 'user', description: 'Access the username and roles of the logged in user' },
         { text: 'screen', displayText: 'screen', description: 'Object with information about the screen and available view area' },
         { text: 'dayjs', displayText: 'dayjs', description: 'Access to the Day.js object for date manipulation & formatting' }
       ]
     }
   } else {
+    const lastAtOp = line.substring(0, cursor.ch).replace(/@[A-Za-z0-9_-]*$/, '@')
+    if (lastAtOp.endsWith('@')) {
+      return hintItems(cm, line, false, false, true)
+    }
     const lastDot = line.substring(0, cursor.ch).replace(/\.[A-Za-z0-9_-]*$/, '.')
     if (lastDot.endsWith('items.')) {
       return hintItems(cm, line, false, true)
diff --git a/bundles/org.openhab.ui/web/src/components/config/editor/hint-utils.js b/bundles/org.openhab.ui/web/src/components/config/editor/hint-utils.js
index 438d87e0d..7707912fc 100644
--- a/bundles/org.openhab.ui/web/src/components/config/editor/hint-utils.js
+++ b/bundles/org.openhab.ui/web/src/components/config/editor/hint-utils.js
@@ -31,7 +31,7 @@ export function remove (node) {
 export function filterPartialCompletions (cm, line, completions, property = 'text') {
   const cursor = cm.getCursor()
   const lineBeforeCursor = line.substring(0, cursor.ch)
-  const completionBeginPos = Math.max(lineBeforeCursor.lastIndexOf(' '), lineBeforeCursor.lastIndexOf('.'))
+  const completionBeginPos = Math.max(lineBeforeCursor.lastIndexOf(' '), lineBeforeCursor.lastIndexOf('.'), lineBeforeCursor.lastIndexOf('@'))
   const partialCompletion = lineBeforeCursor.substring(completionBeginPos + 1)
   return completions.filter((c) => c[property] && c[property].toLowerCase().indexOf(partialCompletion.toLowerCase()) >= 0)
 }
diff --git a/bundles/org.openhab.ui/web/src/components/widgets/generic-widget-component.vue b/bundles/org.openhab.ui/web/src/components/widgets/generic-widget-component.vue
index 53ed4ed4c..89630d63b 100644
--- a/bundles/org.openhab.ui/web/src/components/widgets/generic-widget-component.vue
+++ b/bundles/org.openhab.ui/web/src/components/widgets/generic-widget-component.vue
@@ -13,10 +13,21 @@
   <div v-else-if="componentType && componentType === 'Label' && visible" :class="config.class" :style="config.style">
     {{ config.text }}
   </div>
+  <fragment v-else-if="componentType && componentType === 'Content'">
+    {{ config.text }}
+  </fragment>
   <pre v-else-if="componentType && componentType === 'Error' && visible" class="text-color-red" style="white-space: pre-wrap">{{ config.error }}</pre>
+  <component v-else-if="visible" :is="componentType" v-bind="config">
+    {{ config.content }}
+    <template v-if="context.component.slots && context.component.slots.default">
+      <generic-widget-component :context="childContext(slotComponent)" v-for="(slotComponent, idx) in context.component.slots.default" :key="'default-' + idx" />
+    </template>
+  </component>
 </template>
 
 <script>
+import { Fragment } from 'vue-fragment'
+
 import mixin from './widget-mixin'
 
 import * as SystemWidgets from './system/index'
@@ -28,6 +39,7 @@ import * as LayoutWidgets from './layout/index'
 export default {
   mixins: [mixin],
   components: {
+    Fragment,
     ...SystemWidgets,
     ...StandardWidgets,
     ...StandardListWidgets,
diff --git a/bundles/org.openhab.ui/web/src/components/widgets/widget-mixin.js b/bundles/org.openhab.ui/web/src/components/widgets/widget-mixin.js
index 2f232e16e..fa50ca521 100644
--- a/bundles/org.openhab.ui/web/src/components/widgets/widget-mixin.js
+++ b/bundles/org.openhab.ui/web/src/components/widgets/widget-mixin.js
@@ -1,6 +1,6 @@
 // Import into widget components as a mixin!
 
-import expr from 'expression-eval'
+import expr from 'jse-eval'
 import dayjs from 'dayjs'
 import relativeTime from 'dayjs/plugin/relativeTime'
 import calendar from 'dayjs/plugin/calendar'
@@ -10,6 +10,22 @@ import isToday from 'dayjs/plugin/isToday'
 import isYesterday from 'dayjs/plugin/isYesterday'
 import isTomorrow from 'dayjs/plugin/isTomorrow'
 import scope from 'scope-css'
+import store from '@/js/store'
+
+import jsepRegex from '@jsep-plugin/regex'
+import jsepArrow from '@jsep-plugin/arrow'
+import jsepObject from '@jsep-plugin/object'
+import jsepTemplate from '@jsep-plugin/template'
+expr.jsep.plugins.register(jsepRegex, jsepArrow, jsepObject, jsepTemplate)
+
+expr.addUnaryOp('@', (itemName) => {
+  const itemState = store.getters.trackedItems[itemName]
+  if (itemState.displayState === undefined) return itemState.state
+  return itemState.displayState
+})
+expr.addUnaryOp('@@', (itemName) => {
+  return store.getters.trackedItems[itemName].state
+})
 
 dayjs.extend(relativeTime)
 dayjs.extend(calendar)
@@ -30,7 +46,7 @@ export default {
   },
   computed: {
     componentType () {
-      return this.context.component.component
+      return this.evaluateExpression('type', this.context.component.component)
     },
     childWidgetComponentType () {
       if (!this.componentType.startsWith('widget:')) return null
@@ -112,7 +128,7 @@ export default {
           if (!this.exprAst[key] || ctx.editmode) {
             this.exprAst[key] = expr.parse(value.substring(1))
           }
-          return expr.eval(this.exprAst[key], {
+          return expr.evaluate(this.exprAst[key], {
             items: ctx.store,
             props: this.props,
             vars: ctx.vars,
@@ -124,7 +140,8 @@ export default {
             device: this.$device,
             screen: this.getScreenInfo(),
             JSON: JSON,
-            dayjs: dayjs
+            dayjs: dayjs,
+            user: this.$store.getters.user
           })
         } catch (e) {
           return e