Dynamically changing the whole dashboard (colors, dark/light mode)

I figured I'd share this since I've seen a lot of threads/questions about how to dynamically change the colors of a dashboard using CSS in the head section, it's far from perfect and I'm not really a front-end developer so it's mostly just a bunch of stuff from these forums.

Suggestions for improvements happily accepted but hopefully this can help out others trying to do the same.

[{"id":"c0fee764.1fb408","type":"ui_template","z":"3fdbb98a.3e95c6","group":"","name":"Head - Template","order":2,"width":0,"height":0,"format":"<style>\n    /*\n    Pre-defined colors\n    */\n    :root {\n        --color-main-dark:  rgb(148,87,87,1);\n        --color-main-light:  rgb(0,0,66,1);\n        --color-main: var(--color-main-light);\n        --color-text-light: rgba(12, 9, 62,1);\n        --color-text-dark: rgb(254, 222, 222);\n        --color-text: var(--color-text-light);\n        --color-widget-border: rgb(84, 78, 78);\n        --color-widget-trans: rgba(178, 178, 178, 0.2);\n        --color-disabled: rgb(200, 200, 210);\n        --color-button-light: rgba(5,16,60,0.8);\n        --color-button-dark: rgba(54,16,16,0.8);\n        --color-button: var(--color-button-light);\n        --color-selection-light: rgba(100,100,155,1);\n        --color-selection-dark: rgba(154,116,116,0.8);\n        --color-selection: var(--color-selection-light);\n        --color-tint-light: rgba(182, 182, 255,1);\n        --color-tint-dark: rgba(255, 182, 182,1);\n        --color-tint: var(--color-tint-light);\n        --color-gauge-light: rgba(19,1,106,0.8);\n        --color-gauge-dark: rgba(229,156,131,0.8);\n        --color-gauge: var(--color-gauge-light);\n        --color-background-primary-light: rgba(230,246,246,1);\n        --color-background-secondary-light: rgba(188,212,217,0.5);\n        --color-background-primary-dark: rgba(17,02,02,1);\n        --color-background-secondary-dark: rgba(07,0,0,0.8);\n        --color-background-primary: var(--color-background-primary-light);\n        --color-background-secondary: var(--color-background-primary-light);\n    }\n    /*\n    Various CSS segments borrowed from the Node Red forums and customized\n    */\n    .nr-dashboard-template {\n        padding: 0px;\n    }\n    .masonry-container {\n        position: relative;\n        width: 100%;\n        height:100%;\n        margin: 0 auto;\n        background: linear-gradient(180deg,  var(--color-background-primary) 0%, var(--color-background-secondary) 100%); \n    }\n    .nr-dashboard-cardcontainer {\n        position: relative;\n        box-shadow: inset 0px 1px 4px 0px #000000bb;\n        border-radius: 10px;\n    }\n    .nr-dashboard-theme ui-card-panel {\n        background-color: #33333300;\n        color:var(--color-text);\n        border-radius: 10px;\n        box-shadow: 0 0 8px 0px #00000080;\n    }\n    body.nr-dashboard-theme md-content md-card {\n        background-color: #33333300;\n        color: var(--color-text);\n        text-shadow: 4px 2px 4px #00000045;\n        box-shadow: 0 -1px 5px 1px #00000045;\n        border-radius: 10px;\n        border: 1px solid var(--color-widget-border);\n    }\n    .nr-dashboard-theme ui-card-panel p.nr-dashboard-cardtitle {\n        color: var(--color-text);\n    }\n    .nr-dashboard-theme .nr-dashboard-gauge text {\n        fill: var(--color-gauge);\n        margin: 8px;\n    }\n    .nr-dashboard-theme .nr-dashboard-button .md-button  {\n        display: inline-block;\n        position: relative;\n        cursor: pointer;\n        min-height: 36px;\n        min-width: 88px;\n        line-height: 36px;\n        vertical-align: middle;\n        align-items: center;\n        text-align: center;\n        border-radius: 12px;\n        box-sizing: border-box;\n        -webkit-user-select: none;\n        -moz-user-select: none;\n        -ms-user-select: none;\n        user-select: none;\n        outline: none;\n        border: 0;\n        padding: 0 6px;\n        margin: 6px 8px;\n        background: transparent;\n        color:var(--color-text);\n        white-space: nowrap;\n        text-transform: uppercase;\n        font-weight: 500;\n        font-size: 14px;\n        font-style: inherit;\n        font-variant: inherit;\n        font-family: inherit;\n        text-decoration: none;\n        overflow: hidden;\n        transition: box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1);\n    }\n    .nr-dashboard-theme .nr-dashboard-template .md-button:disabled {\n        color: var(--color-disabled);\n    }\n    md-toolbar#toolbar{\n        color: #ffffffff;\n        background: linear-gradient(120deg, var(--color-main) 20%, var(--color-background-secondary) 100%); \n    }\n    body.nr-dashboard-theme md-sidenav {\n        color: var(--color-text);\n    }\n    body.nr-dashboard-theme md-sidenav {\n        color: var(--color-text);\n        background: linear-gradient(120deg, var(--color-main) 60%, var(--color-background-primary)); \n    }\n    body.nr-dashboard-theme md-sidenav div.md-list-item-inner {\n        color: var(--color-background-secondary);\n        background-color: transparent;\n    }\n    .nr-dashboard-theme .nr-dashboard-button .md-button {\n        background-color: #88888800;\n        color:var(--color-text);\n    }\n    .nr-dashboard-theme .nr-dashboard-button .md-button:hover {\n        background-color: #88888855;\n    }\n    .nr-dashboard-theme .nr-dashboard-slider .md-track {\n        background-color: var(--color-main);\n    }\n    md-slider .md-thumb {\n        box-shadow: 0 0 10px 0px #101086;\n    }    \n    .nr-dashboard-slider .md-thumb:after {\n        background-image: linear-gradient(to bottom right, var(--color-button), var(--color-tint));\n        border-style: none;\n    }\n    .nr-dashboard-theme .nr-dashboard-template ::-webkit-scrollbar {\n        background-color: #666666;\n    }\n    .nr-dashboard-theme .nr-dashboard-template ::-webkit-scrollbar-thumb {\n        background-color: var(--color-main);\n    }\n    md-switch .md-thumb {\n        background-color: var(--color-main);\n    }\n    md-switch .md-bar {\n        background-color: var(--color-background-secondary);\n    }\n    md-switch.md-checked:not([disabled]) .md-bar {\n        background-color: var(--color-button) !important; \n    }\n    md-switch.md-checked:not([disabled]) .md-thumb {\n        background-color: var(--color-text) !important;\n    }\n    .nr-dashboard-theme .nr-dashboard-numeric .value {\n        background:unset;\n        color:var(--color-text);\n    }\n    .nr-dashboard-theme md-select-menu md-option {\n        background-color:  var(--color-background-primary);\n        color: var(--color-text);\n        height: 29px;\n        border-radius: 10px;*/\n        smargin-left: 10px;\n        margin-right: 10px;\n        margin-top: 2px;\n        box-shadow: 0 0 6px 6px #24202133;\n        transition: 0.3s;\n    }\n    .nr-dashboard-theme .nr-dashboard-dropdown md-select .md-select-value, .nr-dashboard-theme .nr-dashboard-dropdown md-select .md-select-value.md-select-placeholder {\n        margin-top: -3px;\n        border-color: var(--color-widget-border);\n        color: var(--color-text);\n    }\n    .nr-dashboard-theme md-select-menu md-option[selected] {\n        color: var(--color-text) !important;\n        background-color: var(--color-background-primary) !important;\n    }\n    .nr-dashboard-theme md-select-menu md-option:last-child {\n        margin-bottom: 8px;\n    }\n    .nr-dashboard-theme md-select-menu md-option:hover {\n        background-color: var(--color-selection) !important;\n        padding-left: 24px;\n    }\n    .nr-dashboard-theme md-select-menu md-option .md-ripple-container{\n        border-radius: 10px;\n    }\n</style>\n\n<script id=\"headScript\" type=\"text/javascript\">\n    /*\n    Javascript to inject UI elements into the top of all dashboards (head)\n    */\n    var clockInterval;\n    $(function () {\n        if (clockInterval) return;\n\n        //add logo\n        var div0 = $('<div/>');\n        var logo = new Image();\n        logo.src = '/plugins/signalk-node-red/redAdmin/red/images/Boatcontrol_Logo_Light.png'\n        logo.height = 45;\n\n        div0[0].style.margin = '10px auto';\n        div0[0].style.width = '100px';\n        div0[0].style.float = 'left';\n\n        div0.append(logo);\n        \n        \n        //add clock\n        var div1 = $('<div/>');\n        var p = $('<p/>');\n\n        div1.append(p);\n        div1[0].style.margin = '5px';\n        div1[0].style.width = '120px';\n        div1[0].style.float = 'right';\n\n        function displayTime() {\n            p.text(new Date().toLocaleTimeString());\n        }\n        \n        //add day/night mode button\n        var div2 = $('<div/>');\n        var button = document.createElement('BUTTON');\n        var html = document.getElementsByTagName('html')[0];\n        var elem = window.getComputedStyle(html);\n        var night_icon = document.createElement(\"night\");\n        var day_icon = document.createElement(\"day\");\n        day_icon.className =\"fa fa-eye\";\n        night_icon.className =\"fa fa-eye-slash\";\n        button.appendChild(night_icon);\n        button.onclick = function(){\n            button.replaceChild((button.firstChild.nodeName == \"NIGHT\") ? day_icon : night_icon, (button.firstChild.nodeName == \"DAY\") ? day_icon : night_icon);\n     \n            html.style.setProperty(\"--color-main\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-main-light\") : elem.getPropertyValue(\"--color-main-dark\"));\n            html.style.setProperty(\"--color-text\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-text-light\") : elem.getPropertyValue(\"--color-text-dark\"));\n            html.style.setProperty(\"--color-button\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-button-light\") : elem.getPropertyValue(\"--color-button-dark\"));\n            html.style.setProperty(\"--color-gauge\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-gauge-light\") : elem.getPropertyValue(\"--color-gauge-dark\"));\n            html.style.setProperty(\"--color-selection\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-selection-light\") : elem.getPropertyValue(\"--color-selection-dark\"));\n            html.style.setProperty(\"--color-tint\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-tint-light\") : elem.getPropertyValue(\"--color-tint-dark\"));\n            html.style.setProperty(\"--color-background-primary\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-background-primary-light\") : elem.getPropertyValue(\"--color-background-primary-dark\"));\n            html.style.setProperty(\"--color-background-secondary\",(button.firstChild.nodeName == \"NIGHT\") ? elem.getPropertyValue(\"--color-background-secondary-light\") :elem.getPropertyValue(\"--color-background-secondary-dark\"));\n        }\n\n        div2[0].style.margin = '5px auto';\n        div2[0].style.width = '60px';\n        div2[0].style.float = 'right';\n        button.tooltip = 'Switch between night(dark) and day(light) themes';\n        button.style.color = elem.getPropertyValue(\"--color-widget\");\n        button.style.background = elem.getPropertyValue(\"--color-widget-trans\");\n        button.style.borderRadius = '20%';\n        div2.append(button);\n        \n        clockInterval = setInterval(displayTime, 1000);\n\n        //add to toolbar when it's available\n        var addToToolbarTimer;\n        \n        function addToToolbar() {\n            var toolbar = $('.md-toolbar-tools');\n            \n            if(!toolbar.length) return;\n            \n            toolbar.append(div0);\n            toolbar.append(div1);\n            toolbar.append(div2);\n            clearInterval(addToToolbarTimer);\n        }\n        addToToolbarTimer = setInterval(addToToolbar, 100);\n    });\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":760,"y":40,"wires":[[]]}]
8 Likes

Welcome to forum Antonia.

As the topic of your post says, it changes colors and it really does. Good job. Keep going :slight_smile:
As you asked, I'll give my couple of cents...

Alongside of color change your solution also makes some changes for widgets look. It may be hard to figure out what comes from where for less experienced users. They tend to take anything and try to use without ability to adjust it cos it is complicated and takes to learn before use. And the outcome is almost always something not that good they imagined but not that bad they desperately trying to fix. But as the CSS is art - the factor of satisfaction is bound into it. This fact cant be underestimated.

So may be it will be better to break out the coloring stuff from other branding and provide such solution. Also I see that you havent reached to all standard widgets. That is definitely point of improvement. And why not to look over the contrib widgets also and see if they have something to take care of.

And then - why not to go for full brand override ... :slight_smile:

Thanks for the feedback, it's appreciated and makes sense.

I'll continue working on this alongside with my project ( GitHub - antevens/boatcontrol: A Relay PCB with a socket for a Raspberry Pi or Nvidia Jetson to control everything on a boat ) and hopefully as the project matures so will the Node-Red code, who knows more people might join the project later on as open hardware becomes more popular for boat navigation and automation.

My ultimate goal is a full brand override but I haven't worked with JS in .. over 20 years so I need to do a "bit" of catching up :smiley:

Hey... that sounds like me :laughing: although I am slowly learning the "adjusting" part :wink:

1 Like

Hi!

I already searched for this solution to build a day/night look of my dashboard. Is it also possible, to change the colors automatically (at a specific time, maybe at sunset) and not manually by clicking on that button?

Otherwise I love your work!

Hi Stefan,

It shouldn't be too hard to change based on the time of day assuming you know the latitude/longtitude of the user. With a mobile device you can probably get that from the GPS but with a regular computer you might have to rely on using IP based geolocation in combination with time zone information. I also think Google and others have the ability to determine location based on which Wifi networks are in range but I've never used it.

If you can rely on the end user always having an internet connection I'm sure you can find a free library or API that you can call using JavaScript, if you need things to work offline it might be a little bit harder since you would have to bundle the code with the app.

I had a similar usecase myself but since I can't depend on having an internet connection I decided that for now it was easier to just switch manually.

Sorry, my problem is not the astro time (there a already some nodes for getting sunrise or sunset in my geo location), but a way to change the theme via a flow instead of clicking your button in the header bar.

There is many ways to do it.
Examine this example, you may get some ideas from it.

[
    {
        "id": "81a7e519f87b90e8",
        "type": "ui_button",
        "z": "2e6686c4918f199d",
        "name": "",
        "group": "6ff3bb48.c2b764",
        "order": 41,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "swap",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "",
        "payload": "swap",
        "payloadType": "str",
        "topic": "topic",
        "topicType": "msg",
        "x": 220,
        "y": 300,
        "wires": [
            [
                "a448d248339e2b87"
            ]
        ]
    },
    {
        "id": "45789b278ea3396d",
        "type": "ui_button",
        "z": "2e6686c4918f199d",
        "name": "",
        "group": "6ff3bb48.c2b764",
        "order": 41,
        "width": "3",
        "height": "1",
        "passthru": false,
        "label": "toggle",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "",
        "payload": "toggle",
        "payloadType": "str",
        "topic": "topic",
        "topicType": "msg",
        "x": 220,
        "y": 340,
        "wires": [
            [
                "a448d248339e2b87"
            ]
        ]
    },
    {
        "id": "89169270a5949362",
        "type": "ui_template",
        "z": "2e6686c4918f199d",
        "group": "6ff3bb48.c2b764",
        "name": "change theme color",
        "order": 4,
        "width": "0",
        "height": "0",
        "format": "<script>\n    (function(scope) {\n    var colors = [\"#006600\", \"#006633\", \"#006666\", \"#006699\", \"#0066CC\", \"#0066FF\",\n    \"#009900\", \"#009933\", \"#009966\", \"#009999\", \"#0099CC\", \"#0099FF\",\n    \"#00CC00\", \"#00CC33\", \"#00CC66\", \"#00CC99\", \"#00CCCC\", \"#00CCFF\",\n    \"#00FF00\", \"#00FF33\", \"#00FF66\", \"#00FF99\", \"#00FFCC\", \"#00FFFF\"]\n   \n    scope.$watch('msg', function(msg) {\n        if (msg) {\n\n            var val = msg.payload // slider (0-23) or hourly timestamp\n            if(val > 23){\n                val = new Date().getHours()// current hour\n            }\n        \n            var color = colors[val]\n            document.documentElement.style.setProperty('--theme-color', color);\n        }\n    });\n})(scope);\n</script>",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 410,
        "y": 430,
        "wires": [
            []
        ]
    },
    {
        "id": "546f6dd24157e24a",
        "type": "ui_slider",
        "z": "2e6686c4918f199d",
        "name": "",
        "label": "change color",
        "tooltip": "",
        "group": "6ff3bb48.c2b764",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": true,
        "outs": "all",
        "topic": "topic",
        "topicType": "msg",
        "min": 0,
        "max": "23",
        "step": 1,
        "className": "",
        "x": 200,
        "y": 400,
        "wires": [
            [
                "89169270a5949362"
            ]
        ]
    },
    {
        "id": "0d6a37d3f277ef3f",
        "type": "ui_template",
        "z": "2e6686c4918f199d",
        "group": "6ff3bb48.c2b764",
        "name": "CSS",
        "order": 7,
        "width": 0,
        "height": 0,
        "format": "<style>\n    :root {\n        --theme-color: #4b7930;\n    }\n    \n    .nr-dashboard-theme .nr-dashboard-button .md-button {\n        background-color: var(--theme-color);\n    }\n    .nr-dashboard-theme .nr-dashboard-switch md-switch.md-checked .md-thumb {\n        background-color: var(--theme-color);\n    }\n\n    .nr-dashboard-theme .nr-dashboard-slider .md-thumb:after {\n        background-color: var(--theme-color);\n        border-color: var(--theme-color);\n    }\n    .nr-dashboard-theme .nr-dashboard-slider .md-focus-ring {\n        background-color: var(--theme-color);   \n    }\n    .nr-dashboard-theme .nr-dashboard-slider .md-sign {\n        background-color: var(--theme-color);\n    }\n    .nr-dashboard-theme .nr-dashboard-slider .md-track-fill {\n        background-color: var(--theme-color);\n    }\n</style>",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 450,
        "y": 470,
        "wires": [
            []
        ]
    },
    {
        "id": "b0f96ea8548aa883",
        "type": "ui_switch",
        "z": "2e6686c4918f199d",
        "name": "",
        "label": "switch",
        "tooltip": "",
        "group": "6ff3bb48.c2b764",
        "order": 8,
        "width": 0,
        "height": 0,
        "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": 220,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "3e35a62657813016",
        "type": "inject",
        "z": "2e6686c4918f199d",
        "name": "hourly",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "3600",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payloadType": "date",
        "x": 210,
        "y": 450,
        "wires": [
            [
                "89169270a5949362"
            ]
        ]
    },
    {
        "id": "6ff3bb48.c2b764",
        "type": "ui_group",
        "name": "FlowChart",
        "tab": "c209d57.e95de28",
        "order": 1,
        "disp": true,
        "width": "8",
        "collapse": false,
        "className": ""
    },
    {
        "id": "c209d57.e95de28",
        "type": "ui_tab",
        "name": "Default",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    }
]