JavaScript Countdown Timer

A countdown timer tracks and displays the time remaining until a specific event or deadline. From building excitement for the New Year to creating urgency during limited-time promotions, countdown timers are a powerful way to engage users and drive action.

In this tutorial, you'll learn how to build a countdown timer for the New Year. The timer will be reusable and customizable, so you can easily adapt it for other events and add dynamic features to your web projects.

Step 1: Set Up the Project Structure

Open the CodeSandbox HTML + CSS template and click Sign In at the top right to edit it. Sign in with GitHub, Google or Apple.

Create two JavaScript files in the left Explorer sidebar by right-clicking on the background and selecting New File:

Finally, download the image below and drag and drop it into the explorer, with the name fireworks.avif.

Fireworks background image for the countdown timer

Step 2: Replace content in index.html

Replace all content in the HTML file index.html, to create the foundation for the countdown timer:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Countdown Timer</title>
  <link rel="stylesheet" href="/styles.css">
</head>
<body>
  <h1>New Year Countdown</h1>
  <div class="countdown-timer"></div>
  <div class="message"></div>
  <div class="year"></div>
  <script src="/app.js" type="module"></script>
</body>
</html>

Step 3: Develop the countdown JavaScript

Create a createCountdown function in countdown.js:

export function createCountdown(targetDate, onUpdate, onComplete) {
  const currentTime = new Date().getTime();
  let timeRemaining = targetDate.getTime() - currentTime;

  let intervalId;

  function tick() {
    timeRemaining -= 1000;

    if (timeRemaining <= 0) {
      onComplete();
      clearInterval(intervalId);
    } else {
      onUpdate({
        days: Math.floor(timeRemaining / 1000 / 60 / 60 / 24),
        hours: Math.floor(timeRemaining / 1000 / 60 / 60) % 24,
        minutes: Math.floor(timeRemaining / 1000 / 60) % 60,
        seconds: Math.floor(timeRemaining / 1000) % 60,
      });
    }
  }

  tick();
  setInterval(tick, 1000);
}
        

Step 4: Initialize the countdown

In app.js, initialize and manage the countdown:

import confetti from 'https://esm.sh/canvas-confetti@1.9.3';
import { createCountdown } from './countdown.js';

const countdownElement = document.querySelector('.countdown-timer');
const messageElement = document.querySelector('.message');
const yearElement = document.querySelector('.year');

const currentYear = new Date().getFullYear();
const targetDate = new Date(`1 Jan ${
  // If it's the first 10 minutes of the new year, set the target
  // year to the current year, to be able to see the celebration
  new Date().getMonth() === 0 && new Date().getDate() === 1 && new Date().getHours() === 0 && new Date().getMinutes() < 10
    ? currentYear
    : currentYear + 1
} 00:00:00`);
const countdown = createCountdown(
  targetDate,
  function onUpdate({ days, hours, minutes, seconds }) {
    countdownElement.innerHTML = `
      
${days} days
${hours} hours
${minutes} minutes
${seconds} seconds
`; }, function onComplete() { confetti({ particleCount: 200, angle: 60, spread: 55, origin: { x: 0 }, }); confetti({ particleCount: 200, angle: 120, spread: 55, origin: { x: 1 }, }); countdownElement.style.display = 'none'; messageElement.textContent = 'Happy New Year!'; yearElement.textContent = currentYear + 1; } );

Step 5: Style the countdown timer

Replace all content styles.css with new styles:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  color: #fff;
  line-height: 1.8;
  position: relative;
  background: #000;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  margin: 0;
}

body::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-image: url("/fireworks.avif");
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  opacity: 0.2; /* Adjusted to 20% opacity */
  z-index: -1;
}

h1 {
  font-size: 3.75rem; /* 60px */
  margin: 20px 0 40px;
  text-align: center;
}

.countdown-timer {
  display: flex;
  gap: 20px;
  justify-content: center;
  align-items: flex-start; /* Align blocks to the top */
  flex-wrap: wrap;
}

.timer {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-top: 5px;
  font-size: 1rem;
  text-transform: uppercase;
}

.timer span {
  margin: 0;
  font-size: 4rem; /* Adjusted size for the count */
}

.message {
  font-size: 4rem; /* Adjusted for clarity */
  font-weight: bold;
  text-align: center;
}

.year {
  font-size: 12.5rem; /* 200px */
  z-index: -1;
  opacity: 0.2;
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
}

@media (max-width: 500px) {
  h1 {
    font-size: 1.375rem; /* 22px */
  }

  .countdown-timer {
    gap: 10px;
    font-size: 12px;
  }

  .timer span {
    font-size: 1.875rem; /* 30px */
  }

  .year {
    font-size: 6.25rem; /* 100px */
  }

  .message {
    font-size: 1.563rem; /* 25px */
  }
}

        

You're done! The preview URL at the top right of the CodeSandbox editor (ending in csb.app/) is your website.

Step 6 (stretch goal): Deploy to Netlify

CodeSandbox URLs can sometimes stop working. To work around this, deploying to another platform like Netlify is an option.

Do this by clicking on the Download button at the top left of CodeSandbox and then dragging the downloaded ZIP file into Netlify Drop