Node-RED Flow: Status Display with Icons and Colors

This flow provides a status display for a device (a fan) using Node-RED Dashboard. It uses Material Icons and color coding to visualize different states.

How it shoud work:

Inject Nodes
Four inject nodes simulate different status messages: "on", "off", "warn", and "error". Each inject node sends its payload to the function node for testing.

Function Node ("Status interpretieren")
This node interprets the status string and maps it to:

  • An icon (from Google Material Icons)
  • A color (green, gray, orange, or red)
  • A descriptive text (e.g., "Eingeschaltet" = "On", "Ausgeschaltet" = "Off", etc.)

Mapping:

  • "on" → green power icon, "Eingeschaltet"
  • "off" → gray power_off icon, "Ausgeschaltet"
  • "warn" → orange warning icon, "Warnung"
  • "error" → red error icon, "Fehler"

UI Template Node ("Statusanzeige"):

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

<div style="text-align: center;">
  <span class="material-icons" style="font-size: 64px; color: {{msg.payload.color}};">
    {{msg.payload.icon}}
  </span>
  <div style="font-size: 18px; margin-top: 8px;">
    {{msg.payload.text}}
  </div>
</div>

This node displays the icon and text in the dashboard, centered and styled according to the payload from the function node.

Debug Node:
For troubleshooting, the output is also sent to a debug node.

The icon and the text change, but the color does not.

I have tried it on two different Node-RED instances and displayed it on different pages, browsers, iPhone, and computer, so it seems that changing the color does not work in my setup.
Can anyone give me a tip where the problem might be?

In Dashboard 1, everything was so easy :frowning:

[
    {
        "id": "4f939a4c6949aad0",
        "type": "debug",
        "z": "96021c4f811b5928",
        "name": "debug 65",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1180,
        "y": 920,
        "wires": []
    },
    {
        "id": "c90a1a41d6bd105d",
        "type": "ui-template",
        "z": "96021c4f811b5928",
        "group": "e48a101406376c80",
        "page": "",
        "ui": "",
        "name": "Statusanzeige",
        "order": 2,
        "width": 3,
        "height": 2,
        "format": "<link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n\n<div style=\"text-align: center;\">\n  <span class=\"material-icons\" style=\"font-size: 64px; color: {{msg.payload.color}};\">\n    {{msg.payload.icon}}\n  </span>\n  <div style=\"font-size: 18px; margin-top: 8px;\">\n    {{msg.payload.text}}\n  </div>\n</div>",
        "storeOutMessages": false,
        "passthru": false,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 1240,
        "y": 860,
        "wires": [
            []
        ]
    },
    {
        "id": "4778c6fa41a35d40",
        "type": "function",
        "z": "96021c4f811b5928",
        "name": "Status interpretieren",
        "func": "let status = msg.payload;\n\nlet result = {\n    icon: \"help\",\n    color: \"gray\",\n    text: \"Unbekannt\"\n};\n\nif (status === \"on\") {\n    result.icon = \"power\";\n    result.color = \"green\";\n    result.text = \"Eingeschaltet\";\n} else if (status === \"off\") {\n    result.icon = \"power_off\";\n    result.color = \"gray\";\n    result.text = \"Ausgeschaltet\";\n} else if (status === \"warn\") {\n    result.icon = \"warning\";\n    result.color = \"orange\";\n    result.text = \"Warnung\";\n} else if (status === \"error\") {\n    result.icon = \"error\";\n    result.color = \"red\";\n    result.text = \"Fehler\";\n}\n\nmsg.payload = result;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 940,
        "y": 860,
        "wires": [
            [
                "c90a1a41d6bd105d",
                "4f939a4c6949aad0"
            ]
        ]
    },
    {
        "id": "539b16f9db1fbfcb",
        "type": "inject",
        "z": "96021c4f811b5928",
        "name": "ON",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "on",
        "payloadType": "str",
        "x": 640,
        "y": 800,
        "wires": [
            [
                "4778c6fa41a35d40"
            ]
        ]
    },
    {
        "id": "f60964ceb0086413",
        "type": "inject",
        "z": "96021c4f811b5928",
        "name": "OFF",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "off",
        "payloadType": "str",
        "x": 640,
        "y": 840,
        "wires": [
            [
                "4778c6fa41a35d40"
            ]
        ]
    },
    {
        "id": "d1def772a3467d5e",
        "type": "inject",
        "z": "96021c4f811b5928",
        "name": "WARN",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "warn",
        "payloadType": "str",
        "x": 640,
        "y": 880,
        "wires": [
            [
                "4778c6fa41a35d40"
            ]
        ]
    },
    {
        "id": "5b10ba8ca031a2d8",
        "type": "inject",
        "z": "96021c4f811b5928",
        "name": "ERROR",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "error",
        "payloadType": "str",
        "x": 640,
        "y": 920,
        "wires": [
            [
                "4778c6fa41a35d40"
            ]
        ]
    },
    {
        "id": "e48a101406376c80",
        "type": "ui-group",
        "name": "LĂĽfter Keller1",
        "page": "4bf14cd0fe0d343e",
        "width": "3",
        "height": "1",
        "order": 1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "id": "4bf14cd0fe0d343e",
        "type": "ui-page",
        "name": "Page 2",
        "ui": "f96401822df0a597",
        "path": "/page2",
        "icon": "thermometer",
        "layout": "grid",
        "theme": "e69c750bbe40bb7a",
        "breakpoints": [
            {
                "name": "Default",
                "px": "0",
                "cols": "3"
            },
            {
                "name": "Tablet",
                "px": "576",
                "cols": "6"
            },
            {
                "name": "Small Desktop",
                "px": "768",
                "cols": "9"
            },
            {
                "name": "Desktop",
                "px": "1024",
                "cols": "12"
            }
        ],
        "order": 4,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "f96401822df0a597",
        "type": "ui-base",
        "name": "My Dashboard",
        "path": "/dashboard",
        "appIcon": "",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false,
        "showPageTitle": true,
        "navigationStyle": "default",
        "titleBarStyle": "default"
    },
    {
        "id": "e69c750bbe40bb7a",
        "type": "ui-theme",
        "name": "Default Theme",
        "colors": {
            "surface": "#ffffff",
            "primary": "#0094ce",
            "bgPage": "#eeeeee",
            "groupBg": "#eeeeee",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "density": "default",
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    }
]

While your {{msg.payload.icon}} and {{msg.payload.text}} are between <div> and </div> tabs, msg.payload.color is inside the <span ... > tag.
In order to access a message property here you need to use a completely different syntax.
The template documentation explains it like this

  • Attribute Binding - Use : to bind a variable to an attribute. Anything inside the "" here is treated as JavaScript, for example:
<!-- Change color based on msg.payload. Expects payload to be either "error", "warning" or "info" -->`
<p :class="'text-' + msg.payload">Hello World</p>
<!-- or with string literals: -->
<p :class="`text-${msg.payload}`">Hello World</p>
1 Like

Hi jbudd
Sorry, but I can't figure out where the error is. I tried several versions.
I don't understand why I can't control the color.

I'd gladly help but alas I can't comprehend the documentation either :face_with_raised_eyebrow:

The example is setting a class based on msg.payload.
Maybe, just maybe, use the "Attribute binding" syntax to set span class="material-icons-red" or "material-icons-green" and use a seperate CSS template to specify color and font-size for span .material-icons-green.