# Looking for a countdown clock

**URL:** <https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942>\
**Category:** General\
**Created:** [8 May 2026 08:18 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942 "2026-05-08T08:18:53Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [8 May 2026 08:18 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/1 "2026-05-08T08:18:53Z")

</div>

I'm looking for a digital countdown clock that displays Hours, Minutes and Seconds. I will need to be able to preset the countdown time for up to 12 Hours. I've been able to get the Digital Clock to count down using the epoch timestamp and multiplying by -1 but I haven't yet figured out how to preset a time like say 6:00:00 and have it countdown to 00:00:00 so I can shutdown a system after the preset time. What's confusing me is how to calculate what number I need to enter for a preset time and then get it to actually countdown. I've tried setting the countdown increment to 1000 but I can't get it to work.

Thanks in advance.

---

<div class="post-metadata">

**Author:** ![gregorius](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gregorius/32/73816_2.png) [@gregorius](https://discourse.nodered.org/u/gregorius)\
**Post date:** [8 May 2026 08:54 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/2 "2026-05-08T08:54:38Z")

</div>

> [@Technogeek](#):
>
> Digital Clock

Which digital clock? Do you have a link to a node package that provides the digital clock - sorry for asking the obvious if everyone else knows which clock is meant!

> [@Technogeek](#):
>
> count down using the epoch timestamp and multiplying by -1

The digital clock is smart enough to know to go backwards if value is negative? For me the series of values you are sending to the clock would be (each second and epoch is second not millisecond):

```auto
epoch * -1
(epoch+1) * -1
(epoch+2) * -1
...

```

I assume you send something to the clock at a specific time interval (e.g. 1 second) and you send the clock a value that it should be displaying.

Also `epoch` here represents the timestamp when you begin sending messages.

Or do you store the original epoch value, i.e. first epoch value and subtract from that so that the series becomes:

```auto
epoch * -1
(epoch-1) * -1
(epoch-2) * -1
...

```

In this second case, won't it be a matter of sending 6 \* 60 \* 60 as initial value and the subtracting one from that value until the final value is zero?

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 May 2026 09:32 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/3 "2026-05-08T09:32:24Z")

</div>

> [@Technogeek](#):
>
> a digital countdown clock that displays Hours, Minutes and Seconds

How/where are you displaying this? In a browser? If so, which tool are you using to display it - Dashboard-2 or UIBUILDER? The answers will likely change the advice given.

---

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [8 May 2026 16:28 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/4 "2026-05-08T16:28:28Z")

</div>

My apologies for not supplying that info. I'm working with the node-red-contrib-ui-digital-clock node. Now this is likely not meant to work in countdown mode and I'm quite brutal at code writing so that's why I'm trying to leverage this node.

---

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [8 May 2026 16:29 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/5 "2026-05-08T16:29:25Z")

</div>

Currently I'm using Dashboard2 but I'm open to anything.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 May 2026 16:51 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/6 "2026-05-08T16:51:23Z")

</div>

Let me see if I can knock something together as a fun excercise.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 May 2026 16:58 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/7 "2026-05-08T16:58:56Z")

</div>

Here you go. 😃

Doesn't yet have control from Node-RED but that is easy to add.

I'm afraid that you will need to install node-red-contrib-uibuilder and then restart node-red before using this flow.

```flows
[{"id":"fa0240a25588cf67","type":"group","z":"b2f18a716bd20f99","name":"Countdown timer demo","style":{"label":true},"nodes":["e51ba7ef3e333151","a86aea213a59fe7d","2d170e4ebc0b7f1f","fa56099adb3acf7e","3dc0031905a11e96","7fc5dc8d632aca16","b0661e7aef09d16f","24be6cd926d1deb0"],"x":88,"y":1459,"w":648,"h":328},{"id":"e51ba7ef3e333151","type":"debug","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"debug 7","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":655,"y":1500,"wires":[],"l":false},{"id":"a86aea213a59fe7d","type":"uibuilder","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"","topic":"","url":"becker","instancePath":"","okToGo":true,"fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"blank","extTemplate":"","showfolder":false,"reload":true,"sourceFolder":"src","deployedVersion":"7.7.0","showMsgUib":false,"title":"","descr":"","editurl":"vscode://file/D:/src\\uibRoot/becker/?windowId=_blank","x":300,"y":1540,"wires":[["2d170e4ebc0b7f1f"],[]]},{"id":"2d170e4ebc0b7f1f","type":"switch","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"query","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":450,"y":1520,"wires":[["e51ba7ef3e333151","3dc0031905a11e96"],["fa56099adb3acf7e"]]},{"id":"fa56099adb3acf7e","type":"debug","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"debug 33","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":565,"y":1560,"wires":[],"l":false},{"id":"3dc0031905a11e96","type":"link out","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"link out 62","mode":"link","links":["7fc5dc8d632aca16"],"x":695,"y":1540,"wires":[]},{"id":"7fc5dc8d632aca16","type":"link in","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"link in 19","links":["3dc0031905a11e96"],"x":135,"y":1540,"wires":[["b0661e7aef09d16f"]]},{"id":"b0661e7aef09d16f","type":"change","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"RESPONSE","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":195,"y":1540,"wires":[["a86aea213a59fe7d"]],"l":false},{"id":"24be6cd926d1deb0","type":"group","z":"b2f18a716bd20f99","g":"fa0240a25588cf67","name":"RUN ONCE: Initialise Front-end Code - Run after the uibuilder node has been deployed. \\n REMEMBER to change the uib node name before use \\n Sets up FE code, reloads connected clients. \\n ","style":{"label":true,"stroke":"#a4a4a4","fill-opacity":"0.33","color":"#000000","fill":"#ffffff"},"nodes":["54afea71bcf96374","c1d30ee72e72cbb0","c92d83bda675832c","5bcce172317b7bfd","34ff3e8e97de6fd5","5763fbb206b384ed"],"x":114,"y":1591,"w":554,"h":170},{"id":"54afea71bcf96374","type":"inject","z":"b2f18a716bd20f99","g":"24be6cd926d1deb0","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"setup all FE files","x":175,"y":1680,"wires":[["34ff3e8e97de6fd5","5763fbb206b384ed"]],"l":false},{"id":"c1d30ee72e72cbb0","type":"template","z":"b2f18a716bd20f99","g":"24be6cd926d1deb0","name":"index.html","field":"payload","fieldType":"msg","format":"html","syntax":"plain","template":"<!doctype html>\n<html lang=\"en\"><head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <link rel=\"icon\" href=\"../uibuilder/images/uib-world.svg\" type=\"image/svg+xml\">\n\n <title>Countdown Timer - Node-RED UIBUILDER</title>\n <meta name=\"description\" content=\"Node-RED UIBUILDER - Countdown Timer\">\n\n <!-- Your own CSS (defaults to loading uibuilders css)-->\n <link type=\"text/css\" rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n\n <style>\n #timer {\n font-size: 5rem;\n font-weight: bold;\n font-variant-numeric: tabular-nums;\n letter-spacing: 4px;\n }\n\n .labels {\n display: flex;\n justify-content: center;\n gap: 2.5rem;\n margin-top: -10px;\n color: #888;\n text-transform: uppercase;\n font-size: 0.8rem;\n }\n </style>\n\n <script type=\"module\" async src=\"./index.js\"></script>\n</head><body>\n <h1 class=\"with-subtitle\">Countdown Timer</h1>\n <div role=\"doc-subtitle\">Using UIBUILDER for Node-RED</div>\n \n <!-- '#more' is used as a parent for dynamic HTML content in examples\n Also, send {topic:\"more\", payload:\"Hello from <b>Node-RED</b>\"} to auto-display the payload -->\n <div id=\"more\" uib-topic=\"more\"></div>\n\n <article class='container'>\n <h2>COUNTDOWN</h2>\n <div id='timer'>00:00:00</div>\n <div class='labels'>\n <span>Hours</span>\n <span>Mins</span>\n <span>Secs</span>\n </div>\n </article>\n\n</body></html>\n","output":"str","x":380,"y":1680,"wires":[["c92d83bda675832c"]]},{"id":"c92d83bda675832c","type":"uib-save","z":"b2f18a716bd20f99","g":"24be6cd926d1deb0","url":"becker","uibId":"a86aea213a59fe7d","folder":"src","fname":"","createFolder":false,"reload":true,"usePageName":false,"encoding":"utf8","mode":438,"name":"","topic":"","x":550,"y":1680,"wires":[]},{"id":"5bcce172317b7bfd","type":"template","z":"b2f18a716bd20f99","g":"24be6cd926d1deb0","name":"index.js","field":"payload","fieldType":"msg","format":"javascript","syntax":"plain","template":"// Give VS Code IntelliSense for uibuilder\n/// <reference path=\"../types/uibuilder.d.ts\" />\n\n// @ts-ignore\nimport { uibuilder } from '../uibuilder/uibuilder.esm.min.js'\n\n// Set the initial countdown time in seconds (Max 43200 for 12 hours)\nlet timeLeft = 3600 // 1 hour\n\nconst timerDisplay = document.getElementById('timer')\n\nconst updateTimer = () => {\n if (timeLeft <= 0) {\n clearInterval(countdownInterval)\n timerDisplay.textContent = '00:00:00'\n timerDisplay.style.color = '#ff4b2b'\n return\n }\n\n const hours = Math.floor(timeLeft / 3600)\n const minutes = Math.floor((timeLeft % 3600) / 60)\n const seconds = timeLeft % 60\n\n // Format numbers to always show two digits\n const displayHours = String(hours).padStart(2, '0')\n const displayMinutes = String(minutes).padStart(2, '0')\n const displaySeconds = String(seconds).padStart(2, '0')\n\n timerDisplay.textContent = `${displayHours}:${displayMinutes}:${displaySeconds}`\n \n timeLeft--\n}\n\n// Run the timer immediately on load, then every second\nupdateTimer()\nconst countdownInterval = setInterval(updateTimer, 1000)\n","output":"str","x":390,"y":1720,"wires":[["c92d83bda675832c"]]},{"id":"34ff3e8e97de6fd5","type":"change","z":"b2f18a716bd20f99","g":"24be6cd926d1deb0","name":"index.html","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.html","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":265,"y":1680,"wires":[["c1d30ee72e72cbb0"]],"l":false},{"id":"5763fbb206b384ed","type":"change","z":"b2f18a716bd20f99","g":"24be6cd926d1deb0","name":"index.js","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.js","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":265,"y":1720,"wires":[["5bcce172317b7bfd"]],"l":false},{"id":"db5d34e28a4ae831","type":"global-config","env":[],"modules":{"node-red-contrib-uibuilder":"7.7.0"}}]

```

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/3/a/3a3d14c4901257e094001ef75fd4baa9e3e85606.png)

Oh, and you can add this into the `index.js` to be able to update the timer from Node-RED:

```javascript
uibuilder.onTopic('setTime', (msg) => {
    const newTime = parseInt(msg.payload, 10)
    if (!isNaN(newTime) && newTime >= 0 && newTime <= 43200) {
        timeLeft = newTime
        updateTimer() // Update immediately when time is set
    } else {
        console.warn('Invalid time received. Please send a number between 0 and 43200.')
    }
})

```

And add this to the end of the `updateTimer` function to get an alert in Node-RED when the timer is expiring:

```javascript
    if (timeLeft <= 1000) {
        uibuilder.send({ topic: 'timeout', payload: timeLeft })
    }

```

---

<div class="post-metadata">

**Author:** ![gregorius](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gregorius/32/73816_2.png) [@gregorius](https://discourse.nodered.org/u/gregorius)\
**Post date:** [8 May 2026 16:59 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/8 "2026-05-08T16:59:27Z")

</div>

> [@Technogeek](#):
>
> node-red-contrib-ui-digital-clock

Having a keep look at the code, it does seem to take a [millisecond value](https://github.com/patrickknabe/node-red-contrib-ui-digital-clock/blob/76ac9788e5bef223e00e1228f71e272268437f75/clock.html#L66-L69):

> ```
> The time to be displayed is transferred as timestamp (milliseconds since 01.01.1970) in
> <code>msg.payload</code>. The local time is displayed in the dashboard.
> 
> ```

So generating a storing a value in the context of the node could work:

```flows
[{"id":"da0f9a3098c5a96b","type":"inject","z":"b06b5fcc3ecc1bac","name":"","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":585.0000228881836,"y":395.7142915725708,"wires":[["6cb2380794d52e8e"]]},{"id":"6cb2380794d52e8e","type":"function","z":"b06b5fcc3ecc1bac","name":"function 1","func":"if ( context.get(\"timer\")) {\n let v = context.get(\"timer\") \n msg.payload = v - 1000\n context.set(\"timer\", msg.payload)\n} else {\n msg.payload = 60 * 6 * 60 * 1000\n context.set(\"timer\", msg.payload)\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750.0000190734863,"y":538.5714836120605,"wires":[["3446616efbce26e5"]]},{"id":"3446616efbce26e5","type":"ui_digital_clock","z":"b06b5fcc3ecc1bac","name":"","group":"a49a51e326c4bbe1","order":2,"width":0,"height":0,"x":967.8572082519531,"y":610.0000400543213,"wires":[]},{"id":"a49a51e326c4bbe1","type":"ui_group","name":"Cachemap","tab":"2e11dca0bed4eaa2","order":1,"disp":true,"width":"12","collapse":false,"className":""},{"id":"2e11dca0bed4eaa2","type":"ui_tab","name":"GeoCache","icon":"dashboard","order":9,"disabled":false,"hidden":false}]

```

try that, it does the epoch - 1000 each second and starts with 6 \* 60 \* 60 \* 1000 \<--- that might be wrong though.

---

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [8 May 2026 17:05 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/9 "2026-05-08T17:05:08Z")

</div>

So this works but I can't seem to figure out the formula to use to calculate the times. If I use 39600 I get 5 Hours but if I use 43200 I get 6 hours. 39600/5/60/60 = 2.2 but 43200/6/60/60 = 2 and so I'm not sure why the dividend changes.

[flows.json](https://discourse.nodered.org/uploads/short-url/hTpfFF9ZKSyXwILRbT4Alp9aHyL.json) (6.8 KB)

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 May 2026 17:08 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/10 "2026-05-08T17:08:19Z")

</div>

The timestamp is in JavaScript Date milliseconds. 1000 = 1 second, 60,000 = 1 minute, 3,600,000=1000_60_60= 1 hour. So 12 hours would be 43,200,000.

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [8 May 2026 17:11 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/11 "2026-05-08T17:11:57Z")

</div>

For dashboard 2 (no extra contrib nodes - just 1 ui-template). Its not perfect but it should give you a head start.

 ![Screenshot 2026-05-08 17.59.35](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/d/a/dad93d6f669f2099560fa8f65a6b9803ba51a944.png)  
 ![Screenshot 2026-05-08 17.59.48](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/7/7/778c75fd7477b5a6469145a8bc9a34d6c061901c.png)  
 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/6/16f708b4f0e49b656d282f712bf2add8ecb4b466.png)

```auto
[
    {
        "id": "5deb7ffd584f9748",
        "type": "ui-template",
        "z": "648fda2a9b3e2104",
        "group": "group1",
        "page": "",
        "ui": "",
        "name": "Countdown Timer",
        "order": 1,
        "width": 6,
        "height": 3,
        "head": "",
        "format": "<template>\n <v-card class=\"pa-2\" elevation=\"2\" style=\"background-color: #000; color: #0f0; font-family: 'DS-Digital', monospace;\">\n <v-row align=\"center\" justify=\"space-between\">\n <v-col cols=\"8\" class=\"d-flex justify-center\">\n <div :class=\"['countdown', { 'alert': alert }]\">\n {{ displayTime }}\n </div>\n </v-col>\n <v-col cols=\"4\" class=\"d-flex justify-end\">\n <v-btn icon @click=\"showSettings = true\">\n <v-icon>mdi-cog</v-icon>\n </v-btn>\n <v-btn icon @click=\"startTimer\">\n <v-icon>mdi-play</v-icon>\n </v-btn>\n <v-btn icon @click=\"stopTimer\">\n <v-icon>mdi-stop</v-icon>\n </v-btn>\n <v-btn icon @click=\"resetTimer\">\n <v-icon>mdi-replay</v-icon>\n </v-btn>\n </v-col>\n </v-row>\n\n <v-dialog v-model=\"showSettings\" max-width=\"400\">\n <v-card>\n <v-card-title>Set Duration</v-card-title>\n <v-card-text>\n <v-text-field\n label=\"Duration (HH:mm:ss)\"\n v-model=\"inputTime\"\n :rules=\"[timeRule]\"\n maxlength=\"8\"\n counter\n ></v-text-field>\n </v-card-text>\n <v-card-actions>\n <v-spacer></v-spacer>\n <v-btn text @click=\"showSettings = false\">Cancel</v-btn>\n <v-btn text @click=\"applySettings\" :disabled=\"!validTime\">Apply</v-btn>\n </v-card-actions>\n </v-card>\n </v-dialog>\n </v-card>\n</template>\n\n<script>\nexport default {\n data() {\n return {\n duration: 0, // in seconds\n remaining: 0, // in seconds\n timer: null,\n running: false,\n alert: false,\n showSettings: false,\n inputTime: '00:00:00',\n }\n },\n computed: {\n displayTime() {\n const absRemaining = Math.abs(this.remaining)\n const h = Math.floor(absRemaining / 3600).toString().padStart(2, '0')\n const m = Math.floor((absRemaining % 3600) / 60).toString().padStart(2, '0')\n const s = (absRemaining % 60).toString().padStart(2, '0')\n const sign = this.remaining < 0 ? '-' : ''\n return `${sign}${h}:${m}:${s}`\n },\n validTime() {\n return /^([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)$/.test(this.inputTime)\n }\n },\n watch: {\n inputTime(val) {\n if (this.validTime) {\n this.$refs.inputField?.setError(false)\n }\n }\n },\n methods: {\n startTimer() {\n if (!this.running) {\n this.send({topic: 'start', payload: this.remaining})\n this.running = true\n this.timer = setInterval(() => {\n this.remaining--\n if (this.remaining === 0) {\n this.send({topic: 'complete', payload: 0})\n } else if (this.remaining < 0) {\n this.alert = true\n }\n }, 1000)\n }\n },\n stopTimer() {\n const report = this.running\n this.running = false\n clearInterval(this.timer)\n if (report) {\n this.send({topic: 'stop', payload: this.remaining})\n }\n this.timer = null\n },\n resetTimer() {\n this.stopTimer()\n this.remaining = this.duration\n this.send({topic: 'reset', payload: this.remaining})\n this.alert = false\n },\n applySettings() {\n const parts = this.inputTime.split(':')\n this.duration = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2])\n this.remaining = this.duration\n this.send({topic: 'set', payload: this.remaining})\n this.alert = false\n this.showSettings = false\n this.stopTimer()\n },\n timeRule(value) {\n return this.validTime || 'Invalid time format'\n },\n handleInput(msg) {\n switch (msg.topic) {\n case 'start':\n this.startTimer()\n break\n case 'pause':\n this.stopTimer()\n break\n case 'stop':\n this.resetTimer()\n break\n case 'reset':\n this.resetTimer()\n break\n case 'set':\n if (typeof msg.payload === 'number') {\n this.duration = Math.floor(msg.payload / 1000)\n this.remaining = this.duration\n this.alert = false\n this.stopTimer()\n this.send({topic: 'set', payload: this.remaining})\n }\n break\n }\n }\n },\n mounted() {\n this.$watch('msg', (msg) => {\n if (msg) {\n this.handleInput(msg)\n }\n })\n },\n beforeUnmount() {\n this.stopTimer()\n }\n}\n</script>\n\n<style scoped>\n@import url('https://fonts.googleapis.com/css2?family=DS-Digital&display=swap');\n.countdown {\n font-size: 3rem;\n letter-spacing: 0.2rem;\n user-select: none;\n transition: color 0.5s ease;\n}\n.countdown.alert {\n color: red;\n animation: blink 1s infinite;\n}\n@keyframes blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0; }\n}\n</style>",
        "storeOutMessages": true,
        "passthru": false,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 340,
        "y": 200,
        "wires": [
            [
                "5c498c5cc67e64aa"
            ]
        ]
    },
    {
        "id": "18ca3e6f93f4591c",
        "type": "inject",
        "z": "648fda2a9b3e2104",
        "name": "Start",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "start",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 100,
        "wires": [
            [
                "5deb7ffd584f9748"
            ]
        ]
    },
    {
        "id": "2ba5093f59a96efe",
        "type": "inject",
        "z": "648fda2a9b3e2104",
        "name": "Pause",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "pause",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 140,
        "wires": [
            [
                "5deb7ffd584f9748"
            ]
        ]
    },
    {
        "id": "02654f4556a65d54",
        "type": "inject",
        "z": "648fda2a9b3e2104",
        "name": "Stop",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "stop",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 180,
        "wires": [
            [
                "5deb7ffd584f9748"
            ]
        ]
    },
    {
        "id": "8c4a3e6a4d78e328",
        "type": "inject",
        "z": "648fda2a9b3e2104",
        "name": "Reset",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "reset",
        "payload": "",
        "payloadType": "date",
        "x": 100,
        "y": 220,
        "wires": [
            [
                "5deb7ffd584f9748"
            ]
        ]
    },
    {
        "id": "ac77491e25f02c68",
        "type": "inject",
        "z": "648fda2a9b3e2104",
        "name": "Set (1 min)",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "set",
        "payload": "60000",
        "payloadType": "num",
        "x": 110,
        "y": 260,
        "wires": [
            [
                "5deb7ffd584f9748"
            ]
        ]
    },
    {
        "id": "5c498c5cc67e64aa",
        "type": "debug",
        "z": "648fda2a9b3e2104",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 550,
        "y": 200,
        "wires": []
    },
    {
        "id": "group1",
        "type": "ui-group",
        "name": "Countdown Group",
        "page": "page1",
        "width": 6,
        "height": 3,
        "order": 1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "id": "page1",
        "type": "ui-page",
        "name": "Countdown Page",
        "ui": "f4aaf8d032f42383",
        "path": "/countdown",
        "icon": "home",
        "layout": "grid",
        "theme": "theme1",
        "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": 1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "f4aaf8d032f42383",
        "type": "ui-base",
        "name": "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": false
    },
    {
        "id": "theme1",
        "type": "ui-theme",
        "name": "Countdown Theme",
        "colors": {
            "surface": "#ffffff",
            "primary": "#0094CE",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "density": "default",
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    },
    {
        "id": "ba56b23e0a8890cc",
        "type": "global-config",
        "env": [],
        "modules": {
            "@flowfuse/node-red-dashboard": "1.30.2"
        }
    }
]

```

---

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [8 May 2026 17:13 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/12 "2026-05-08T17:13:50Z")

</div>

Yes, that makes sense however this is not the result I'm getting with the Digital-Clock Node. 39600000 gives me 5:00:00 hours and 43200000 results in 6:00:00 hours. Zero gives me 18:00:00 Hours and 18000000 gives me 23:00:00 Hours so I think maybe 18:00:00 is the common number here and I will need to include that in a formula to calculate my preset times.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 May 2026 17:16 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/13 "2026-05-08T17:16:04Z")

</div>

Oops! No, got that wrong!

An hour is 3600 of course! 5 hours would be 5\*3600 = 18000

My if statement in the onTopic callback also slightly wrong, it should be:

```javascript
if (!isNaN(newTime) && newTime >= 0 && newTime <= Number.MAX_SAFE_INTEGER) {

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 May 2026 17:20 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/14 "2026-05-08T17:20:51Z")

</div>

Argh! Fools rush in!

The input is in seconds of course, not milliseconds:

```javascript
// Give VS Code IntelliSense for uibuilder
/// <reference path="../types/uibuilder.d.ts" />

// @ts-ignore
import { uibuilder } from '../uibuilder/uibuilder.esm.min.js'

// Set the initial countdown time in seconds (Max 43200 for 12 hours)
let timeLeft = 3600 // 1 hour

uibuilder.onTopic('setTime', (msg) => {
    const newTime = parseInt(msg.payload, 10)
    console.log('Received new time:', msg.payload, newTime)
    if (!isNaN(newTime) && newTime >= 0 && newTime <= Number.MAX_SAFE_INTEGER) {
        timeLeft = newTime
        updateTimer() // Update immediately when time is set
    } else {
        console.warn('Invalid time received. Please send a number between 0 and 43200.')
    }
})

const timerDisplay = document.getElementById('timer')

const updateTimer = () => {
    if (timeLeft <= 0) {
        clearInterval(countdownInterval)
        timerDisplay.textContent = '00:00:00'
        timerDisplay.style.color = '#ff4b2b'
        return
    }

    const hours = Math.floor(timeLeft / 3600)
    const minutes = Math.floor((timeLeft % 3600) / 60)
    const seconds = timeLeft % 60

    // Format numbers to always show two digits
    const displayHours = String(hours).padStart(2, '0')
    const displayMinutes = String(minutes).padStart(2, '0')
    const displaySeconds = String(seconds).padStart(2, '0')

    timerDisplay.textContent = `${displayHours}:${displayMinutes}:${displaySeconds}`
    
    timeLeft--

    if (timeLeft <= 1) {
        uibuilder.send({ topic: 'timeout', payload: timeLeft })
    }
}

// Run the timer immediately on load, then every second
updateTimer()
const countdownInterval = setInterval(updateTimer, 1000)

```

---

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [8 May 2026 18:58 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/15 "2026-05-08T18:58:51Z")

</div>

I think I may have a solution with the Digital Clock node but time will tell. To everyone who's contributed to this thread I very much thank you. These forums are great especially since I'm so crappy at any kind of programming. Unfortunately that means that there is very little I can contribute and yet I still get help from these communities.

Thanks again.

---

<div class="post-metadata">

**Author:** ![Technogeek](https://avatars.discourse-cdn.com/v4/letter/t/a88e57/32.png) [@Technogeek](https://discourse.nodered.org/u/Technogeek)\
**Post date:** [12 May 2026 19:12 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/16 "2026-05-12T19:12:37Z")

</div>

So I am able to use the Digital Clock node which is at 00:00:00 when the countdown reaches 21600 and so this will work perfect for me. I just need to setup my conditional statements to compensate for this 21600 offset. Here's a zip file with the test flow that will work for me.

Thanks everyone.  
[flows.json](https://discourse.nodered.org/uploads/short-url/e1m2vp6iir911UGcLdWGevQy5rD.json) (7.2 KB)

---

<div class="post-metadata">

**Author:** ![smanjunath211](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smanjunath211/32/95742_2.png) [@smanjunath211](https://discourse.nodered.org/u/smanjunath211)\
**Post date:** [14 May 2026 08:38 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/17 "2026-05-14T08:38:02Z")

</div>

I asked perplexity and see what it gave.  
completely ready to delpoy flow.  
[https://www.perplexity.ai/search/16a4fac4-ab5b-455f-8cf9-25537c329c1f](https://www.perplexity.ai/search/16a4fac4-ab5b-455f-8cf9-25537c329c1f)

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/7/d/7d3737d0cd253c2e08b446e207aeddcf64fb3097.jpeg)

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [14 May 2026 11:06 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/18 "2026-05-14T11:06:26Z")

</div>

Which would be easily adjusted to work with uibuilder to both set the timer and be alerted when it expired. 😃

---

<div class="post-metadata">

**Author:** ![smanjunath211](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smanjunath211/32/95742_2.png) [@smanjunath211](https://discourse.nodered.org/u/smanjunath211)\
**Post date:** [14 May 2026 12:25 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/19 "2026-05-14T12:25:31Z")

</div>

Yes @TotallyInformation , I myself am a proud user of UIBUILDER and it really helps since it does all the heavy weight lifting,  
what I really wanted to convey from my post is that compared to just a few months ago the AI has improved a lot, earlier, i would be doing so many to and fro with AI and tweaking myself here and there to get to where i want, now a days, there is hardly any rework on the AI given solution. this particular ready to deploy flow was just **one** attempt.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [14 May 2026 12:32 UTC](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942/20 "2026-05-14T12:32:19Z")

</div>

🙂

Yes, AI has been a real help. Though sadly, we are now hitting the buffers a bit. For example, I've been making good use of GitHub Copilot. But from next month, I will have to greatly reduce that because their costs are going through the roof.

Still, thankfully, combining Node-RED with UIBUILDER lets people quite easily build custom, data-driven, web apps with minimal coding. And now even information heavy websites thanks to Markweb (still with the data-driven interfaces when desired). And the free AI is still easily good enough to produce simple web widgets like the one you've shown.

UIBUILDER's minimal coding approach still produces some great results:

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/d/4/d441b3625729a671d6ae5d86db10e7c84405b88c.png)

OK, advertising mode off!

[Next page](https://discourse.nodered.org/t/looking-for-a-countdown-clock/100942.md?page=2)
