CSS Square & Round On/Off Buttons (Revisited)

Buttons

Standard Buttons

In Dashboard 2 a widget height is specified in the page theme but the width varies according to the browser window size. Therefore a standard button perhaps loses some of the label as it overflows at different widths.

Button CSS Tweeked

The buttons presented here are the standard ui-button widget with CSS adjustments to maintain their width (and position within the widget) regardless of the widget display width, thus they remain round or square. Button height (and font size) can be configured as a proportion of the theme's row height. Note that there is still one button per widget, so you couldn't have eg three buttons side by side in a single 3x1 widget.

(In this pic showing different browser window sizes, ui-button widgets are outlined in red.)

Action on button press

A default button does not change the label or colour when pressed. I've used a function node for each button to toggle between "active" and "inactive" states to allow changes to the background/foreground colour and text/icon value.

Usage

There is a CSS template which can be applied to a single page or all. A button must be 1x1 or 2x2 grid units and have custom class: `mybutton large round`, `mybutton small square` etc. The function node for each button is identical except for the settings at the top eg
const foregroundcolor = { "active": "#6d3b07", "inactive": "#6d3b07" }
const backgroundcolor = { "active": "whitesmoke", "inactive": "whitesmoke" }
const icon =            { "active": "coffee-outline", "inactive": "coffee-off-outline" }
const label =           { "active": "", "inactive": "" }

Unfortunately the function nodes for the buttons on a page can't easily be combined since they use a context variable to store the button state.

Other customisations such as the button height and placement within the widget can be applied via the CSS file.

If you use the buttons to control a real world device such as a smart switch, there has to be a way to sychronise the button with the real world. For this you can pass "ON" or "OFF" into the button's function node.

Best practise is for the displayed button colour and content to represent the result of pressing it. ie if the coffee machine is switched on, the button should show red "OFF" not green "ON".

Example Flow

[{"id":"9f9f6bd43bb10890","type":"tab","label":"Buttons minimal","disabled":false,"info":"","env":[]},{"id":"92ad9b7a93d606fc","type":"junction","z":"9f9f6bd43bb10890","x":280,"y":120,"wires":[["882083c1881f21ff","3d89bd567a8d4b0a","dbebd50383aed279","56cc1637f21593ca"]]},{"id":"a9c48dc172ca860c","type":"junction","z":"9f9f6bd43bb10890","x":280,"y":135,"wires":[["882083c1881f21ff","3d89bd567a8d4b0a","dbebd50383aed279","56cc1637f21593ca"]]},{"id":"e4e0c59f2abcf432","type":"ui-button","z":"9f9f6bd43bb10890","group":"a10c2b1bf4418866","name":"On/Off","label":"","order":1,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"mybutton small square","icon":"","iconPosition":"left","payload":"toggle","payloadType":"str","topic":"Button 1 ON OFF","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":265,"y":30,"wires":[["882083c1881f21ff"]]},{"id":"7abd9ff982b9d648","type":"ui-button","z":"9f9f6bd43bb10890","group":"a10c2b1bf4418866","name":"Pare","label":"","order":4,"width":"2","height":"2","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"mybutton large round","icon":"","iconPosition":"left","payload":"toggle","payloadType":"str","topic":"Button 4 Pare","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":260,"y":210,"wires":[["56cc1637f21593ca"]]},{"id":"78b98bbae7d221c4","type":"ui-template","z":"9f9f6bd43bb10890","group":"","page":"dcb042e7ae65cbcd","ui":"","name":"mybutton CSS","order":0,"width":0,"height":0,"head":"","format":":root { \n--heightfraction: 1; /* Proportion of widget height for the button */\n--vertical: center;   /* Vertical justification self-start (top), center or self-end (bottom) */\n--horizontal: center; /* Horizontal justification left, center or right */\n\n--borderscale: 0.04;  /* Proportion of widget height for border (0 - 0.1?) */\n--bordercolor: #888;  /* Border color */\n--borderradius: 5%;   /* For 'square' buttons. */\n\n--fontscale: 0.35;     /* Proportion of button height for the text/icon (0.2 to 0.5?) */\n}\n\n/* Align buttons inside widget */\n.nrdb-ui-button.mybutton {\n    display: flex !important;\n    align-items: var(--vertical);\n    justify-content: var(--horizontal);\n}\n\n.nrdb-ui-button.mybutton button { \n    background-color: whitesmoke;   /* get rid of theme button colour */\n    font-family: arial, sans-serif;\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n    border-radius: var(--borderradius) ;  \n    border: calc(var(--borderscale) * var(--widget-row-height)) solid var(--bordercolor);    \n}\n.nrdb-ui-button.mybutton button i { /* outline icon */\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}\n.nrdb-ui-button.mybutton.round button {   /* round button border radius */\n    border-radius: 5em ;\n}\n\n/* Widget height for 1x1 buttons */\n.nrdb-ui-button.mybutton, .nrdb-ui-button.mybutton.small {\n    max-height: var(--widget-row-height);\n    min-height: var(--widget-row-height);\n}\n/* 1x1 button size */\n.nrdb-ui-button.mybutton.small button {\n    max-height: calc(var(--heightfraction) * var(--widget-row-height));\n    max-width: calc(var(--heightfraction)  * var(--widget-row-height));\n    min-height: calc(var(--heightfraction)  * var(--widget-row-height));\n    min-width: calc(var(--heightfraction)  * var(--widget-row-height));\n    font-size: calc(var(--heightfraction) * var(--fontscale) * var(--widget-row-height));\n    font-weight: normal;\n    letter-spacing: normal;\n    box-shadow: inset rgba(20, 70, 85, 0.8) 0px 0px 5px -3px;\n}\n\n/* Widget height for 2x2 buttons */\n.nrdb-ui-button.mybutton.large {\n    max-height: calc(2 * var(--widget-row-height) + var(--widget-gap)) ;\n    min-height: calc(2 * var(--widget-row-height) + var(--widget-gap));\n}\n\n/* 2x2 button size */\n.nrdb-ui-button.mybutton.large button {\n    max-height: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    max-width: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    min-height: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    min-width: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    font-size: calc(2 * var(--heightfraction) * var(--fontscale) * var(--widget-row-height) + var(--widget-gap));\n        font-size: calc(var(--heightfraction) * var(--fontscale) * (( 2 * var(--widget-row-height)) + var(--widget-gap)));\n    font-weight: bold;\n    letter-spacing: -2px;\n    box-shadow: inset rgba(20, 70, 85, 0.8) 0px 0px 10px -5px;\n}\n","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"page:style","className":"","x":100,"y":30,"wires":[[]]},{"id":"e232bb9f0d24571e","type":"ui-button","z":"9f9f6bd43bb10890","group":"a10c2b1bf4418866","name":"I/O","label":"","order":2,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"mybutton small round","icon":"","iconPosition":"left","payload":"toggle","payloadType":"str","topic":"Button 2 I O","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":265,"y":75,"wires":[["dbebd50383aed279"]]},{"id":"1372e8fb3ded30f1","type":"ui-button","z":"9f9f6bd43bb10890","group":"a10c2b1bf4418866","name":"Coffee","label":"","order":3,"width":"2","height":"2","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"mybutton large square","icon":"","iconPosition":"left","payload":"toggle","payloadType":"str","topic":"Button 3 Coffee","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":260,"y":165,"wires":[["3d89bd567a8d4b0a"]]},{"id":"03e28259adf8680b","type":"ui-template","z":"9f9f6bd43bb10890","group":"","page":"dcb042e7ae65cbcd","ui":"","name":"Outline buttons","order":0,"width":0,"height":0,"head":"","format":"<template>\n</template>\n\n<script>\n    export default {\n        watch: {\n            msg: function () {\n                if (this.msg?.topic === 'add' || this.msg?.topic === 'remove') {\n                    const fn = this[this.msg?.topic] // add or remove\n                    const selector = this.msg.selector || 'div.nrdb-ui-page'\n                    const className = this.msg.payload\n                    fn(selector, className)\n                }\n            }\n        },\n        methods: {\n            add: function (selector, className) {\n                const el = document.querySelector(selector)\n                el.classList.add(className)\n            },\n            remove: function (selector, className) {\n                const el = document.querySelector(selector)\n                el.classList.remove(className)\n            }\n        }\n    }\n</script>\n\n<style>\n.widgetoutlines .mybutton {\n    outline: 1px solid red;\n}\n</style>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"widget:page","className":"","x":485,"y":270,"wires":[[]]},{"id":"896c5144335a8d2d","type":"ui-button","z":"9f9f6bd43bb10890","group":"eb678fc84115e36b","name":"Outline","label":"Outline Widgets","order":3,"width":"2","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"widgetoutlines","payloadType":"str","topic":"add","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":90,"y":270,"wires":[["f1dbfe6c870d27be"]]},{"id":"f1dbfe6c870d27be","type":"function","z":"9f9f6bd43bb10890","name":"Toggle","func":"let state = context.get (\"state\") ?? \"on\"\nlet ctrlmsg = {}\nif (state === \"off\") {\n    ctrlmsg = {\"payload\": \"widgetoutlines\", \"topic\": \"remove\"}\n    state = \"on\"\n}\nelse {\n    ctrlmsg = { \"payload\": \"widgetoutlines\", \"topic\": \"add\" }\n    state = \"off\"\n}\ncontext.set (\"state\", state)\nreturn ctrlmsg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":270,"wires":[["03e28259adf8680b"]]},{"id":"862a6cab6e2af5d2","type":"ui-button","z":"9f9f6bd43bb10890","group":"eb678fc84115e36b","name":"Activate","label":"Activate all buttons","order":1,"width":"2","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"ON","payloadType":"str","topic":"","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":90,"y":105,"wires":[["92ad9b7a93d606fc"]]},{"id":"dc70d76497ea21dc","type":"ui-button","z":"9f9f6bd43bb10890","group":"eb678fc84115e36b","name":"Deactivate","label":"Deactivate all buttons","order":2,"width":"2","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"OFF","payloadType":"str","topic":"add","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":85,"y":150,"wires":[["a9c48dc172ca860c"]]},{"id":"56cc1637f21593ca","type":"function","z":"9f9f6bd43bb10890","name":"Control","func":"const foregroundcolor = { \"active\": \"whitesmoke\", \"inactive\": \"whitesmoke\" }\nconst backgroundcolor = { \"active\": \"green\", \"inactive\": \"red\" }\nconst icon =            { }\nconst label =           { \"active\": \"SIGA\", \"inactive\": \"PARE\" }\n\nlet state = context.get(\"state\") ?? \"active\"  // if context doesnt exist, this will result in inactive\n\nif (msg.payload === \"ON\") state = \"inactive\"  // explicit commands to sync button and real state \nif (msg.payload === \"OFF\") state = \"active\"\n\nmsg.ui_update = {}\n\nif (state === \"active\") {\n    //msg.ui_update = {\"iconColor\": inactivefgcolor, \"icon\": inactiveicon, \"buttonColor\": inactivebgcolor }\n    state = \"inactive\"\n}  \nelse if (state === \"inactive\") {\n    //msg.ui_update = {\"iconColor\": activefgcolor, \"icon\": activeicon, \"buttonColor\": activebgcolor }\n    state = \"active\"\n}\ncontext.set(\"state\", state)\n\nmsg.ui_update.label = label[state]\nmsg.ui_update.icon = icon[state]\nmsg.ui_update.textColor = foregroundcolor[state]\nmsg.ui_update.iconColor = foregroundcolor[state]\nmsg.ui_update.buttonColor = backgroundcolor[state]\n\nmsg.payload = state\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":465,"y":210,"wires":[["7abd9ff982b9d648","7f9449343fbb889f"]]},{"id":"dbebd50383aed279","type":"function","z":"9f9f6bd43bb10890","name":"Control","func":"const foregroundcolor = { \"active\": \"green\", \"inactive\": \"red\" }\nconst backgroundcolor = { \"active\": \"hsl(125, 100%, 90%)\", \"inactive\": \"hsl(0, 100%, 90%)\"}\nconst icon =            { \"active\": \"power-off\", \"inactive\": \"power-on\" }\nconst label =           { \"active\": \"\", \"inactive\": \"\" }\n\nlet state = context.get(\"state\") ?? \"active\"  // if context doesnt exist, this will result in inactive\n\nif (msg.payload === \"ON\") state = \"inactive\"  // explicit commands to sync button and real state \nif (msg.payload === \"OFF\") state = \"active\"\n\nmsg.ui_update = {}\n\nif (state === \"active\") {\n    //msg.ui_update = {\"iconColor\": inactivefgcolor, \"icon\": inactiveicon, \"buttonColor\": inactivebgcolor }\n    state = \"inactive\"\n}  \nelse if (state === \"inactive\") {\n    //msg.ui_update = {\"iconColor\": activefgcolor, \"icon\": activeicon, \"buttonColor\": activebgcolor }\n    state = \"active\"\n}\ncontext.set(\"state\", state)\n\nmsg.ui_update.label = label[state]\nmsg.ui_update.icon = icon[state]\nmsg.ui_update.textColor = foregroundcolor[state]\nmsg.ui_update.iconColor = foregroundcolor[state]\nmsg.ui_update.buttonColor = backgroundcolor[state]\n\nmsg.payload = state\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":465,"y":75,"wires":[["3bf837c96da753a5","e232bb9f0d24571e"]]},{"id":"882083c1881f21ff","type":"function","z":"9f9f6bd43bb10890","name":"Control","func":"const foregroundcolor = { \"active\": \"green\", \"inactive\": \"red\" }\nconst backgroundcolor = { \"active\": \"whitesmoke\", \"inactive\": \"whitesmoke\" }\nconst icon =            { }\nconst label =           { \"active\": \"ON\", \"inactive\": \"OFF\"  }\n\nlet state = context.get(\"state\") ?? \"active\"  // if context doesnt exist, this will result in inactive\n\nif (msg.payload === \"ON\") state = \"inactive\"  // explicit commands to sync button and real state \nif (msg.payload === \"OFF\") state = \"active\"\n\nmsg.ui_update = {}\n\nif (state === \"active\") {\n    //msg.ui_update = {\"iconColor\": inactivefgcolor, \"icon\": inactiveicon, \"buttonColor\": inactivebgcolor }\n    state = \"inactive\"\n}  \nelse if (state === \"inactive\") {\n    //msg.ui_update = {\"iconColor\": activefgcolor, \"icon\": activeicon, \"buttonColor\": activebgcolor }\n    state = \"active\"\n}\ncontext.set(\"state\", state)\n\nmsg.ui_update.label = label[state]\nmsg.ui_update.icon = icon[state]\nmsg.ui_update.textColor = foregroundcolor[state]\nmsg.ui_update.iconColor = foregroundcolor[state]\nmsg.ui_update.buttonColor = backgroundcolor[state]\n\nmsg.payload = state\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":465,"y":30,"wires":[["e4e0c59f2abcf432","329e147d0f4bd32e"]]},{"id":"3d89bd567a8d4b0a","type":"function","z":"9f9f6bd43bb10890","name":"Control","func":"const foregroundcolor = { \"active\": \"#6d3b07\", \"inactive\": \"#6d3b07\" }\nconst backgroundcolor = { \"active\": \"whitesmoke\", \"inactive\": \"whitesmoke\" }\nconst icon =            { \"active\": \"coffee-outline\", \"inactive\": \"coffee-off-outline\" }\nconst label =           { \"active\": \"\", \"inactive\": \"\" }\n\nlet state = context.get(\"state\") ?? \"active\"  // if context doesnt exist, this will result in inactive\n\nif (msg.payload === \"ON\") state = \"inactive\"  // explicit commands to sync button and real state \nif (msg.payload === \"OFF\") state = \"active\"\n\nmsg.ui_update = {}\n\nif (state === \"active\") {\n    //msg.ui_update = {\"iconColor\": inactivefgcolor, \"icon\": inactiveicon, \"buttonColor\": inactivebgcolor }\n    state = \"inactive\"\n}  \nelse if (state === \"inactive\") {\n    //msg.ui_update = {\"iconColor\": activefgcolor, \"icon\": activeicon, \"buttonColor\": activebgcolor }\n    state = \"active\"\n}\ncontext.set(\"state\", state)\n\nmsg.ui_update.label = label[state]\nmsg.ui_update.icon = icon[state]\nmsg.ui_update.textColor = foregroundcolor[state]\nmsg.ui_update.iconColor = foregroundcolor[state]\nmsg.ui_update.buttonColor = backgroundcolor[state]\n\nmsg.payload = state\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":465,"y":165,"wires":[["dc86a1b81266644b","1372e8fb3ded30f1"]]},{"id":"3bf837c96da753a5","type":"debug","z":"9f9f6bd43bb10890","name":"Payload","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":630,"y":75,"wires":[]},{"id":"329e147d0f4bd32e","type":"debug","z":"9f9f6bd43bb10890","name":"Payload","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":625,"y":30,"wires":[]},{"id":"dc86a1b81266644b","type":"debug","z":"9f9f6bd43bb10890","name":"Payload","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":630,"y":165,"wires":[]},{"id":"7f9449343fbb889f","type":"debug","z":"9f9f6bd43bb10890","name":"Payload","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":625,"y":210,"wires":[]},{"id":"a10c2b1bf4418866","type":"ui-group","name":"Text or icon button","page":"dcb042e7ae65cbcd","width":6,"height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"dcb042e7ae65cbcd","type":"ui-page","name":"Text button","ui":"e9c974f7c1d080d1","path":"/textbutton","icon":"home","layout":"grid","theme":"0d92c765bfad87e6","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":2,"className":"","visible":true,"disabled":false},{"id":"eb678fc84115e36b","type":"ui-group","name":"Actions","page":"dcb042e7ae65cbcd","width":6,"height":1,"order":2,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"e9c974f7c1d080d1","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":true},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#4d58ff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"2px","density":"default"}},{"id":"d531269ebdf73a91","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.29.0"}}]
2 Likes

It is possible to use the same technique to constrain rectangular or "pill" button width to a multiple of the theme row height, but I can't see any use for it.
I don't see a problem with buttons that are neither square nor round changing proportions with the browser window.

It's also easy for the text to overflow the button and the button to overflow the widget size setting.

Personally, I would say that text overflows are a bug, The text should be constrained inside the button.

I agree. If I thought the fixed ratio rectangular buttons worthwhile I would fix it.
It's a non-positive feature of the default buttons shown (in blue) above too.

Not such an issue with round and square buttons because, if the button is centralised in the widget, there is generally unused space within the widget but not inside the button.

There is a pre-internet meme of circles with overflowing text:

Some CSS transform effects might be useful for buttons too. A small adjustment of the CSS offers these
:
Buttons

[{"id":"e36b0931d8ca72ce","type":"ui-template","z":"cdc8d1d1591c532f","group":"","page":"004f1baddf79d52e","ui":"","name":"CSS Rotating Icons","order":0,"width":0,"height":0,"head":"","format":":root { /* All customisations here - button shape, size, position */\n--heightfraction: 1; /* Proportion of widget height for the button */\n/* NB heightfraction too small breaks proportions. Too large can cause 2x2 button to overflow at some screen sizes */\n/* For 1x1 buttons suggest 0.6 to 1, 2x2 suggest 0.3 to 0.9 */\n--fontscale: 0.4; /* Proportion of button height for the icon (0.2 to 0.7) */\n--vertical: center; /* Vertical justification self-start (top), center or self-end (bottom) */\n--horizontal: center; /* Horizontal justification left, center or right */\n--borderradius: 5%;  /* For round buttons 50% is hard coded */\n--borderscale: 0.03; /* Proportion of widget height for border (0 - 0.1) */\n--bordercolor: #888; /* Border color */\n\n--inactiveiconcolor: red; \n--inactivebuttoncolor: whitesmoke; \n\n--activeiconcolor: hsl(60, 65%, 90%); \n--activebuttoncolor: hsl(60, 65%, 90%);\n\n--nulliconcolor: whitesmoke; /* other statuses */\n--nullbuttoncolor: whitesmoke;\n\n    --inactiveiconcolor: hsl(0, 100%, 50%);      /* Using red for off */\n    --inactivebuttoncolor: hsl(0, 65%, 70%);     /* red but duller */\n\n    --activeiconcolor: hsl(125, 100%, 50%);     /* green for on */\n    --activebuttoncolor: hsl(125, 65%, 70%);\n\n    --inactiveiconcolor: whitesmoke;            /* other statuses */\n    --inactivebuttoncolor: whitesmoke;\n}\n@keyframes spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n/* Align buttons inside widget */\n.nrdb-ui-button.mybutton {\n    display: flex !important;\n    align-items: var(--vertical);\n    justify-content: var(--horizontal);\n}\n\n.nrdb-ui-button.mybutton button {\n    background-color: white;\n    border: calc(var(--borderscale) * var(--widget-row-height)) solid var(--bordercolor); \n}\n.nrdb-ui-button.mybutton.square button {\n    border-radius: var(--borderradius) ;  \n    border: calc(var(--borderscale) * var(--widget-row-height)) solid var(--bordercolor);    \n}\n.nrdb-ui-button.mybutton.round button {\n    border-radius: 50% ;\n    border: calc(var(--borderscale) * var(--widget-row-height)) solid var(--bordercolor);\n}\n\n/* Widget height for 1x1 buttons */\n.nrdb-ui-button.mybutton, .nrdb-ui-button.mybutton.small {\n    max-height: var(--widget-row-height);\n    min-height: var(--widget-row-height);\n}\n/* 1x1 button size */\n.nrdb-ui-button.mybutton.small button {\n    max-height: calc(var(--heightfraction) * var(--widget-row-height));\n    max-width: calc(var(--heightfraction)  * var(--widget-row-height));\n    min-height: calc(var(--heightfraction)  * var(--widget-row-height));\n    min-width: calc(var(--heightfraction)  * var(--widget-row-height));\n    font-size: calc(var(--heightfraction) * var(--fontscale) * var(--widget-row-height));\n    box-shadow: inset rgba(20, 70, 85, 0.8) 0px 0px 5px -3px;\n}\n\n/* Widget height for 2x2 buttons */\n.nrdb-ui-button.mybutton.large {\n    max-height: calc(2 * var(--widget-row-height) + var(--widget-gap)) ;\n    min-height: calc(2 * var(--widget-row-height) + var(--widget-gap));\n}\n/* 2x2 button size */\n.nrdb-ui-button.mybutton.large button {\n    max-height: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    max-width: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    min-height: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    min-width: calc(var(--heightfraction) * 2 * var(--widget-row-height) + var(--widget-gap));\n    font-size: calc(2 * var(--heightfraction) * var(--fontscale) * var(--widget-row-height) + var(--widget-gap));\n    box-shadow: inset rgba(20, 70, 85, 0.8) 0px 0px 10px -5px;\n}\n\n/* no set value - outline icon */\n.nrdb-ui-button.mybutton button i {\n    background-color: transparent;\n    color: whitesmoke;\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}\n\n/* Apply background style for background buttons */\n.nrdb-ui-button.mybutton.background.active button {\n\tbackground-color: var(--activebuttoncolor);\n    transition: none;\n}\n.nrdb-ui-button.mybutton.background.inactive button {\n\tbackground-color: var(--inactivebuttoncolor);\n    transform: rotate(180deg);\n    transition: none;\n}\n.nrdb-ui-button.mybutton.background.null button {\n\tbackground-color: var(--nullbuttoncolor);\n}\n\n/* Apply foreground style for Icon buttons */\n.nrdb-ui-button.mybutton.icon.active button i {/* NB animation here different from default */\n    color: var(--activeiconcolor);\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n    animation: spin 2s linear infinite; /* class icon must be explicit */\n}\n.nrdb-ui-button.mybutton.icon.inactive button i {\n    color: var(--inactiveiconcolor);\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}\n.nrdb-ui-button.mybutton.icon.null button i {\n    color: var(--nulliconcolor);\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}\n/******/\n.nrdb-ui-button.mybutton.icon.active button {\n    transition: none;\n}\n.nrdb-ui-button.mybutton.icon.inactive button {\n    transform: rotate(180deg);\n    transition: none;\n}\n.nrdb-ui-button.mybutton.icon.null button {\n    transition: none;\n}\n/********************/\n\n/* Neither icon nor background specified - apply both*/\n.nrdb-ui-button.mybutton:not(.icon):not(.background).active button {\n    background-color: var(--activebuttoncolor);\n    transition: none;\n}\n.nrdb-ui-button.mybutton:not(.icon):not(.background).inactive button {\n    background-color: var(--inactivebuttoncolor);\n    transform: rotate(180deg);\n    transition: none;\n}\n.nrdb-ui-button.mybutton:not(.icon):not(.background).null button {\n    background-color: var(--nullbuttoncolor);\n}\n.nrdb-ui-button.mybutton:not(.icon):not(.background).active button i { \n    color: var(--activeiconcolor);\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}\n.nrdb-ui-button.mybutton:not(.icon):not(.background).inactive button i {\n    color: var(--inactiveiconcolor);\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}\n.nrdb-ui-button.mybutton:not(.icon):not(.background).null button i {\n    color: var(--nulliconcolor);\n    text-shadow: -1px -1px 1px #444, 1px -1px 1px #444, -1px 1px 1px #444, 1px 1px 1px #444;\n}","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"page:style","className":"","x":135,"y":30,"wires":[[]]},{"id":"6dbb6470bc8a6f62","type":"ui-button","z":"cdc8d1d1591c532f","group":"6f0334dfeaf33c65","name":"switch","label":"","order":1,"width":"2","height":"2","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"mybutton large background","icon":"toggle-switch-variant-off","iconPosition":"left","payload":"stop","payloadType":"str","topic":"","topicType":"str","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":400,"y":75,"wires":[["0d9ff70d29d7ff7f"]]},{"id":"0d9ff70d29d7ff7f","type":"function","z":"cdc8d1d1591c532f","name":"Toggle","func":"if (msg.payload == \"reset\") context.set (\"buttonstate\", \"active\")\n    \nlet state = context.get(\"buttonstate\") || \"active\" \nif (state === \"active\") {\n    state = \"inactive\"\n}\nelse {\n    state = \"active\"\n}\ncontext.set (\"buttonstate\", state)\nmsg.class = state\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":245,"y":75,"wires":[["6dbb6470bc8a6f62"]]},{"id":"49b5aa59deda8a86","type":"ui-button","z":"cdc8d1d1591c532f","group":"6f0334dfeaf33c65","name":"fan","label":"","order":2,"width":"2","height":"2","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"mybutton large icon","icon":"fan","iconPosition":"left","payload":"stop","payloadType":"str","topic":"","topicType":"str","buttonColor":"","textColor":"","iconColor":"transform: rotate (180deg)","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":405,"y":120,"wires":[["aaa02a26c3dd0fe0"]]},{"id":"aaa02a26c3dd0fe0","type":"function","z":"cdc8d1d1591c532f","name":"Toggle","func":"let state = context.get(\"buttonstate\") || \"active\" \nif (state === \"active\") {\n    state = \"inactive\"\n}\nelse {\n    state = \"active\"\n}\ncontext.set (\"buttonstate\", state)\nmsg.class = state\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":245,"y":120,"wires":[["49b5aa59deda8a86"]]},{"id":"b588b5e9c0b10838","type":"inject","z":"cdc8d1d1591c532f","name":"init","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"reset","payloadType":"str","x":95,"y":105,"wires":[["0d9ff70d29d7ff7f","aaa02a26c3dd0fe0"]]},{"id":"004f1baddf79d52e","type":"ui-page","name":"CSS Rotation Buttons","ui":"e9c974f7c1d080d1","path":"/buttons4","icon":"home","layout":"grid","theme":"11edfaf96d0baec1","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":7,"className":"","visible":"true","disabled":"false"},{"id":"6f0334dfeaf33c65","type":"ui-group","name":"Using CSS transform","page":"004f1baddf79d52e","width":"6","height":1,"order":1,"showTitle":false,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"e9c974f7c1d080d1","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":true},{"id":"11edfaf96d0baec1","type":"ui-theme","name":"Dark1","colors":{"surface":"#424242","primary":"#0094ce","bgPage":"#424242","groupBg":"#424242","groupOutline":"#424242"},"sizes":{"density":"default","pagePadding":"2px","groupGap":"3px","groupBorderRadius":"4px","widgetGap":"10px"}},{"id":"83049f6e1c7f558b","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.29.0"}}]
1 Like