# Dashboard 2.0 is now Generally Available

**URL:** <https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913>\
**Category:** Dashboard\
**Tags:** dashboard-2\
**Created:** [25 January 2024 16:59 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913 "2024-01-25T16:59:41Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![TheSaltydog](https://avatars.discourse-cdn.com/v4/letter/t/47e85d/32.png) [@TheSaltydog](https://discourse.nodered.org/u/TheSaltydog)\
**Post date:** [29 January 2024 17:07 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/41 "2024-01-29T17:07:32Z")

</div>

Any news for a Gauge widget?

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [29 January 2024 19:45 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/42 "2024-01-29T19:45:29Z")

</div>

> [@TheSaltydog](#):
>
> Any news for a Gauge widget

It's currently under development, see [New Widget: UI Gauge (in Draft) by joepavitt · Pull Request #530 · FlowFuse/node-red-dashboard · GitHub](https://github.com/FlowFuse/node-red-dashboard/pull/530)

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [30 January 2024 09:52 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/43 "2024-01-30T09:52:35Z")

</div>

Some building blocks to play with:

Template:

```auto
<template>
    <div class="level">
        <div v-for="(color, index) in colors" :key="index" class="led" :ref="'dot-' + index +'-'+this.id"></div>
    </div>
</template>

<script>
export default {
    data(){
        return {
            //define me here                   
            min:0,
            max:100,
            dark:0.4,            
            colors:["#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",
                    "red",
                    "red",
                    "red"],

            //no need to change those
            value:0,
            inited:false
        }
    },

   
    methods: {        
        getElement: function(name){
            return this.$refs[name][0]
        },

        full: function(){
            return Math.floor(this.colors.length*this.percentage/100)
        },
        half: function (){
            let p = this.colors.length*this.percentage/100;
            p -= this.full()
            p *= .5
            p += this.dark;
            return p;
        },

        lit: function(){
            if(this.inited == false){
                return
            }
            this.colors.forEach((e,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }                
                if(i<this.full()){
                    dot.style.filter= "brightness(1.1)";
                }
                else if(i==this.full()){                   
                    dot.style.filter = "brightness("+this.half()+")";
                }
                else{
                    dot.style.filter= "brightness("+this.dark+")";
                }
            })
        }
    },
    watch: {
        msg: function(){    
            if(this.msg.payload != undefined){           
                this.value = this.msg.payload                
                this.lit()
            }
        }
    },
    computed: {
        formattedValue: function () {
            return this.value.toFixed(2)
        },
        percentage: function(){
            return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);
        }
    },
    mounted(){
        this.colors.forEach((c,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }
                dot.style.backgroundColor = c
            }
        )       
        this.inited = true;
    },
        
}
</script>

```

CSS:

```auto
.level{
    display: flex;
    gap:2px;
}    
.led {
    background: #ffffff;
    width: 100%;
    height: 100%;
    border-radius: 4px;
    box-shadow: inset 0px 0px 20px 0px #00000099, 0px 0px 3px 0px #00000099;
    filter: brightness(0.4);
}

```

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/b/e/be94b1dfee655edc94b72291be066762128a018a.png)

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [30 January 2024 13:55 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/44 "2024-01-30T13:55:44Z")

</div>

Nice work my friend.. I already got it here! =]

---

<div class="post-metadata">

**Author:** ![Cyprien](https://avatars.discourse-cdn.com/v4/letter/c/a5b964/32.png) [@Cyprien](https://discourse.nodered.org/u/Cyprien)\
**Post date:** [31 January 2024 09:29 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/45 "2024-01-31T09:29:04Z")

</div>

I've work a few with dashboard 2 and ui-template. I'm new with Vue.js and vuetify.

So, i've tried to make a flow to start with ui-template in 5 (simple) steps.

Next step for me, trying to make some examples with this tutorial : [Tutorial | Vue.js](https://vuejs.org/tutorial/#step-1)  
and using ui-template in a Vue.js way...

```auto
[
    {
        "id": "359f4155f8c47688",
        "type": "tab",
        "label": "The easy way",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "161e548163a6e3f7",
        "type": "group",
        "z": "359f4155f8c47688",
        "name": "0 - start default template",
        "style": {
            "label": true
        },
        "nodes": [
            "ac6602aedb2bc00e",
            "95a84f14b2851bca",
            "fa75fdf0120a9f8d"
        ],
        "x": 34,
        "y": 19,
        "w": 452,
        "h": 122
    },
    {
        "id": "a124bca636cf7e1d",
        "type": "group",
        "z": "359f4155f8c47688",
        "name": "1 - Most basic Vue template",
        "style": {
            "label": true
        },
        "nodes": [
            "0d26e5311e23b0da"
        ],
        "x": 34,
        "y": 159,
        "w": 181,
        "h": 82
    },
    {
        "id": "847acde69067f419",
        "type": "group",
        "z": "359f4155f8c47688",
        "name": "2 - Adding your first Vuetify component (Text Field)",
        "style": {
            "label": true
        },
        "nodes": [
            "3643280942742de0",
            "73e263314550aaaf"
        ],
        "x": 34,
        "y": 259,
        "w": 322,
        "h": 122
    },
    {
        "id": "ed383e6317517380",
        "type": "group",
        "z": "359f4155f8c47688",
        "name": "3 - Change label value from Node-RED",
        "style": {
            "label": true
        },
        "nodes": [
            "dcdf3507637a958a",
            "aa4058b90cc301f5",
            "8a3d68de5a549211"
        ],
        "x": 34,
        "y": 399,
        "w": 432,
        "h": 122
    },
    {
        "id": "1d47ea8478ef9d1c",
        "type": "group",
        "z": "359f4155f8c47688",
        "name": "4 - Send text to Nodered",
        "style": {
            "label": true
        },
        "nodes": [
            "9db6bd112414a6f7",
            "fa9dc8a93420fa72",
            "fe4ecec7a56b7a3e"
        ],
        "x": 34,
        "y": 539,
        "w": 462,
        "h": 122
    },
    {
        "id": "531f51f1625c11e2",
        "type": "group",
        "z": "359f4155f8c47688",
        "name": "5 - Send value to Nodered",
        "style": {
            "label": true
        },
        "nodes": [
            "418ebd279d8609fe",
            "7a0c7f9c75e885bb",
            "dbfd55ba2afcf6a0"
        ],
        "x": 34,
        "y": 679,
        "w": 462,
        "h": 122
    },
    {
        "id": "ac6602aedb2bc00e",
        "type": "ui-template",
        "z": "359f4155f8c47688",
        "g": "161e548163a6e3f7",
        "group": "ebee2ab416276aee",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n 0 - start default template\n <div>\n <h2>Counter</h2>\n <p>Current Count: {{ count }}</p>\n <p class=\"my-class\">Formatted Count: {{ formattedCount }}</p>\n <v-btn @click=\"increase()\">Increment</v-btn>\n </div>\n</template>\n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n count: 0\n }\n },\n watch: {\n // watch for any changes of \"count\"\n count: function () {\n if (this.count % 5 === 0) {\n this.send({payload: 'Multiple of 5'})\n }\n }\n },\n computed: {\n // automatically compute this variable\n // whenever VueJS deems appropriate\n formattedCount: function () {\n return this.count + 'Apples'\n }\n },\n methods: {\n // expose a method to our <template> and Vue Application\n increase: function () {\n this.count++\n }\n },\n mounted() {\n // code here when the component is first loaded\n },\n unmounted() {\n // code here when the component is removed from the Dashboard\n // i.e. when the user navigates away from the page\n }\n }\n</script>\n<style>\n /* define any styles here - supports raw CSS */\n .my-class {\n color: red;\n }\n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 120,
        "y": 100,
        "wires": [
            [
                "95a84f14b2851bca"
            ]
        ]
    },
    {
        "id": "95a84f14b2851bca",
        "type": "debug",
        "z": "359f4155f8c47688",
        "g": "161e548163a6e3f7",
        "name": "0 - start default template",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 330,
        "y": 100,
        "wires": []
    },
    {
        "id": "fa75fdf0120a9f8d",
        "type": "comment",
        "z": "359f4155f8c47688",
        "g": "161e548163a6e3f7",
        "name": "click me for help !",
        "info": "## Setup\n1 - Edit this template node\n2 - Edit Group\n3 - Edit Page\n4 - Edit UI\n5 - Save UI \n6 - Edit Theme\n7 - Add Theme\n8 - Save all and deploy\n=> You can see widget organisation on the dashboard 2.0 tab\n\n## Running\n1 - Click on Open Dashboard button (Or go to /dashboard url, eg : http://localhost:1880/dashboard/ )\nIn Home assistant Addon, go to http://ip_home_assistant:1880/endpoint/dashboard/\n2 - click 5 time on increment to see the debug is working",
        "x": 140,
        "y": 60,
        "wires": []
    },
    {
        "id": "0d26e5311e23b0da",
        "type": "ui-template",
        "z": "359f4155f8c47688",
        "g": "a124bca636cf7e1d",
        "group": "ebee2ab416276aee",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n 1 - Most basic Vue template <br>\n \n This is the most basic Vue template !\n</template>\n\n<script>\n export default {\n }\n</script>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 120,
        "y": 200,
        "wires": [
            []
        ]
    },
    {
        "id": "3643280942742de0",
        "type": "ui-template",
        "z": "359f4155f8c47688",
        "g": "847acde69067f419",
        "group": "ebee2ab416276aee",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n 2 - Adding your first Vuetify component (Text Field)\n <v-text-field label=\"Label\"></v-text-field>\n</template>\n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n }\n }\n }\n</script>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 120,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "73e263314550aaaf",
        "type": "comment",
        "z": "359f4155f8c47688",
        "g": "847acde69067f419",
        "name": "click me for help !",
        "info": "Dashboard 2.0 is using Vue.js (https://vuejs.org/) \nand Vuetify (https://vuetifyjs.com) is working out of the box\n\nIt's easy to add any Vuetify component (https://vuetifyjs.com/en/components/all/#containment)\nLet's start with a Label !",
        "x": 140,
        "y": 300,
        "wires": []
    },
    {
        "id": "dcdf3507637a958a",
        "type": "ui-template",
        "z": "359f4155f8c47688",
        "g": "ed383e6317517380",
        "group": "ebee2ab416276aee",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n 3 - Change label value from Node-RED <br>\n Get msg.payload value from Nodered\n <v-text-field label=\"\">{{this.msg.payload}}</v-text-field>\n</template>\n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n }\n }\n }\n</script>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 380,
        "y": 480,
        "wires": [
            []
        ]
    },
    {
        "id": "aa4058b90cc301f5",
        "type": "comment",
        "z": "359f4155f8c47688",
        "g": "ed383e6317517380",
        "name": "click me for help !",
        "info": "From ui-template, you have access to buil-in variables.\nthis.msg - The last message received by the ui-template node.\n(see : https://dashboard.flowfuse.com/nodes/widgets/ui-template.html#built-in-variables)\n\nLet's get the payload from this inject node !",
        "x": 140,
        "y": 440,
        "wires": []
    },
    {
        "id": "8a3d68de5a549211",
        "type": "inject",
        "z": "359f4155f8c47688",
        "g": "ed383e6317517380",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "Hello from Node-RED !",
        "payloadType": "str",
        "x": 180,
        "y": 480,
        "wires": [
            [
                "dcdf3507637a958a"
            ]
        ]
    },
    {
        "id": "9db6bd112414a6f7",
        "type": "ui-template",
        "z": "359f4155f8c47688",
        "g": "1d47ea8478ef9d1c",
        "group": "ebee2ab416276aee",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n <div>\n <v-text-field \n label=\"4 - Send text to Nodered\"\n @input=\"send({payload: 'text changed'})\"\n ></v-text-field>\n </div>\n</template> \n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n }\n }\n }\n</script>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 120,
        "y": 620,
        "wires": [
            [
                "fe4ecec7a56b7a3e"
            ]
        ]
    },
    {
        "id": "fa9dc8a93420fa72",
        "type": "comment",
        "z": "359f4155f8c47688",
        "g": "1d47ea8478ef9d1c",
        "name": "click me for help !",
        "info": "https://dashboard.flowfuse.com/nodes/widgets/ui-template.html#built-in-functions\n\nfunction this.send - Send a message to the Node-RED flow.\n\nLet's send a message to nodered from ui-template\n",
        "x": 140,
        "y": 580,
        "wires": []
    },
    {
        "id": "fe4ecec7a56b7a3e",
        "type": "debug",
        "z": "359f4155f8c47688",
        "g": "1d47ea8478ef9d1c",
        "name": "4 - Send text to Nodered",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 340,
        "y": 620,
        "wires": []
    },
    {
        "id": "418ebd279d8609fe",
        "type": "ui-template",
        "z": "359f4155f8c47688",
        "g": "531f51f1625c11e2",
        "group": "ebee2ab416276aee",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n <div>\n <v-text-field \n label=\"5 - Send value to Nodered\"\n @input=\"send({payload: $event.target.value})\"\n ></v-text-field>\n </div>\n</template> \n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n }\n }\n }\n</script>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 120,
        "y": 760,
        "wires": [
            [
                "dbfd55ba2afcf6a0"
            ]
        ]
    },
    {
        "id": "7a0c7f9c75e885bb",
        "type": "comment",
        "z": "359f4155f8c47688",
        "g": "531f51f1625c11e2",
        "name": "click me for help !",
        "info": "You can get input value with $event.target.value\nBut now, it's time to discover Vue.js !",
        "x": 140,
        "y": 720,
        "wires": []
    },
    {
        "id": "dbfd55ba2afcf6a0",
        "type": "debug",
        "z": "359f4155f8c47688",
        "g": "531f51f1625c11e2",
        "name": "5 - Send value to Nodered",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 340,
        "y": 760,
        "wires": []
    },
    {
        "id": "ebee2ab416276aee",
        "type": "ui-group",
        "name": "My Group ",
        "page": "027e47745a6c6bee",
        "width": "12",
        "height": "1",
        "order": -1,
        "showTitle": true,
        "className": ""
    },
    {
        "id": "027e47745a6c6bee",
        "type": "ui-page",
        "name": "Page test",
        "ui": "5818fe6ad310e494",
        "path": "/",
        "icon": "home",
        "layout": "grid",
        "theme": "3bedd6defc6b1907",
        "order": -1,
        "className": ""
    },
    {
        "id": "5818fe6ad310e494",
        "type": "ui-base",
        "name": "UI Name",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ]
    },
    {
        "id": "3bedd6defc6b1907",
        "type": "ui-theme",
        "name": "Theme Name",
        "colors": {
            "surface": "#ffffff",
            "primary": "#0094ce",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        }
    }
]

```

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [31 January 2024 17:14 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/46 "2024-01-31T17:14:23Z")

</div>

How to start measuring from the bottom up? @hotNipi

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/7/17e65cc5956f8a0aca9fc0f3d70bb5313f0a0d72.png)

```auto
<template>    
    <v-card style="display: flex; flex-direction: column; margin: auto; width: 75px; height: 150px; background-color: #4F4F4F; border: 1px solid #000000; border-radius: 18px;">  
        
        
               
        <div v-for="(color, index) in colors" :key="index" class="led" :ref="'dot-' + index +'-'+this.id"></div>      
        <span style="color: black; margin: auto; font-size: 11px;"> Valor {{this.value}}%</span>
        
    </v-card>
</template>

<script>
    export default {
    data(){
        return {
            //define me here                   
            min:0,
            max:100,
            dark:0.4,            
            colors:["red",
                    "red",
                    "red",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",                    
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e"
                    ],

            //no need to change those
            value:0,
            inited:false
        }
    },

   
    methods: {        
        getElement: function(name){
            return this.$refs[name][0]
        },

        full: function(){
            return Math.floor(this.colors.length*this.percentage/100)
        },
        half: function (){
            let p = this.colors.length*this.percentage/100;
            p -= this.full()
            p *= .5
            p += this.dark;
            return p;
        },

        lit: function(){
            if(this.inited == false){
                return
            }
            this.colors.forEach((e,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }                
                if(i<this.full()){
                    dot.style.filter= "brightness(1.1)";
                }
                else if(i==this.full()){                   
                    dot.style.filter = "brightness("+this.half()+")";
                }
                else{
                    dot.style.filter= "brightness("+this.dark+")";
                }
            })
        }
    },
    watch: {
        msg: function(){    
            if(this.msg.payload != undefined){           
                this.value = this.msg.payload                
                this.lit()
            }
        }
    },
    computed: {
        formattedValue: function () {
            return this.value.toFixed(2)
        },
        percentage: function(){
            return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);
        }
    },
    mounted(){
        this.colors.forEach((c,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }
                dot.style.backgroundColor = c
            }
        )       
        this.inited = true;
    },
        
}
</script>

<style>
    .level{
        display: flex;
        gap:2px;
        flex-direction: row;
        margin: auto;
    }
    .led {
        background: #ffffff;
        width: 100%;
        height: 100%;
        border-radius: 4px;
        box-shadow: inset 0px 0px 20px 0px #00000099, 0px 0px 3px 0px #00000099;
        filter: brightness(0.4);
    }
</style>

```

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [31 January 2024 17:38 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/47 "2024-01-31T17:38:24Z")

</div>

For CSS add those:

```auto
.vertical{
        flex-direction:column-reverse;
    }
.vertical .led{
    box-shadow: inset 0px 0px 8px 0px #00000099, 0px 0px 3px 0px #00000099;
}

```

Template:

```auto
<template>
    <div class="level" :class="{ vertical: isVertical}">
        <div v-for="(color, index) in colors" :key="index" class="led" :ref="'dot-' + index +'-'+this.id"></div>
    </div>
</template>

```

And for data definition add `isVertical` (true for vertical, false for horisontal)

```auto
 data(){
        return {
            //define me here                   
            min:0,
            max:100,
            dark:0.4,
            isVertical:true,
// continue with colors and other stuff

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [31 January 2024 19:34 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/48 "2024-01-31T19:34:43Z")

</div>

Love it when you guys share some nice UI. Spurs me on to create a pure HTML/CSS/JS version. 🙂

So I'm trying to work on a web component version - something I meant to find time to do a long time back. Anyway, getting there slowly. Need to make the sizing a bit more robust & add in the colouring.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/e/d/edc86621792da4df39020cec2c09457a256e78f7.png)

```html
<simple-gauge 
    min="0" max="5" value="3.5" 
    mode="v" 
    style="width:50%;"
>
    Caption 1
</simple-gauge>

<p></p>

<simple-gauge min="0" max="5" value="3.5" mode="h" style="width:50%">
    Caption 2
</simple-gauge>   

```

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [31 January 2024 19:42 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/49 "2024-01-31T19:42:22Z")

</div>

> [@TotallyInformation](#):
>
> So I'm trying to work on a web component version

So is this something that would render in Dashboard 2, or are you talking about something for UIbuilder?

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [31 January 2024 19:55 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/50 "2024-01-31T19:55:39Z")

</div>

I used another approach, but I'm learning a lot from the elements you're posting. I'm modifying it and applying it to the panels of the projects I create. I would like to ask you for permission to provide these components on my YouTube channel to promote dashborad 2.0 and encourage the use of node-red in Brazil. Everything I create I will post here and we will help each other. Below is the element I derived for using water and gas box measurements in the automations we carry out.

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/8/7/872c2e4e201073ac30718c2123c3296a0477bfb0.png)

```auto
<template>     
    <v-card class="elemento">         
        <div class="titulo1">{{this.value}}%</div>
        <div class="led" v-for="(color, index) in colors" :key="index" :ref="'dot-' + index +'-'+this.id"></div>
        <div class="titulo2">Valor</div>
    </v-card>
</template>

<script>
    export default {
    data(){
        return {                   
            min:0,
            max:100,
            dark:0.4,            
            colors:[
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00", 
                    "red",
                    "red",
                    "red"                
                ],            
            value:0,            
            inited:false
        }
    },   
    methods: {        
        getElement: function(name){
            return this.$refs[name][0]
        },

        full: function(){
            return Math.floor(this.colors.length*this.percentage/100)
        },
        half: function (){
            let p = this.colors.length*this.percentage/100;
            p -= this.full()
            p *= .5
            p += this.dark;
            return p;
        },

        lit: function(){
            if(this.inited == false){
                return
            }
            this.colors.forEach((e,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }                
                if(i<this.full()){
                    dot.style.filter= "brightness(1.1)";
                }
                else if(i==this.full()){                   
                    dot.style.filter = "brightness("+this.half()+")";
                }
                else{
                    dot.style.filter= "brightness("+this.dark+")";
                }
            })
        }
    },
    watch: {
        msg: function(){    
            if(this.msg.payload != undefined){           
                this.value = this.msg.payload                
                this.lit()
            }
        }
    },
    computed: {
        formattedValue: function () {
            return this.value.toFixed(2)
        },
        percentage: function(){
            return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);
        }
    },
    mounted(){
        this.colors.forEach((c,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }
                dot.style.backgroundColor = c
            }
        )       
        this.inited = true;
    },        
}
</script>

<style>
    .elemento {
        display: flex; 
        flex-direction: column-reverse; 
        margin: auto; 
        width: 75px !important; 
        height: 150px !important;        
        border: 1px solid #000000 !important; 
        border-radius: 18px;
    }
    .titulo1 {
        color: #4F4F4F; 
        margin: auto; 
        font-size: 14px;
    }
    .titulo2 {
        color: #4F4F4F; 
        margin: auto; 
        font-size: 14px;
    }
    .led {
        background: #ffffff; 
        width: 100%; 
        height: 100%; 
        box-shadow: inset 0px 0px 20px 0px #00000099, 0px 0px 3px 0px #00000099; 
        filter: brightness(0.4);
    }
</style>

```

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [31 January 2024 19:59 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/51 "2024-01-31T19:59:39Z")

</div>

I'm having trouble knowing how to align the elements in the groups. In d1 it was simple, but in d2 I still haven't learned how to do it.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/3/7/371fd5111a1a5de1b08692f160e74b6406d8cd9b.jpeg)

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [31 January 2024 20:13 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/52 "2024-01-31T20:13:26Z")

</div>

> [@Paul-Reed](#):
>
> So is this something that would render in Dashboard 2, or are you talking about something for UIbuilder?

THe web components I build are mostly not tied to UIBUILDER but designed to work in any environment. Even outside Node-RED! If you can imagine such a thing!

So this should indeed render within D1, D2, http-in/-out or UIBUILDER. That is one of the purposes of me recommending that approach. I wouldn't have commented here otherwise.

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [31 January 2024 20:18 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/53 "2024-01-31T20:18:13Z")

</div>

> [@TotallyInformation](#):
>
> I wouldn't have commented here otherwise

I should think not 😆

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [31 January 2024 20:58 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/54 "2024-01-31T20:58:43Z")

</div>

> [@diegodamaceno](#):
>
> I would like to ask you for permission to provide these components on my YouTube channel

Granted. Just don't forget to mention how those components involved and why not some thank you for community and people who are closely behind it.

Maybe it's only me but if you are doing such work you later going to share - please do use English in your code. (variable names, function names...) It is not easy to read...

> [@TotallyInformation](#):
>
> So I'm trying to work on a web component version - something I meant to find time to do a long time back. Anyway, getting there slowly.

None of my skills is hidden for you to gain in tempo or fight with obstacles. Just drop me a letter. I'm just not on position to create and maintain ready made components by myself.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [31 January 2024 21:10 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/55 "2024-01-31T21:10:05Z")

</div>

> [@diegodamaceno](#):
>
> I'm having trouble knowing how to align the elements in the groups.

`<v-card style="display: flex; flex-direction: column; margin: auto; width: 75px; height: 150px;`

If you create elements with hardcoded dimensions, you most probably need to adjust containers where those elements will be situated. That means - you'll need to override most of the dashboard styles. Are you sure you want to do it?

There's no right or wrong way doing it. But most simple will be not to fight with dashboard but learn to live in it. As all your created elements will be placed into same sort of container, accept it's behaviors and allow dynamic sizes in your widgets. Dashboard does the overall layouting for you.

If you want to have really custom layout, use UIBUILDER or create your own dashboard solution from scratch.

That simple it is.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [31 January 2024 22:28 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/56 "2024-01-31T22:28:34Z")

</div>

> [@hotNipi](#):
>
> > [@TotallyInformation](#):
> >
> > So I'm trying to work on a web component version - something I meant to find time to do a long time back. Anyway, getting there slowly.
> 
> None of my skills is hidden for you to gain in tempo or fight with obstacles. Just drop me a letter. I'm just not on position to create and maintain ready made components by myself.

🙂 Thanks - your work is always an ongoing inspiration.

The process is much of the fun though. So learning goes on. But I'll reach out if I get stuck - thanks.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [31 January 2024 23:56 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/57 "2024-01-31T23:56:32Z")

</div>

As we do currently waiting for gauge widget, the one can at least use this _poor man version of ui-level replica_.

```auto
[{"id":"cf79351b4995d067","type":"ui-slider","z":"4eb808f5e19fb7e2","group":"5d78082af21e201d","name":"","label":"slider","tooltip":"","order":3,"width":0,"height":0,"passthru":false,"outs":"all","topic":"topic","topicType":"msg","thumbLabel":true,"min":0,"max":"100","step":1,"className":"","x":330,"y":280,"wires":[["7b950200a285852f","b0e36cfaef476d35"]]},{"id":"b0e36cfaef476d35","type":"ui-template","z":"4eb808f5e19fb7e2","group":"5d78082af21e201d","page":"","ui":"","name":"Linear Led Gauge","order":0,"width":"6","height":"1","head":"","format":"<template>\n <div class=\"led-level\">\n <div class=\"led-level-text\">\n <span class=\"led-level-label\">{{label}}</span>\n <span class=\"led-level-value\">{{formattedValue}}<span class=\"led-level-unit\">{{unit}}</span></span>\n </div>\n <div class=\"led-level-stripe\" :class=\"{ vertical: isVertical}\">\n <div v-for=\"(color, index) in colors\" :key=\"index\" class=\"led-level-led\" :ref=\"'dot-' + index +'-'+this.id\"></div>\n </div>\n <div class=\"led-level-limits\">\n <span>{{min}}</span>\n <span>{{max}}</span>\n </div>\n <div>\n</template>\n\n<script>\nexport default {\n data(){\n return {\n //define me here \n min:0,\n label:\"MEASURE\",\n unit:\"cm³\",\n max:100,\n dark:0.4,\n isVertical:false,\n animate:true, \n colors:[\"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#4ed34e\",\n \"#ffcf00\",\n \"#ffcf00\",\n \"#ffcf00\",\n \"#ffcf00\",\n \"red\",\n \"red\",\n \"red\"],\n\n //no need to change those\n value:0,\n previousValue:0,\n inited:false\n }\n },\n\n \n methods: { \n getElement: function(name){\n return this.$refs[name][0]\n },\n\n full: function(){\n return Math.floor(this.colors.length*this.percentage/100)\n },\n half: function (){\n let p = this.colors.length*this.percentage/100;\n p -= this.full()\n p *= .5\n p += this.dark;\n return p;\n },\n\n lit: function(){\n if(this.inited == false){\n return\n }\n const down = this.previousValue > this.value\n\n let time = .01\n this.colors.forEach((e,i) => {\n let dot = this.getElement(\"dot-\"+i+\"-\"+this.id);\n if(!dot){\n console.log(\"no dots found\")\n return\n } \n if(i<this.full()){\n dot.style.filter= \"brightness(1.1)\";\n }\n else if(i==this.full()){ \n dot.style.filter = \"brightness(\"+this.half()+\")\";\n }\n else{\n dot.style.filter= \"brightness(\"+this.dark+\")\";\n }\n if(down){\n time = (this.colors.length - i) * .12 \n }\n dot.style.transition = this.animate ? \"filter \"+time+\"s\" : \"unset\";\n })\n this.previousValue = this.value\n }\n },\n watch: {\n msg: function(){ \n if(this.msg.payload != undefined){ \n this.value = this.msg.payload \n this.lit()\n }\n }\n },\n computed: {\n formattedValue: function () {\n return this.value.toFixed(2)\n },\n percentage: function(){\n return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);\n }\n },\n mounted(){\n this.colors.forEach((c,i) => {\n let dot = this.getElement(\"dot-\"+i+\"-\"+this.id);\n if(!dot){\n console.log(\"no dots found\")\n return\n }\n dot.style.backgroundColor = c \n dot.style.transition = \"filter 0.1s\";\n }\n ) \n this.inited = true;\n },\n \n}\n</script>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":630,"y":280,"wires":[[]]},{"id":"de08b76d5e936b8a","type":"ui-template","z":"4eb808f5e19fb7e2","group":"","page":"","ui":"da26eafa8eb48ab3","name":"Linear Led Gauge for Dasboard 2.0 CSS","order":0,"width":0,"height":0,"head":"","format":".led-level{\n display: grid;\n grid-template-rows: 1.3em 1fr .7em;\n gap: 2px;\n}\n.led-level-stripe{\n display: flex;\n gap:2px;\n} \n.led-level-led {\n background: #ffffff;\n width: 100%;\n height: 100%;\n border-radius: 4px;\n box-shadow: inset 0px 0px 10px 0px #00000099, 0px 0px 3px 0px #00000099;\n filter: brightness(0.4);\n}\n.led-level-text{\n font-size: 1.25em;\n line-height: 1em;\n align-self: end;\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n user-select: none;\n}\n.led-level-value{ \n font-weight:bold;\n}\n.led-level-unit{\n font-size:.75em;\n font-weight:normal;\n padding-inline-start: 0.15em;\n}\n.led-level-limits{\n display: flex;\n justify-content: space-between;\n font-size: .75em;\n line-height: .75em;\n align-content: flex-end;\n flex-wrap: wrap;\n user-select: none;\n}","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"site:style","className":"","x":700,"y":240,"wires":[[]]},{"id":"5d78082af21e201d","type":"ui-group","name":"Some stuff","page":"71097e9858ab99f9","width":"6","height":"1","order":3,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"da26eafa8eb48ab3","type":"ui-base","name":"Board","path":"/dashboard"},{"id":"71097e9858ab99f9","type":"ui-page","name":"Test page","ui":"da26eafa8eb48ab3","path":"/second","icon":"home","layout":"grid","theme":"a965ccfef139317a","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"a965ccfef139317a","type":"ui-theme","name":"Default","colors":{"surface":"#5c5c5c","primary":"#0094ce","bgPage":"#383838","groupBg":"#4f4f4f","groupOutline":"#858585"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

```

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/b/c/bc04f9376f38c53d52548031e8d3d6bf70872597.png)

---

<div class="post-metadata">

**Author:** ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)\
**Post date:** [1 February 2024 00:44 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/58 "2024-02-01T00:44:59Z")

</div>

In the [docs for loading external dependencies in the ui-template](https://dashboard.flowfuse.com/nodes/widgets/ui-template.html#loading-external-dependencies), it shows a solution of using an interval to check for some value defined since it can't be sure that the 3rd party lib has loaded yet. The alternative solution that i have found is using [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import#import_a_module_for_its_side_effects_only) and it seems reliable and supported by all browsers that i have tried so far.

```auto
<template>
    <my-custom-element v-bind:src="msg.src" ref="mce">loading...</my-custom-element>
</template>
<script>
    (async () => {

        await import('https://cdn.jsdelivr.net/npm/hls.js@1');

        await import('/custom-elements/my-custom-element.js');

        this.send(`my-custom-element ${this.id} has loaded`);

        console.log('last received msg', this.msg);

        const eventName = `msg-input:${this.id}`;

        this.$socket.on(eventName, (msg) => {
            console.log(eventName, msg);
        });

        // here, mce is direct reference to the element in the template
        const { mce } = this.$refs; 

        mce.doSomething(); // or whatever

    })();
</script>

```

This always ensured that the hls.js lib was available for my custom element and also that my custom element was defined before i started interacting with it. Also, the browser only makes a network call once per import and any further elements calling import will get a cached value to use right away.

@TotallyInformation you reminded me when you mentioned the custom element you are working on being compatible with all dashboards, which was also my goal.

Warning, if you are going to dynamic import your custom element class, put a guard clause around it so you don't get the error of the class already being defined (probably only would happen when trying to load it in the ui-template where we have less control of using 3rd party libs).

```auto
if (!customElements.get('my-custom-element')) {

    class MyCustomElement extends HTMLElement {
       //...
    }

    customElements.define('my-custom-element', MyCustomElement);

}

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [1 February 2024 00:55 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/59 "2024-02-01T00:55:45Z")

</div>

> [@kevinGodell](#):
>
> it seems reliable and supported by all browsers that i have tried so far.

> **[JavaScript modules: dynamic import() | Can I use... Support tables for HTML5,...](https://caniuse.com/es6-module-dynamic-import)**
>
> "Can I use" provides up-to-date browser support tables for support of front-end web technologies on desktop and mobile web browsers.

Should be good on all major browsers from about 2018. Not IE of course. 🙂

> [@kevinGodell](#):
>
> @TotallyInformation you reminded me when you mentioned the custom element you are working on being compatible with all dashboards, which was also my goal.

😀

> [@kevinGodell](#):
>
> put a guard clause around it

Yes, could easily happen accidentally with a 2nd ui\_template.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [1 February 2024 14:22 UTC](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913/60 "2024-02-01T14:22:40Z")

</div>

OK. Level is not gauge. Maybe if to bend it hard and...

```auto
[{"id":"9f3c815ec5f4ba93","type":"ui-template","z":"4eb808f5e19fb7e2","group":"5d78082af21e201d","page":"","ui":"","name":"Round Led Gauge","order":7,"width":"2","height":"2","head":"","format":"<template>\n <div class=\"round-led-level\" :style=\"`--dot-count:${count}; --size:${size}; --ledsize:${ledSize};`\" >\n <header>\n <div class=\"round-led-level-text\">\n <span class=\"round-led-level-label\">{{label}}</span>\n </div>\n </header>\n <div>\n <div class=\"round-led-level-stripe\">\n <div v-for=\"(color, index) in colors\" :key=\"index\" class=\"round-led-level-led\" :ref=\"'dot-' + index +'-'+this.id\">\n </div>\n </div>\n <div class=\"round-led-level-centered-text\">\n <span class=\"round-led-level-value\">{{formattedValue}}</span>\n <span class=\"round-led-level-unit\">{{unit}}</span>\n </div>\n <div class=\"round-led-level-limits\">\n <span>{{min}}</span>\n <span>{{max}}</span>\n </div>\n </div> \n <div>\n</template>\n\n<script>\nexport default {\n data(){\n return {\n //define me here\n label:\"MEASURE\", // the label\n min:0, //smallest expected value\n max:100, //higest expected value\n unit:\"cm³\",// unit of the measurement\n size:140, // ovaerall size. (think in pixels) under 100 will be too small. \n animate:true,// animating leds is not most performant thing in the world. \n dark:0.4, // how dim is led when not glowing \n //define colors as you wish but the thing works clockwise.\n //led size depends on how many colors is defined. \n colors:[\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-primary))\",\n \"rgb(var(--v-theme-warning))\",\n \"rgb(var(--v-theme-warning))\",\n \"rgb(var(--v-theme-warning))\",\n \"rgb(var(--v-theme-error))\",\n \"rgb(var(--v-theme-error))\",\n], \n //no need to change those\n value:0,\n previousValue:0,\n inited:false\n }\n },\n\n \n methods: { \n getElement: function(name,base){\n if(base){\n return this.$refs[name]\n }\n return this.$refs[name][0]\n },\n\n full: function(){\n return Math.floor(this.colors.length*this.percentage/100)\n },\n half: function (){\n let p = this.colors.length*this.percentage/100;\n p -= this.full()\n p *= .5\n p += this.dark;\n return p;\n },\n\n lit: function(){\n if(this.inited == false){\n return\n }\n const down = this.previousValue > this.value\n\n let time = .01\n this.colors.forEach((e,i) => {\n let dot = this.getElement(\"dot-\"+i+\"-\"+this.id);\n if(!dot){\n console.log(\"no dots found\")\n return\n } \n if(i<this.full()){\n dot.style.filter= \"brightness(1.1)\";\n }\n else if(i==this.full()){ \n dot.style.filter = \"brightness(\"+this.half()+\")\";\n }\n else{\n dot.style.filter= \"brightness(\"+this.dark+\")\";\n }\n if(down){\n time = (this.colors.length - i) * .12 \n }\n dot.style.transition = this.animate ? \"filter \"+time+\"s\" : \"unset\";\n })\n this.previousValue = this.value\n }\n },\n watch: {\n msg: function(){ \n if(this.msg.payload != undefined){ \n this.value = this.msg.payload \n this.lit()\n }\n }\n },\n computed: {\n formattedValue: function () {\n return this.value.toFixed(2)\n },\n percentage: function(){\n return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);\n },\n count: function() {\n return this.colors.length;\n }, \n ledSize:function(){\n const s = 4.71239 * ((this.size - (this.size*0.3))/2) \n return s / this.colors.length\n }\n },\n mounted(){\n let angle;\n const step = 270 / this.colors.length; //this.ledSize/2;//270 / (this.colors.length + 2);\n const radius = (this.size - (this.size*0.1))/2//this.size / 2;\n const s = this.ledSize / -2; \n this.colors.forEach((c,i) => {\n let dot = this.getElement(\"dot-\"+i+\"-\"+this.id);\n if(!dot){\n console.log(\"no dots found\")\n return\n }\n dot.style.backgroundColor = c \n dot.style.transition = \"filter 0.1s\";\n dot.style.setProperty('--dot',i);\n angle = ((i+1)*step) * Math.PI / 180;\n dot.style.left = s + radius * Math.cos(angle) + 'px';\n dot.style.top = s + radius * Math.sin(angle) + 'px';\n dot.style.transform = 'translate('+s+'px, '+s+'px)'; \n dot.style.rotate = (angle - 0.08)+\"rad\" \n }\n )\n \n this.inited = true;\n },\n \n}\n</script>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":630,"y":360,"wires":[[]]},{"id":"2545d92a227a8572","type":"ui-template","z":"4eb808f5e19fb7e2","group":"","page":"","ui":"da26eafa8eb48ab3","name":"Round Led Gauge for Dasboard 2.0 CSS","order":0,"width":0,"height":0,"head":"","format":".round-led-level{\n display: grid;\n grid-template-rows: 1em 1fr;\n height: calc(var(--size) * 1px);\n aspect-ratio: 1/1;\n position: relative;\n margin: auto;\n}\n.round-led-level>div{\n position: relative;;\n}\n.round-led-level-stripe{\n display: block;\n position: absolute;\n left: 50%;\n top: 56%;\n rotate: 135deg; \n} \n.round-led-level-led {\n background: #ffffff;\n position: absolute;\n width: calc(var(--ledsize) * 1px);\n aspect-ratio: 1/1;\n border-radius: 4px;\n box-shadow: inset 0px 0px calc(var(--ledsize) / 3 * 1px) 0px #00000099, 0px 0px calc(var(--ledsize) / 7 * 1px) 0px #00000099;\n filter: brightness(0.4);\n transform-origin: center center;\n \n\n}\n.round-led-level-text{ \n font-size: 1rem;\n line-height: 1rem;\n text-align: center;\n user-select: none;\n}\n.round-led-level-centered-text{\n position: absolute;\n inset: 0;\n font-size: 1rem;\n line-height: 1;\n display: grid;\n text-align: center;\n grid-template-rows: 1fr 1fr;\n gap: .25em;\n user-select: none;\n padding-block: 42%;\n align-items: center;\n}\n.round-led-level-value{ \n font-weight:bold;\n font-size: calc(var(--size) * .15 * 1px);\n}\n.round-led-level-unit{\n font-size:calc(var(--size) * .1 * 1px);\n font-weight:normal;\n padding-inline-start: 0.15em;\n}\n.round-led-level-limits{\n position: absolute;\n inset:0; \n display: flex;\n justify-content: space-between;\n font-size: calc(var(--size) * .06 * 1px);\n line-height: calc(var(--size) * .06 * 1px);\n align-content: flex-end;\n flex-wrap: wrap;\n padding-inline:1em;\n user-select: none;\n}","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"site:style","className":"","x":700,"y":320,"wires":[[]]},{"id":"5d78082af21e201d","type":"ui-group","name":"Some stuff","page":"71097e9858ab99f9","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"da26eafa8eb48ab3","type":"ui-base","name":"Board","path":"/dashboard"},{"id":"71097e9858ab99f9","type":"ui-page","name":"Test page","ui":"da26eafa8eb48ab3","path":"/second","icon":"home","layout":"grid","theme":"a965ccfef139317a","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"a965ccfef139317a","type":"ui-theme","name":"Default","colors":{"surface":"#575757","primary":"#0fb8ad","bgPage":"#474747","groupBg":"#525252","groupOutline":"#7c837e"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

```

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/f/f/ff59740986236f7d18fe2d731dc62af6482c9d5f.png)

[Previous page](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913.md?page=2)

[Next page](https://discourse.nodered.org/t/dashboard-2-0-is-now-generally-available/84913.md?page=4)
