Dual Display LED Indicator

Hi Guys!!

In my home auto system, on another package, I was able to create a "Dual" LED by overlaying one LED over another... This package is on my short list to be culled soon in favour of Node Red Dashboard.

I would dearly like to do a similar "LED" on the NR side of things, for example:
screenshot- The blue background indicates the device is under "Auto Control" ... The Green centre indicates that it is currently in an "on" state....

Any pointers as to how I could go about this? (bear in mind, I am a professional eejut where it comes to things even slightly beyond the average!!)


Have you looked at node-red-contrib-ui-led?

Try something like this in a template node (Dashboard) set to Added to site <head> section

    .led {
        background-color: rgba(255, 255, 255, 0.25);
        min-height: 14px;
        min-width: 14px;
        height: 14px;
        width: 14px;
        max-height: 14px;
        max-width: 14px;
        text-align: center;
        margin: 17px 11px 11px;
        border-radius: 50%;

    .led-red {
        background-color: green;                   // use for red LED  -  #ff0000;
        box-shadow: #0000009e 0 0px 3px 0px, #ff0000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;



Hi Paul,

I am using that LED already, the problem for me is overlaying it with a second,smaller diameter, indicator colour ....



Hi Jeff,

That is pretty much, EXACTLY what I am hoping to achieve!!

But, unfortunately, being a brain cripple in the area of template node authoring, it is waaaay above my fireplace!!

I will try and give it a go though!!

Thanks sooo much for the pointer!!



Not so elegant, but i do something like this to achieve what you want (almost)

[{"id":"64c5b7ac1226eb4b","type":"change","z":"c2f9171804efc2f3","name":"MANUAL","rules":[{"t":"set","p":"bc","pt":"msg","to":"lightblue","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":920,"wires":[["e6a8cf972b47c972"]]},{"id":"df45e243dd46cd00","type":"switch","z":"c2f9171804efc2f3","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":530,"y":900,"wires":[["d145ba7674727f1c"],["64c5b7ac1226eb4b"]]},{"id":"e6a8cf972b47c972","type":"ui_button","z":"c2f9171804efc2f3","name":"STATUS","group":"cda860da59b77b03","order":3,"width":"5","height":"2","passthru":false,"label":"<font size=8>{{label}}","tooltip":"","color":"{{fc}}","bgcolor":"{{bc}}","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":900,"y":940,"wires":[[]]},{"id":"ecc84a5c5017eae3","type":"ui_switch","z":"c2f9171804efc2f3","name":"","label":"AUTO/MANUAL","tooltip":"","group":"cda860da59b77b03","order":1,"width":"6","height":"2","passthru":true,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"className":"","x":360,"y":900,"wires":[["df45e243dd46cd00"]]},{"id":"d145ba7674727f1c","type":"change","z":"c2f9171804efc2f3","name":"AUTO","rules":[{"t":"set","p":"bc","pt":"msg","to":"green","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":880,"wires":[["e6a8cf972b47c972"]]},{"id":"f1a5f144d8824ae9","type":"change","z":"c2f9171804efc2f3","name":"ON","rules":[{"t":"set","p":"label","pt":"msg","to":"ON","tot":"str"},{"t":"set","p":"fc","pt":"msg","to":"white","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":980,"wires":[["e6a8cf972b47c972"]]},{"id":"85685df3070e73ae","type":"change","z":"c2f9171804efc2f3","name":"OFF","rules":[{"t":"set","p":"label","pt":"msg","to":"OFF","tot":"str"},{"t":"set","p":"fc","pt":"msg","to":"red","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":1020,"wires":[["e6a8cf972b47c972"]]},{"id":"9b48d6c1db3f99e3","type":"switch","z":"c2f9171804efc2f3","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":530,"y":980,"wires":[["f1a5f144d8824ae9"],["85685df3070e73ae"]]},{"id":"8e981e3f33059c56","type":"ui_switch","z":"c2f9171804efc2f3","name":"","label":"ON/OFF","tooltip":"","group":"cda860da59b77b03","order":2,"width":"6","height":"2","passthru":true,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"className":"","x":380,"y":980,"wires":[["9b48d6c1db3f99e3"]]},{"id":"cda860da59b77b03","type":"ui_group","name":"DISPLAY","tab":"62d102ab34e6a898","order":5,"disp":false,"width":"6","collapse":false,"className":""},{"id":"62d102ab34e6a898","type":"ui_tab","name":"HOME","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

I wanted to tackle this challenge by passing a custom CSS class to the node-red-contrib-ui-led node.
It already has internal and external box shadows giving the LED glow effect Presumably the external glow could indicate auto/manual and the internal on/off.
Unfortunately that node has not been updated with the Dashboard 3 msg.className feature.

So now I'm considering wrapping the LED node in a template to get more control over it.
I'm sure I've seen somewhere in the forum a standard ui widget wrapped in a template but I can't find it.
Can someone please point me in the right direction?

Have you opened an issue in the nodes GitHub page asking for this?

Not exactly sure what you were after but this is a ui_template version of an LED. The CSS I use with it is also down below. Someone a bit cleverer than me can probably work out how to automatically generate the colour class.

  <div id="{{'led' + $id}}"
       data-colour='["red", "grey"]'
       data-input='["on", "off"]'>


    (function(scope) {
    // Important. Use JQuery document ready function so that the Unique id is created & loaded
    $(function() {
      const self = $("#led" + scope.$id)

      // Data to use from the HTML
      let ledData = {
        colour: self.data('colour'),
        input: self.data("input"),


      let ledState = 0                                            // Current state
      let currentClass = 'led-' + ledData.colour[ledState]

      // Data to return to HTML led
      self.addClass(currentClass)                                 // Base LED class + colour class
      /* Functions to use in the Scope */
      // Triggered if the node receives a msg. Updates the LED according to the payload received
      scope.$watch('msg', function(msg) {
        let payloadState = msg.payload.toLowerCase()

        // Use Math.max to ensure that ledState is never -1
        ledState = Math.max(0, ledData.input.indexOf(payloadState))

      // Remove the current colour class, update and add the new colour class
      function showChange() {
        currentClass = "led-" + ledData.colour[ledState]





    .led {
        background-color: rgba(255, 255, 255, 0.25);
        min-height: 14px;
        min-width: 14px;
        height: 14px;
        width: 14px;
        max-height: 14px;
        max-width: 14px;
        text-align: center;
        margin: 17px 11px 11px;
        border-radius: 50%;

    .led-red {
        background-color: #ff0000;
        box-shadow: #0000009e 0 0px 3px 0px, #ff0000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;

    .led-orange {
        background-color: #FF7000;
        box-shadow: #0000009e 0 0px 3px 0px, #FF7000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;

    .led-yellow {
        background-color: #ffff00;
        box-shadow: #0000009e 0 0px 3px 0px, #ffff00 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;

    .led-green {
        background-color: #008000;
        box-shadow: #0000009e 0 0px 3px 0px, #008000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;

    .led-blue {
        background-color: #0000ff;
        box-shadow: #0000009e 0 0px 3px 0px, #0000ff 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;

    .led-grey {
        background-color: #808080;
        box-shadow: #0000009e 0 0px 3px 0px, #808080 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;

Here's another approach to get colors for led type of things a bit more easily.
Also included an example for two-color led as it was asked initially.

[{"id":"ebb5f1d239c7b697","type":"ui_template","z":"9141c3fbcc7d9e2b","group":"f6124e036e2d2ffb","name":"CSS","order":12,"width":0,"height":0,"format":"<style>   \n    .led-container{        \n        display: flex;\n        justify-content: center;\n        align-content: space-around;\n        align-items: center;\n    }\n    .led-stripe-container{\n        overflow: visible;\n        align-items: center;\n        flex-direction: row;\n        align-content: center;\n        justify-content: space-between;\n    }\n    .led {\n        border-radius: 2em;\n        width: 1em;\n        height: 1em;\n        background-color: hsl(var(--ledcolor) 100% 50%);\n        box-shadow: 0 0 6px 3px hwb(var(--ledcolor) 0% 49%), inset 0px -5px 6px -3px hsl(var(--ledcolor) 100% 30%);\n         \n    }\n    .double {\n        width: 1.2em;\n        height: 1.2em;\n    }\n    .led.double:before {\n        content: '';\n        position: absolute;\n        width: .6em;\n        height: .6em;\n        background-color: hsl(var(--ledcolor-inner) 100% 50%);\n        border-radius: 2em;\n        transform: translate(50%, 50%);\n        filter: blur(1px);\n    }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":890,"y":1240,"wires":[[]]},{"id":"7738dcea5490dab0","type":"ui_template","z":"9141c3fbcc7d9e2b","group":"f6124e036e2d2ffb","name":"led stripe","order":11,"width":"5","height":"1","format":"<div id=\"{{'led_'+$id+'_1'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_2'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_3'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_4'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_5'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_6'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_7'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_8'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n<div id=\"{{'led_'+$id+'_9'}}\" class=\"led\" style=\"--ledcolor:360\"></div>\n\n<script>\n    (function(scope) {\n  scope.$watch('msg', function(msg) {\n    if (msg) {  \n        msg.payload.forEach(led => {  \n          var l = document.getElementById(\"led_\"+scope.$id+\"_\"+led.index)\n          if(l){           \n              l.style.setProperty('--ledcolor',led.color);\n          }  \n        })    // Do something when msg arrives\n      \n    }\n  });\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"led-stripe-container","x":900,"y":1320,"wires":[[]]},{"id":"bb9008528aec954a","type":"ui_template","z":"9141c3fbcc7d9e2b","group":"f6124e036e2d2ffb","name":"double led","order":11,"width":"1","height":"1","format":"<div class=\"led double\" style=\"--ledcolor:10; --ledcolor-inner:150\"></div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"led-container","x":910,"y":1280,"wires":[[]]},{"id":"f3ebf334cceccde6","type":"inject","z":"9141c3fbcc7d9e2b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"led","payload":"","payloadType":"date","x":600,"y":1320,"wires":[["a6d1e1e9acb18c15"]]},{"id":"a6d1e1e9acb18c15","type":"function","z":"9141c3fbcc7d9e2b","name":"","func":"msg.payload = []\nfor(var i = 0;i<10;++i){\n    var o = {}\n    o.index = i\n    o.color = Math.floor(Math.random()*360)\n    msg.payload.push(o)\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":1320,"wires":[["7738dcea5490dab0"]]},{"id":"f6124e036e2d2ffb","type":"ui_group","name":"Default","tab":"34ab7d39f3e308fc","order":1,"disp":true,"width":"5","collapse":false,"className":""},{"id":"34ab7d39f3e308fc","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

Different! (And I actually understood how this one works!!)

Thanks for the hint and Idea!!


1 Like

Gave it a try, but ran around in circles, no luck.... Uhhh..... I'm a bit "lost" with this one...

But thanks for posting!! (Those with more grey matter than me would defs have better success than I did!!)


Pretty much EXACTLY what I am trying to do!!

I am gonna spend another couple of hours and see if I can get it going properly... Uhhh.... Nope... Let me rather say "Spend a few hours trying to learn how to make it do what I want!!"

Thanks hotNipi !!

I will report back when I have something intelligent (or rather, less stoopid) to add or ask!!


1 Like

Hey @hotNipi ...

Ok.... So I think I have an idea as to how to control it... (The dual led, that is..)

In the double led template, the line:

<div class="led double" style="--ledcolor:150; --ledcolor-inner:10"></div>

refers to the two numeric colour codes, 150 being the outer colour and 10 being the inner(duh)...

I changed it to:
<div class="led double" style="--ledcolor:{{msg.outer}}; --ledcolor-inner:{{msg.inner}}"></div>
and then fed it via a couple of inject nodes.... It seems to work! (Is there perhaps a better way of doing it?)

Now... The colours.... What colour table does the number represent? Can it be changed to say RGB or something else, more literal maybe, like injecting "yellow" or "orange" etc via the inject node...?

Thanks heaps!!


I would guess its a standard color wheel - eg https://www.w3schools.com/colors/colors_picker.asp

The value is the hue component of the HSL color. Colors HSL

It is used for led background and with the shadows.
It is as simple as it can get. You have to define or change only one number. Well two numbers of course in case of double led.

Your way is simplest and works. And as the content of the ui_template is also very minimal, there is no reason for more complex things.
But as always - it depends what you are expecting the widget will do for you. If you going to make it somehow smarter, it may require more specific treatment.

It is possible but somewhere must be some kind of mapping to translate "yellow" to corresponding number. And that is as previously said "making the widget smarter"

1 Like

Hey hey!!

All noted, possibly the only thing I would like to do to make the widget a bit smarter is to let it "auto choose" the colour displayed given one of 3 options for the inner and outer colours...

I have put together a simple way around it using a function node, but there is probably a waaaay better way of doing it....

Here is my simplistic function node attempt (don't laugh!! ... It only took about 2hrs of messing about to do it...Lol... There were quite a few things I tried that didn't work)

darkred    hsl(346 100% 30%)
red         hsl(359 93% 49%)
lightgreen hsl(127 100% 46%)
green       hsl(123 100% 29%)
lightblue  hsl(186 100% 48%)
blue        hsl(230 100% 45%)
yellow      hsl(54 100% 61%)
orange      hsl(29 100% 52%)
purple      hsl(278 100% 52%)
black       hsl(0 0% 0%)
grey        hsl(0 6% 69%)
pink        hsl(300 100% 83%)
if(msg.inner === 0){msg.inner = "359 93% 49%"}//Red
if(msg.inner === 1){msg.inner = "123 100% 29%"}//Green
if(msg.inner === 2){msg.inner = "230 100% 45%"}//Blue
if(msg.outer === 0){msg.outer = "359 93% 49%"}//Red
if(msg.outer === 1){msg.outer = "123 100% 29%"}//Green
if(msg.outer === 2){msg.outer = "230 100% 45%"}//Blue
return msg;

I did try to combine the function node with the widget and slap them into a subflow.... Had "unpredictable" results.... I am not sure how well the NR dashboard handles subflows, so I backed out quietly....

It would be nice for me to have a single "Dual LED Widget" node rather than have to tack on a function node, but I am immensely happy with it even as it now stands!!

I kicked my other home auto dashboard, non-node-red-software, out yesterday and this will go a long way to getting similar looks....(But with lower cpu overheads!!)

Thanks BIG time!!

Not supported. Some things do work but zero guarantees. Use regular flows.

So you want to have full control over the HSL color components. That takes to make all components dynamic not only the hue.

This will lead you to the CSS (styling) world. All the CSS is in the other ui_template with name CSS as you probably already noticed.

.led {
        border-radius: 2em;
        width: 1em;
        height: 1em;
        background-color: hsl(var(--ledcolor) 100% 50%);
        box-shadow: 0 0 6px 3px hwb(var(--ledcolor) 0% 49%), inset 0px -5px 6px -3px hsl(var(--ledcolor) 100% 30%);

This line:

 background-color: hsl(var(--ledcolor) 100% 50%);

As you see, the variable is used only for hue component.
But you can make them all as variables. For example:

 background-color: hsl(var(--hue) var(--saturation) var(--lightness));

And then you'll need to change the ui_template accordingly and also the incoming message should provide all those values.

Also the shadows use variables. You can play with them also. Best tool for this is browser developer tool where you can change values for any CSS rule and see immediate results. Then If you are satisfied, copy the changed rule to have it as permanent with your creation.

That way you have full control over the led appearance.

1 Like

There is an example of colour calculated from the value of a msg property at https://discourse.nodered.org/t/warp-speed-indicator-changing-background-colour-according-to-payload-value-css-only/57256
In that case it's the background colour which changes according to the value of msg.payload.
It may be applicable to your LED.

1 Like

Hey O great CSS oracle!!

This here:

Shouldn't it be "3px hsl(var"....?

I have been trying for the last couple of days to work out why I am getting some weird colours coming up.... (Don't wanna change anything in case I break it properly!!)