Digital Clock
A live-updating clock that displays hours, minutes, seconds, and AM/PM — with dark mode and timezone selection.
Project Overview
Build a real-time digital clock that updates every second. This project practises: setInterval, the Date object, string formatting with padStart, DOM updates, and event listeners.
What you will build
- Large digital display showing HH:MM:SS
- AM/PM indicator
- Current date (e.g. Sunday, 1 June 2026)
- Toggle between 12-hour and 24-hour format
- Dark / light mode toggle
Concepts used
setIntervalfor recurring executionnew Date()and its methodsString.prototype.padStartfor zero-padding- DOM content update (
textContent) localStoragefor preference persistence
Step-by-Step Build
Step 1 — HTML structure
<div class="clock-wrapper">
<div id="time">00:00:00</div>
<div id="period">AM</div>
<div id="date"></div>
<div class="controls">
<button id="format-btn">24h</button>
<button id="theme-btn">Dark Mode</button>
</div>
</div>
Step 2 — Get the current time
function getTimeParts() {
const now = new Date();
return {
hours: now.getHours(),
minutes: now.getMinutes(),
seconds: now.getSeconds(),
day: now.toLocaleDateString('en-GB', {
weekday: 'long', day: 'numeric',
month: 'long', year: 'numeric'
})
};
}
Step 3 — Format and display
function updateClock() {
const { hours, minutes, seconds, day } = getTimeParts();
const is24h = localStorage.getItem('clock_24h') === 'true';
let displayHours = hours;
let period = '';
if (!is24h) {
period = hours >= 12 ? 'PM' : 'AM';
displayHours = hours % 12 || 12; // 0 → 12
}
const pad = n => String(n).padStart(2, '0');
document.querySelector('#time').textContent =
`${pad(displayHours)}:${pad(minutes)}:${pad(seconds)}`;
document.querySelector('#period').textContent = period;
document.querySelector('#date').textContent = day;
}
Step 4 — Start the interval
updateClock(); // show immediately
setInterval(updateClock, 1000);
Step 5 — Controls
document.querySelector('#format-btn').addEventListener('click', () => {
const current = localStorage.getItem('clock_24h') === 'true';
localStorage.setItem('clock_24h', !current);
document.querySelector('#format-btn').textContent = !current ? '12h' : '24h';
updateClock();
});
document.querySelector('#theme-btn').addEventListener('click', () => {
document.body.classList.toggle('dark');
const isDark = document.body.classList.contains('dark');
localStorage.setItem('clock_theme', isDark ? 'dark' : 'light');
document.querySelector('#theme-btn').textContent = isDark ? 'Light Mode' : 'Dark Mode';
});
// Apply saved preferences on load
if (localStorage.getItem('clock_theme') === 'dark') document.body.classList.add('dark');
if (localStorage.getItem('clock_24h') === 'true') {
document.querySelector('#format-btn').textContent = '12h';
}
Complete Working Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Digital Clock</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #1a1a2e;
color: #e0e0e0;
font-family: 'Courier New', monospace;
transition: background .3s, color .3s;
}
body.light { background: #f0f4f8; color: #1a1a2e; }
.clock-wrapper { text-align: center; }
#time {
font-size: clamp(3rem, 12vw, 7rem);
font-weight: 700;
letter-spacing: 0.05em;
color: #00d4ff;
text-shadow: 0 0 20px #00d4ff88;
}
body.light #time { color: #0066cc; text-shadow: none; }
#period {
font-size: 1.5rem;
letter-spacing: .2em;
margin: .5rem 0;
opacity: .7;
}
#date { font-size: 1.1rem; opacity: .6; margin-bottom: 2rem; }
.controls { display: flex; gap: 1rem; justify-content: center; }
button {
padding: .5rem 1.25rem;
border: 2px solid currentColor;
background: transparent;
color: inherit;
border-radius: 999px;
cursor: pointer;
font-size: .9rem;
transition: background .2s, color .2s;
}
button:hover { background: currentColor; color: #1a1a2e; }
</style>
</head>
<body>
<div class="clock-wrapper">
<div id="time">00:00:00</div>
<div id="period"></div>
<div id="date"></div>
<div class="controls">
<button id="format-btn">24h</button>
<button id="theme-btn">Light Mode</button>
</div>
</div>
<script>
const timeEl = document.querySelector('#time');
const periodEl = document.querySelector('#period');
const dateEl = document.querySelector('#date');
const fmtBtn = document.querySelector('#format-btn');
const thmBtn = document.querySelector('#theme-btn');
function is24h() { return localStorage.getItem('clock_24h') === 'true'; }
function updateClock() {
const now = new Date();
let hours = now.getHours();
const mins = now.getMinutes();
const secs = now.getSeconds();
const pad = n => String(n).padStart(2, '0');
let period = '';
if (!is24h()) {
period = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12;
}
timeEl.textContent = `${pad(hours)}:${pad(mins)}:${pad(secs)}`;
periodEl.textContent = period;
dateEl.textContent = now.toLocaleDateString('en-GB', {
weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'
});
}
fmtBtn.addEventListener('click', () => {
localStorage.setItem('clock_24h', !is24h());
fmtBtn.textContent = is24h() ? '12h' : '24h';
updateClock();
});
thmBtn.addEventListener('click', () => {
document.body.classList.toggle('light');
const isLight = document.body.classList.contains('light');
localStorage.setItem('clock_theme', isLight ? 'light' : 'dark');
thmBtn.textContent = isLight ? 'Dark Mode' : 'Light Mode';
});
if (localStorage.getItem('clock_theme') === 'light') {
document.body.classList.add('light');
thmBtn.textContent = 'Dark Mode';
}
if (is24h()) fmtBtn.textContent = '12h';
updateClock();
setInterval(updateClock, 1000);
</script>
</body>
</html>
Code Explained
padStart(2, '0')
String(5).padStart(2, '0') produces '05' — it pads the start of the string with zeros until the total length is 2. This is why we get 09:05:07 instead of 9:5:7.
hours % 12 || 12
12-hour conversion: hours % 12 gives 0 for both midnight (0) and noon (12). The || 12 replaces 0 with 12 — making midnight show as 12:xx AM.
setInterval vs setTimeout
setInterval(fn, 1000) calls fn every 1000ms indefinitely. We call updateClock() once immediately before starting the interval so the display shows instantly on load (not after a 1-second blank).
Challenges
- Add a milliseconds display that updates every 100ms.
- Add a blinking colon separator (hide/show every half-second using
setInterval). - Add a timezone selector (
<select>) usingIntl.DateTimeFormatwith atimeZoneoption. - Add an alarm: user picks a time, an alert fires when the clock hits it.
- Display elapsed time since midnight as a progress bar.
Improvement Ideas
- Use a monospace font or a 7-segment style web font for an authentic digital look.
- Add a smooth CSS transition when the numbers change.
- Show a small world clock with 3 cities in different timezones.
- Replace the text display with an SVG or canvas-drawn clock face for an analogue version.