What you'll build
<div class="clock">
<p id="greeting">Hello</p>
<h1 id="time">00:00:00</h1>
<p id="date"></p>
<button id="format">Switch to 12-hour</button>
</div>body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #15161b; }
.clock {
text-align: center;
font-family: system-ui, sans-serif;
color: #f2efe8;
padding: 32px 40px;
border: 2px solid #3b404c;
border-radius: 24px;
background: #1c1f27;
}
#greeting { margin: 0; color: #ffd23f; font-weight: 700; }
#time {
margin: 8px 0;
font: 700 clamp(2.5rem, 10vw, 4.5rem) "Courier New", monospace;
letter-spacing: 0.05em;
color: #2bd17f;
text-shadow: 0 0 18px rgb(43 209 127 / 45%);
}
#date { margin: 0 0 16px; color: #9093a0; }
#format {
padding: 8px 16px;
border: 0;
border-radius: 999px;
background: #2bd17f;
color: #07140d;
font-weight: 700;
cursor: pointer;
}const timeEl = document.querySelector("#time");
const dateEl = document.querySelector("#date");
const greetingEl = document.querySelector("#greeting");
let use24 = true;
const pad = (n) => String(n).padStart(2, "0");
function greeting(hour) {
if (hour >= 5 && hour < 12) return "Good morning βοΈ";
if (hour >= 12 && hour < 18) return "Good afternoon π€";
if (hour >= 18 && hour < 22) return "Good evening π";
return "Good night π";
}
function tick() {
const now = new Date();
let h = now.getHours();
let suffix = "";
if (!use24) {
suffix = h >= 12 ? " PM" : " AM";
h = h % 12 || 12;
}
timeEl.textContent = `${pad(h)}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${suffix}`;
dateEl.textContent = now.toLocaleDateString("en-GB", { weekday: "long", day: "numeric", month: "long" });
greetingEl.textContent = greeting(now.getHours());
}
tick();
setInterval(tick, 1000);
document.querySelector("#format").addEventListener("click", (e) => {
use24 = !use24;
e.target.textContent = use24 ? "Switch to 12-hour" : "Switch to 24-hour";
tick();
});You'll build a big, beautiful clock that ticks every second, shows today's date, says "Good morning/afternoon/evening", and lets the user switch between 12- and 24-hour time.
Along the way you'll use JavaScript's Date object, setInterval, string padding and simple conditions.
How a JavaScript clock works
A web page doesn't have a built-in clock that updates on its own. Instead, you ask JavaScript to run a small function once every second, and each time it runs, the function reads the current time and writes it on the page. That's the whole trick.
Two tools make it possible:
- The Date object.
new Date()gives you the date and time at the exact moment it's created. Methods such asgetHours(),getMinutes()andgetSeconds()pull out each part as a number. - setInterval.
setInterval(tick, 1000)calls yourtickfunction every 1,000 milliseconds, which is one second. The timers lesson explains how it differs fromsetTimeout.
Because tick creates a fresh Date every time, the clock never drifts far from the real time, even if a call arrives a few milliseconds late.
Why the numbers need padding
getMinutes() returns 5, not 05. Shown as is, the clock would jump between 10:5:9 and 10:15:30, changing width every second. String(n).padStart(2, "0") adds a leading zero to any single digit, so every part is always two characters wide. The string methods lesson covers padStart and its partner padEnd.
A monospaced or tabular font helps too. With font-variant-numeric: tabular-nums, each digit takes the same width, so the clock doesn't wobble as the numbers change.
Formatting the date the easy way
You could build the date line by hand: read the day of the week as a number from 0 to 6, look it up in an array of day names, do the same for the month, and join everything together. It works, but it's a lot of code and it only ever speaks one language.
toLocaleDateString does all of that for you. Pass it a locale and an options object describing which parts you want:
const now = new Date();
now.toLocaleDateString("en-GB", { weekday: "long", day: "numeric", month: "long" });
// "Thursday 1 October"Change "en-GB" to "en-US" and the order switches to "Thursday, October 1". Change it to "fr-FR" and you get the French names. Leave the locale out entirely and the browser uses the visitor's own language settings, which is usually what you want on a real website. The same options work with toLocaleTimeString for the time, including hour12: true for AM and PM.
Writing the clock by hand first and then replacing parts with these built-in methods is a useful exercise. It shows you what the methods are doing behind the scenes, and it's a good reminder to check whether the language already solves a problem before you write your own solution.
Turn it into an analog clock
Once your digital clock works, an analog clock is a short step away, and it uses the same tick() function. Each hand is a thin element that rotates around the centre of the clock face. All you need is the angle for each hand:
- Seconds: 360 degrees Γ· 60 seconds = 6 degrees per second.
- Minutes: also 6 degrees per minute, plus a little extra for the seconds so the hand moves smoothly.
- Hours: 360 Γ· 12 = 30 degrees per hour, plus 0.5 degrees per minute.
const s = now.getSeconds();
const m = now.getMinutes();
const h = now.getHours() % 12;
secondHand.style.transform = `rotate(${s * 6}deg)`;
minuteHand.style.transform = `rotate(${m * 6 + s * 0.1}deg)`;
hourHand.style.transform = `rotate(${h * 30 + m * 0.5}deg)`;In CSS, give each hand position: absolute, put its bottom at the centre of the face and set transform-origin: bottom center so it spins around that point rather than its middle. The CSS 2D transforms lesson covers rotation and transform origins.
Requirements checklist
- Time displayed as
HH:MM:SS, always two digits per part - Updates every second with
setInterval - Today's date like "Thursday 1 October"
- A greeting based on the hour: morning (5 to 11), afternoon (12 to 17), evening (18 to 21), night otherwise
- A button that toggles 12/24-hour format (12-hour shows AM/PM)
Starter code
Press Run to see where you're starting from, then follow the steps.
<div class="clock">
<p id="greeting">Hello</p>
<h1 id="time">00:00:00</h1>
<p id="date"></p>
<button id="format">Switch to 12-hour</button>
</div>.clock { text-align: center; font-family: system-ui, sans-serif; }// Your clock code
Step-by-step guide
- 1
Read the current time
new Date()gives the current date and time. Its methods return numbers.JavaScript const now = new Date(); now.getHours(); // 0 to 23 now.getMinutes(); // 0 to 59 now.getSeconds(); // 0 to 59 - 2
Format with padStart
7should show as07. Convert to a string and pad it.JavaScript const pad = (n) => String(n).padStart(2, "0"); - 3
Write the tick function
JavaScript const timeEl = document.querySelector("#time"); const dateEl = document.querySelector("#date"); const greetingEl = document.querySelector("#greeting"); let use24 = true; function tick() { const now = new Date(); let h = now.getHours(); let suffix = ""; if (!use24) { suffix = h >= 12 ? " PM" : " AM"; h = h % 12 || 12; } timeEl.textContent = `${pad(h)}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${suffix}`; dateEl.textContent = now.toLocaleDateString("en-GB", { weekday: "long", day: "numeric", month: "long" }); greetingEl.textContent = greeting(now.getHours()); }h % 12 || 12turns 0 into 12 and 13 into 1. - 4
The greeting
JavaScript function greeting(hour) { if (hour >= 5 && hour < 12) return "Good morning βοΈ"; if (hour >= 12 && hour < 18) return "Good afternoon π€"; if (hour >= 18 && hour < 22) return "Good evening π"; return "Good night π"; } - 5
Start ticking and add the toggle
JavaScript tick(); setInterval(tick, 1000); document.querySelector("#format").addEventListener("click", (e) => { use24 = !use24; e.target.textContent = use24 ? "Switch to 12-hour" : "Switch to 24-hour"; tick(); });Call
tick()once straight away so the clock doesn't show 00:00:00 for the first second.
Final solution
Show the complete solution
<div class="clock">
<p id="greeting">Hello</p>
<h1 id="time">00:00:00</h1>
<p id="date"></p>
<button id="format">Switch to 12-hour</button>
</div>body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #15161b; }
.clock {
text-align: center;
font-family: system-ui, sans-serif;
color: #f2efe8;
padding: 32px 40px;
border: 2px solid #3b404c;
border-radius: 24px;
background: #1c1f27;
}
#greeting { margin: 0; color: #ffd23f; font-weight: 700; }
#time {
margin: 8px 0;
font: 700 clamp(2.5rem, 10vw, 4.5rem) "Courier New", monospace;
letter-spacing: 0.05em;
color: #2bd17f;
text-shadow: 0 0 18px rgb(43 209 127 / 45%);
}
#date { margin: 0 0 16px; color: #9093a0; }
#format {
padding: 8px 16px;
border: 0;
border-radius: 999px;
background: #2bd17f;
color: #07140d;
font-weight: 700;
cursor: pointer;
}const timeEl = document.querySelector("#time");
const dateEl = document.querySelector("#date");
const greetingEl = document.querySelector("#greeting");
let use24 = true;
const pad = (n) => String(n).padStart(2, "0");
function greeting(hour) {
if (hour >= 5 && hour < 12) return "Good morning βοΈ";
if (hour >= 12 && hour < 18) return "Good afternoon π€";
if (hour >= 18 && hour < 22) return "Good evening π";
return "Good night π";
}
function tick() {
const now = new Date();
let h = now.getHours();
let suffix = "";
if (!use24) {
suffix = h >= 12 ? " PM" : " AM";
h = h % 12 || 12;
}
timeEl.textContent = `${pad(h)}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${suffix}`;
dateEl.textContent = now.toLocaleDateString("en-GB", { weekday: "long", day: "numeric", month: "long" });
greetingEl.textContent = greeting(now.getHours());
}
tick();
setInterval(tick, 1000);
document.querySelector("#format").addEventListener("click", (e) => {
use24 = !use24;
e.target.textContent = use24 ? "Switch to 12-hour" : "Switch to 24-hour";
tick();
});Bonus challenges
- Add a stopwatch with Start, Stop and Reset buttons.
- Show clocks for three cities using
toLocaleTimeString("en-GB", { timeZone: "Asia/Tokyo" }). - Change the background colour gradually through the day with HSL and the current hour.
Common mistakes and how to fix them
The clock appears one second late. setInterval waits a full second before the first call. Call tick() once straight away, then start the interval.
Midnight shows as 0 in 12-hour mode. In 12-hour time, hour 0 should read 12. Use hours % 12 || 12: the remainder gives 0 for both midnight and noon, and || 12 swaps that 0 for 12.
The greeting is wrong at the boundaries. Check your conditions carefully. With hour < 12 for morning and hour < 18 for afternoon, 12:00 is afternoon and 18:00 is evening. Write the ranges down before coding them, as the if...else lesson suggests.
Months appear one too low. getMonth() counts from 0, so January is 0. Using toLocaleDateString with options avoids this problem entirely and gives you the day and month names for free.
Check your work
- Watch the seconds roll over from 59 to 00. The minute should go up at the same moment.
- Toggle between 12-hour and 24-hour time a few times. AM and PM should only appear in 12-hour mode.
- Change your computer's clock (or temporarily replace
new Date()with a fixed date such asnew Date(2026, 0, 1, 23, 59, 55)) to test midnight and the greeting boundaries. - Leave the tab in the background for a minute and come back. The time should still be correct.
Clock, countdown or stopwatch?
The same building blocks power three different tools. A clock reads the real time on every tick, as you've built here. A countdown timer stores a target date and shows the difference between now and then: target - Date.now() gives the milliseconds left, which you divide into days, hours, minutes and seconds. A stopwatch records Date.now() when you press Start and shows how much time has passed since.
All three should calculate from Date.now() on every tick rather than adding one second each time. Timers can fire late, especially in background tabs, and counting ticks slowly drifts away from the real time, while measuring the difference stays accurate. Building a countdown to a birthday or a product launch is a great next project that reuses almost all of this code.
Questions beginners ask
Why does the clock slow down in a background tab? Browsers save battery by running timers less often in hidden tabs. Because the clock reads the real time on every tick, it catches up as soon as you return.
Can I show the time in another country? Yes. toLocaleTimeString accepts a timeZone option, which is the second bonus challenge. The dates lesson shows more formatting options.
What should I build next? The tip calculator practises reading user input, and the to-do app adds saving data between visits.