Build Your Own Pomodoro Timer with HTML, CSS, and JavaScript

Faraz

By Faraz -

Learn to build a sleek Pomodoro Timer using HTML, JavaScript, and Tailwind CSS. A perfect beginner project to boost your productivity and coding skills.


build-your-own-pomodoro-timer-with-html-css-and-javascript.webp

Table of Contents

  1. Project Introduction
  2. HTML Code
  3. CSS Code
  4. JavaScript Code
  5. Conclusion
  6. Preview

Do you ever struggle to stay focused on a task? The Pomodoro Technique is a simple yet powerful time management method that can help. It breaks your work into focused 25-minute intervals, separated by short breaks. It’s a game-changer for productivity!

Instead of just using a generic app, why not build your own? In this guide, we'll walk you through creating a fully functional Pomodoro Timer using basic HTML, a little bit of magic from Tailwind CSS, and the power of JavaScript. It's a fantastic project for beginners to practice their skills.

What You'll Need (Prerequisites)

  • Basic understanding of HTML: For creating the structure.
  • A little familiarity with Tailwind CSS: For styling. We'll provide all the code!
  • Fundamental JavaScript concepts: For making the timer work.
  • A code editor like VS Code, Sublime Text, or any other you prefer.

Ready? Let's get coding!

Source Code

Step 1 (HTML Code):

First, we need to build the visual part of our timer. With Tailwind CSS, we write styling classes directly in our HTML, which makes things fast and clean.

Create a file named index.html. This single file will contain our structure and styling.

Step 2 (CSS Code):

Now that we have the basic HTML structure, let's bring it to life with some custom styling. A good-looking timer is much more enjoyable to use! We will create a modern, dark-themed interface.

Create a new file in the same folder and name it styles.css. Add the following code to this file.

:root {
  --glow-color: #ff6347; /* Tomato Red-Orange */
  --bg-color-dark: #1a2238; /* Deep Blue */
  --bg-color-light: #252a34;
  --text-color: #eaeaea;
  --shadow-color: rgba(255, 99, 71, 0.3); /* Tomato shadow */
  --shadow-strong-color: rgba(255, 99, 71, 0.5); /* Stronger Tomato shadow */
}

body {
  font-family: 'Poppins', sans-serif;
  background-color: var(--bg-color-dark);
  color: var(--text-color);
  /* Add a subtle radial gradient for depth */
  background-image: radial-gradient(
    circle at center,
    var(--bg-color-light) 0%,
    var(--bg-color-dark) 70%
  );
}

#time-display {
  font-family: 'JetBrains Mono', monospace;
  text-shadow: 0 0 10px var(--shadow-color), 0 0 20px var(--shadow-color);
}

/* Main container for the futuristic look */
.timer-container {
  border: 1px solid var(--glow-color);
  box-shadow: 0 0 25px var(--shadow-color), inset 0 0 15px var(--shadow-color);
  background: rgba(26, 34, 56, 0.8);
  backdrop-filter: blur(10px);
  position: relative;
  overflow: hidden;
}

/* Animated grid background */
.timer-container::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image: linear-gradient(
      to right,
      rgba(255, 99, 71, 0.15) 1px,
      transparent 1px
    ),
    /* Tomato grid */
      linear-gradient(to bottom, rgba(255, 99, 71, 0.15) 1px, transparent 1px); /* Tomato grid */
  background-size: 40px 40px;
  animation: pan-grid 20s linear infinite;
  z-index: 0;
  opacity: 0.5;
}

@keyframes pan-grid {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: 40px 40px;
  }
}

/* Style for the mode buttons */
.mode-button {
  transition: all 0.3s ease;
  text-shadow: 0 0 5px transparent;
  border-bottom: 2px solid transparent;
}
.mode-button.active {
  color: var(--glow-color);
  text-shadow: 0 0 8px var(--shadow-color);
  border-bottom-color: var(--glow-color);
}

/* Progress Ring Styling */
#progress-ring-circle {
  transition: stroke-dashoffset 1s linear;
  filter: drop-shadow(0 0 5px var(--shadow-color));
}

/* Decorative Rings and Animations */
.decorative-ring {
  animation-duration: 40s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  transform-origin: 50% 50%;
}
#ring-1 {
  animation-name: rotate-cw;
  animation-duration: 60s;
}
#ring-2 {
  animation-name: rotate-ccw;
  animation-duration: 35s;
}

/* Spinning animation for running timer */
.timer-running #ring-1 {
  animation-duration: 20s;
}
.timer-running #ring-2 {
  animation-duration: 12s;
}

@keyframes rotate-cw {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframes rotate-ccw {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(-360deg);
  }
}

/* Control Buttons */
#start-pause-button {
  background: var(--glow-color);
  color: var(--bg-color-dark);
  box-shadow: 0 0 15px var(--shadow-strong-color);
  transition: all 0.2s ease-in-out;
}
#start-pause-button:hover {
  box-shadow: 0 0 25px var(--shadow-strong-color);
  transform: translateY(-2px);
}

#reset-button {
  border: 1px solid var(--glow-color);
  color: var(--glow-color);
  transition: all 0.2s ease-in-out;
}
#reset-button:hover {
  background: var(--shadow-color);
  box-shadow: 0 0 10px var(--shadow-color);
}

/* Subtle pulse for the whole UI */
.timer-container {
  animation: pulse 8s infinite ease-in-out;
}
@keyframes pulse {
  0% {
    box-shadow: 0 0 25px var(--shadow-color), inset 0 0 15px var(--shadow-color);
  }
  50% {
    box-shadow: 0 0 35px var(--shadow-strong-color),
      inset 0 0 20px var(--shadow-strong-color);
  }
  100% {
    box-shadow: 0 0 25px var(--shadow-color), inset 0 0 15px var(--shadow-color);
  }
} 

Step 3 (JavaScript Code):

Now that our timer looks good, let's make it work! The JavaScript code will handle the countdown logic and make the buttons functional.

Create a file named script.js and add the following code.

window.onload = () => {
  const timeDisplay = document.getElementById('time-display');
  const currentModeDisplay = document.getElementById('current-mode-display');
  const startPauseButton = document.getElementById('start-pause-button');
  const resetButton = document.getElementById('reset-button');
  const pomodoroCountDisplay = document.getElementById('pomodoro-count');
  const timerVisualContainer = document.getElementById(
    'timer-visual-container'
  );

  const pomodoroModeButton = document.getElementById('pomodoro-mode');
  const shortBreakModeButton = document.getElementById('short-break-mode');
  const longBreakModeButton = document.getElementById('long-break-mode');

  const progressCircle = document.getElementById('progress-ring-circle');
  const radius = progressCircle.r.baseVal.value;
  const circumference = 2 * Math.PI * radius;
  progressCircle.style.strokeDasharray = `${circumference} ${circumference}`;
  progressCircle.style.strokeDashoffset = 0;

  let synth = null; // Initialize synth as null

  const timerModes = {
    pomodoro: 25 * 60,
    shortBreak: 5 * 60,
    longBreak: 15 * 60,
  };

  let timerInterval = null;
  let currentMode = 'pomodoro';
  let timeLeft = timerModes[currentMode];
  let isRunning = false;
  let pomodoroCount = 0;

  function updateDisplay() {
    const minutes = Math.floor(timeLeft / 60);
    const seconds = timeLeft % 60;
    const formattedTime = `${String(minutes).padStart(2, '0')}:${String(
      seconds
    ).padStart(2, '0')}`;
    timeDisplay.textContent = formattedTime;
    document.title = `${formattedTime} - Focus Timer`;

    const totalTime = timerModes[currentMode];
    const offset = circumference - (timeLeft / totalTime) * circumference;
    progressCircle.style.strokeDashoffset = offset;
  }

  function switchMode(newMode) {
    currentMode = newMode;
    timeLeft = timerModes[currentMode];
    pauseTimer();
    updateDisplay();
    updateActiveButton();

    let modeText = 'Focus';
    if (newMode === 'shortBreak') modeText = 'Short Break';
    if (newMode === 'longBreak') modeText = 'Long Break';
    currentModeDisplay.textContent = modeText;
  }

  function updateActiveButton() {
    document
      .querySelectorAll('.mode-button')
      .forEach((button) => button.classList.remove('active'));
    if (currentMode === 'pomodoro') pomodoroModeButton.classList.add('active');
    else if (currentMode === 'shortBreak')
      shortBreakModeButton.classList.add('active');
    else if (currentMode === 'longBreak')
      longBreakModeButton.classList.add('active');
  }

  function startTimer() {
    if (isRunning) return;

    if (typeof Tone !== 'undefined') {
      if (!synth) {
        synth = new Tone.Synth().toDestination();
      }
      if (Tone.context.state !== 'running') {
        Tone.start();
      }
    }

    isRunning = true;
    startPauseButton.textContent = 'PAUSE';
    timerVisualContainer.classList.add('timer-running');

    timerInterval = setInterval(() => {
      timeLeft--;
      updateDisplay();
      if (timeLeft <= 0) {
        handleTimerEnd();
      }
    }, 1000);
  }

  function pauseTimer() {
    isRunning = false;
    startPauseButton.textContent = 'START';
    timerVisualContainer.classList.remove('timer-running');
    clearInterval(timerInterval);
  }

  function resetTimer() {
    pauseTimer();
    timeLeft = timerModes[currentMode];
    updateDisplay();
  }

  function handleTimerEnd() {
    pauseTimer();
    if (synth) {
      synth.triggerAttackRelease('C5', '0.2');
      setTimeout(() => synth.triggerAttackRelease('G5', '0.3'), 200);
    }

    if (currentMode === 'pomodoro') {
      pomodoroCount++;
      pomodoroCountDisplay.textContent = pomodoroCount;
      if (pomodoroCount > 0 && pomodoroCount % 4 === 0) {
        switchMode('longBreak');
      } else {
        switchMode('shortBreak');
      }
    } else {
      switchMode('pomodoro');
    }
  }

  startPauseButton.addEventListener('click', () => {
    if (isRunning) {
      pauseTimer();
    } else {
      startTimer();
    }
  });

  resetButton.addEventListener('click', resetTimer);
  pomodoroModeButton.addEventListener('click', () => switchMode('pomodoro'));
  shortBreakModeButton.addEventListener('click', () =>
    switchMode('shortBreak')
  );
  longBreakModeButton.addEventListener('click', () => switchMode('longBreak'));

  // Initial setup
  updateDisplay();
  updateActiveButton();
};

Final Output:

build-your-own-pomodoro-timer-with-html-css-and-javascript.gif

Conclusion:

You now have a working Pomodoro timer built with HTML, CSS, and JavaScript! This project boosts your coding skills and helps with daily focus. Try using it for study or work sessions.

Feel free to tweak the code and share your version. If you liked this guide, check out more web projects.

That’s a wrap!

I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Components

Please allow ads on our site🥺