The colour change on payload is pretty similar to toggle. Currently used inject nodes can be replaced with any source, just the payload and topic must respect the controller logic.
With that same example and slight change in CSS (removed the !important from remote-button)
[{"id":"bb9bb4b4.35ea68","type":"ui_template","z":"ce26ccc2.6674d","group":"2a7130fc.d53a2","name":"CSS only ","order":7,"width":0,"height":0,"format":"<style>\n :root {\n --dashboard-unit-width: 48px;\n --dashboard-unit-height: 48px;\n --remote-button-background: black;\n --remote-button-foreground: #cccccc;\n }\n .nr-dashboard-template {\n padding: 0px;\n }\n \n .remote-button.disabled{\n cursor: not-allowed;\n pointer-events: none;\n color: #aaaaaa !important;\n }\n \n .remote-button:not([disabled]):hover{\n background-color: #232323 !important;\n }\n\n /* This is the normal button definition */\n .remote-button{\n background-color: var(--remote-button-background);\n color: var(--remote-button-foreground);\n height: var(--dashboard-unit-height);\n width: 100%;\n border-radius: 10px;\n font-size:1.0em;\n font-weight:normal;\n margin: 0;\n min-height: 36px;\n min-width: unset;\n line-height: unset;\n }\n /* This is a sub-set which is invoked by */\n /* <md-button class=\"md-button remote-button bigger\"> */\n /* note the (space) \"bigger\" at the end. */\n .remote-button.bigger{\n font-weight:bold;\n font-size:1.5em;\n }\n /* This is for buttons with a lot of text. `font-size:0.7em` */\n /* makes the font 70% normal size */\n .remote-button.small{\n font-size:0.7em;\n }\n /* This is for buttons with just icons, to upsize the size */\n /* of the icon with the line: */\n /* <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node */\n .remote-icon{\n font-size:2.0em;\n }\n /* This is the same as the other one, but it makes the icon smaller */\n .remote-iconS{\n font-size:0.5em;\n }\n\n .remote-button.black{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground) !important;\n }\n\n .remote-button.red{\n background-color: red !important;\n color: var(--remote-button-foreground) !important;\n }\n .remote-button.red:not([disabled]):hover{\n background-color: orange !important;\n }\n\n .remote-button.blinking{\n \tanimation:blinkingAnim1 0.8s infinite;\n }\n @keyframes blinkingAnim1{\n \t0%{\t\tbackground-color: var(--remote-button-background); color:var(--remote-button-foreground);}\n \t50%{\tbackground-color: var(--remote-button-foreground); color:var(--remote-button-background);}\n \t100%{\tbackground-color: var(--remote-button-background);\tcolor:var(--remote-button-foreground);}\n } \n \n .remote-button.red.blinking{\n \tanimation:blinkingAnim2 0.8s infinite;\n }\n @keyframes blinkingAnim2{\n \t0%{\t\tbackground-color: red; color:#cccccc;}\n \t50%{\tbackground-color: #cccccc; color:red;}\n \t100%{\tbackground-color: red;\tcolor:#cccccc;}\n } \n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":480,"y":360,"wires":[[]]},{"id":"39b5b695.3e656a","type":"ui_template","z":"ce26ccc2.6674d","group":"2a7130fc.d53a2","name":"script for all buttons with class remote-button","order":27,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n //cause a small delay while things load \n //ideally this would be an init event or on all parts of document loaded\n //(may not be necessary!)\n setTimeout(function() {\n //debugger\n $scope.init();\n },100);\n \n var BUTTON_CLASS = \".remote-button\";\n\n /** \n * Initialise all buttons with class BUTTON_CLASS\n */\n $scope.init = function () {\n //debugger\n console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n var clickButtons = $(BUTTON_CLASS + \":not([data-buttontype='repeater'])\") \n clickButtons.click(function(e){\n //debugger\n var btn = $(this)\n var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n \n if(type == \"toggle\"){\n var newPayload = payload == 1 ? 0 : 1;\n setButtonState(btn,newPayload)\n $scope.send({\"topic\":topic,\"payload\":newPayload, \"event\": \"click\"})\n } else {\n $scope.send({\"topic\":topic,\"payload\":payload})\n }\n }); \n \n\n var repeatButtons = $(BUTTON_CLASS + \"[data-buttontype='repeater']\") \n repeatButtons.on('mousedown touchstart',function(e) {\n e.preventDefault();\n e.stopPropagation();\n console.log(e.type);//remove me after debugging\n var btn = this;\n var $btn = $(btn);\n if(btn._intervalId) return; //already in operation\n btn._topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n btn._payload = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n btn._interval = $btn.data(\"interval\");//get the desired repeat duration\n if(isNumeric(btn._interval) == false || btn._interval < 1) btn._interval = 300;//prevent zero & non numeric timeout value\n var max = $btn.data(\"max\");//get the max repeat count\n if(isNumeric(max) && max > 0) \n btn._max = parseInt(max);\n else\n btn._max = undefined;\n btn._count = 1;\n $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n btn._lastevent = \"down\";\n //remove any existing timer\n if(btn._intervalId){\n clearInterval(btn._intervalId);\n btn._intervalId = null;\n }\n //start the timer if conditions are ok\n if(!btn._max || (btn._max && btn._count < btn._max)){\n btn._intervalId = setInterval(function() {\n if(btn._max && btn._count >= btn._max){\n clearInterval(btn._intervalId);\n btn._intervalId = null;\n return;\n } \n btn._count = btn._count + 1;\n $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n },btn._interval); \n }\n }).on('mouseup blur focusout mouseout touchend',function(e) {\n e.preventDefault();\n e.stopPropagation();\n console.log(e.type);//remove me after debugging\n var btn = this;\n var $btn = $(btn);\n \n //next, if data-fireonup == true, then fire up event\n var sendUpEvent = $btn.data(\"fireonup\") == true;\n if(sendUpEvent && btn._lastevent === \"down\"){\n btn._lastevent = \"up\"\n $scope.send({\"topic\":btn._topic,\"payload\":btn._payload,\"event\":\"up\"})\n }\n \n //finally, if the timer id is still valid, clear it (stop repeater)\n if(btn._intervalId){\n clearInterval(btn._intervalId);//stop timer\n btn._intervalId = null;//clear timer id\n }\n });\n \n };\n \n //watch for node-red msgs\n $scope.$watch('msg', function(msg) {\n //debugger\n if(!msg){ //if no msg \n console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n return;\n }\n if(!msg.topic){ //if no topic set found\n console.log(\"msg.topic is empty - cannot match this to any button\")\n return; //stop processing!\n }\n\n var buttonSelector = BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n var $btn = $(buttonSelector);//get the button\n \n if(!$btn.length){ //if no button found\n console.log(buttonSelector + \" not found - cannot set state\")\n return; //stop processing!\n }\n \n if($btn.length > 1){ //if MORE than one button found\n console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n }\n \n //see if this is a command - if so, process the command\n if(typeof msg.payload === \"object\" && msg.payload.command){\n processCommand($btn, msg.payload)\n return;\n } \n \n if($btn.data(\"buttontype\") === \"toggle\"){\n if(msg.payload == \"1\"){\n setButtonState($btn, 1);\n } else if(msg.payload == \"0\"){\n setButtonState($btn, 0);\n } else {\n console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n }\n }\n\n }); \n \n /** \n * helper function to set the correct icon & update the \"data-payload\" memory \n */\n function setButtonState($btn, state){\n \n $btn.data(\"payload\", state);//set data-payload to new state value (used as a memory)\n \n //determine the opposite state\n var oppositeState;\n if(state == \"1\" || state === 1){\n state = 1; //normalise to a number\n oppositeState = 0;\n } else {\n state = 0; //normalise to a number\n oppositeState = 1;\n }\n \n var $icon = $btn.find(\"i\"); //get the <i> element\n if(!$icon.length){\n $icon = $btn.find(\"span\"); //get the <span> element instead!\n }\n if(!$icon.length){\n console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n return;//exit this function - nothing to toggle!\n }\n \n //get the old icon and new icon names\n var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n \n //if we have newIcon and an actual DOM element ($icon) - update it...\n if(newIcon && $icon.length){\n if(newIcon.includes(\"fa-\")){ \n $icon.removeClass(oldIcon).addClass(newIcon); // fontawesome\n } else { \n $icon.text(newIcon); // MDI\n }\n }\n }\n \n function processCommand($btn, payload){\n //first check payload is correct format...\n if(!payload || !payload.command || !payload.value){\n console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n }\n var cmd = payload.command.trim();\n switch(cmd){\n case \"addClass\":\n $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"toggleClass\":\n $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"removeClass\":\n $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n default:\n console.log(\"command '\" + payload.command + \"' is not supported\")\n }\n } \n \n /** \n * helper function to determine a value is REALLY a number \n */\n function isNumeric(n){\n if(n === \"\") return false;\n if(n === true || n === false) return false;\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n \n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":570,"y":540,"wires":[["efa774db.22c848"]]},{"id":"efa774db.22c848","type":"debug","z":"ce26ccc2.6674d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":810,"y":540,"wires":[]},{"id":"a7dda2e3.f6399","type":"ui_template","z":"ce26ccc2.6674d","group":"2a7130fc.d53a2","name":"lock","order":23,"width":"1","height":"1","format":"<div>\n <md-button class=\"md-button remote-button bigger\" \n data-payload=\"0\" \n data-buttontype=\"toggle\"\n data-topic=\"lock\"\n data-icon0=\"fa-unlock\"\n data-icon1=\"fa-lock\"\n aria-label=\"lock\"\n >\n <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":470,"y":420,"wires":[[]]},{"id":"cdcdf10b.a0385","type":"inject","z":"ce26ccc2.6674d","name":"toggleClass blinking, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":220,"y":500,"wires":[["39b5b695.3e656a"]]},{"id":"9ee376a6.930f58","type":"inject","z":"ce26ccc2.6674d","name":"addClass red, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"addClass\",\"value\":\"red\"}","payloadType":"json","x":240,"y":540,"wires":[["39b5b695.3e656a"]]},{"id":"fde928e1.9d7358","type":"inject","z":"ce26ccc2.6674d","name":"removeClass red, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"removeClass\",\"value\":\"red\"}","payloadType":"json","x":250,"y":580,"wires":[["39b5b695.3e656a"]]},{"id":"feedb55e.f4d598","type":"inject","z":"ce26ccc2.6674d","name":"addClass disabled, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"addClass\",\"value\":\"disabled\"}","payloadType":"json","x":250,"y":620,"wires":[["39b5b695.3e656a"]]},{"id":"16ec8c00.3ba3b4","type":"inject","z":"ce26ccc2.6674d","name":"removeClass disabled, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"removeClass\",\"value\":\"disabled\"}","payloadType":"json","x":260,"y":660,"wires":[["39b5b695.3e656a"]]},{"id":"2a7130fc.d53a2","type":"ui_group","z":"","name":"Full_Remote2","tab":"c8123658.c35e48","order":3,"disp":false,"width":"3","collapse":false},{"id":"c8123658.c35e48","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]