FREE 60–75 MINUTE LESSON • GRADES 7–12 • BEGINNER JAVASCRIPT
Build a study timer that makes focus feel manageable
Students create an accessible Pomodoro-style focus timer with HTML, CSS, and JavaScript—then personalize the time, colors, and messages.
Looking for a beginner JavaScript Pomodoro timer project for students? This one-file lesson teaches countdown logic, setInterval, events, state, DOM updates, responsive CSS, and accessibility. No framework, account, internet connection, or special software is required.
| Time | 60–75 minutes |
| Level | Beginner JavaScript |
| Skills | Functions, events, setInterval, DOM updates |
| Setup | Any text editor and modern web browser |
What students will learn
1. Structure
Use semantic HTML, timer modes, accessible controls, and a live status message.
2. Style
Create a responsive timer card, circular progress display, strong focus states, and mobile layout.
3. Interact
Use variables, functions, events, setInterval, and DOM updates to control time.
Build the project
Step 1: Create one file
Create a folder named study-sprint-timer. Inside it, create a file named index.html. Everything lives in one file, so the project is easy to copy, test, and share.
Step 2: Paste the complete code
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Study Sprint Timer</title> <style> :root { font-family: system-ui, sans-serif; color: #12304a; background: #eef3ff; } * { box-sizing: border-box; } body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 24px; background: linear-gradient(135deg, #e7e3ff, #dcf8f1); } .timer-card { width: min(620px, 100%); padding: 36px; text-align: center; background: white; border-radius: 28px; box-shadow: 0 20px 60px rgba(18, 48, 74, 0.16); } .eyebrow { color: #6d4aff; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.11em; } h1 { margin: 8px 0 22px; } .mode-switch { display: flex; justify-content: center; gap: 10px; margin-bottom: 24px; } .mode-button, .control { border: 0; border-radius: 999px; padding: 12px 18px; font: inherit; font-weight: 800; cursor: pointer; } .mode-button { color: #4f46a5; background: #f0eeff; } .mode-button[aria-pressed="true"] { color: white; background: #6d4aff; } .timer-ring { --progress: 100; width: 220px; aspect-ratio: 1; margin: 0 auto 22px; display: grid; place-items: center; position: relative; border-radius: 50%; background: conic-gradient(#19a896 calc(var(--progress) * 1%), #e9e7f5 0); } .timer-ring::before { content: ""; width: 184px; aspect-ratio: 1; position: absolute; border-radius: 50%; background: white; } .time { position: relative; margin: 0; font-size: clamp(3rem, 11vw, 4.5rem); font-weight: 850; letter-spacing: -0.05em; } .status { min-height: 48px; margin: 0 auto 20px; color: #51606f; font-weight: 700; } .controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; } .start { color: white; background: #12304a; } .pause { color: #12304a; background: #ffd166; } .reset { color: #12304a; background: #e7edf4; } button:hover { transform: translateY(-2px); } button:focus-visible { outline: 4px solid #ff9f1c; outline-offset: 3px; } .tip { margin-top: 24px; color: #667085; font-size: 0.88rem; } @media (max-width: 480px) { .timer-card { padding: 28px 20px; } .mode-switch { flex-direction: column; } .timer-ring { width: 190px; } .timer-ring::before { width: 158px; } } @media (prefers-reduced-motion: reduce) { * { transition: none !important; } button:hover { transform: none; } } </style></head><body> <main class="timer-card"> <p class="eyebrow">ONE TASK • ONE SPRINT</p> <h1>Study Sprint Timer</h1> <div class="mode-switch" role="group" aria-label="Choose timer mode"> <button id="focusMode" class="mode-button" aria-pressed="true">Focus · 25 min</button> <button id="breakMode" class="mode-button" aria-pressed="false">Break · 5 min</button> </div> <div id="ring" class="timer-ring"> <p id="time" class="time" aria-label="25 minutes remaining">25:00</p> </div> <p id="status" class="status" aria-live="polite">Ready for a focused study sprint.</p> <div class="controls"> <button id="start" class="control start">Start</button> <button id="pause" class="control pause">Pause</button> <button id="reset" class="control reset">Reset</button> </div> <p class="tip">Choose one small task. When the timer ends, take the break.</p> </main> <script> const DURATIONS = { focus: 25 * 60, break: 5 * 60 }; let mode = "focus"; let timeLeft = DURATIONS[mode]; let timerId = null; const time = document.querySelector("#time"); const status = document.querySelector("#status"); const ring = document.querySelector("#ring"); const focusMode = document.querySelector("#focusMode"); const breakMode = document.querySelector("#breakMode"); const start = document.querySelector("#start"); const pause = document.querySelector("#pause"); const reset = document.querySelector("#reset"); function formatTime(totalSeconds) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return String(minutes).padStart(2, "0") + ":" + String(seconds).padStart(2, "0"); } function render() { const total = DURATIONS[mode]; const label = formatTime(timeLeft); time.textContent = label; time.setAttribute( "aria-label", Math.floor(timeLeft / 60) + " minutes and " + (timeLeft % 60) + " seconds remaining" ); ring.style.setProperty("--progress", String((timeLeft / total) * 100)); document.title = label + " · Study Sprint"; } function stopTimer() { if (timerId !== null) { clearInterval(timerId); timerId = null; } } function chooseMode(nextMode) { stopTimer(); mode = nextMode; timeLeft = DURATIONS[mode]; focusMode.setAttribute("aria-pressed", String(mode === "focus")); breakMode.setAttribute("aria-pressed", String(mode === "break")); status.textContent = mode === "focus" ? "Ready for a focused study sprint." : "Ready for a five-minute reset."; render(); } function startTimer() { if (timerId !== null) return; if (timeLeft === 0) timeLeft = DURATIONS[mode]; status.textContent = mode === "focus" ? "Focus sprint in progress." : "Break in progress. Breathe and reset."; timerId = setInterval(function () { timeLeft -= 1; render(); if (timeLeft === 0) { stopTimer(); status.textContent = mode === "focus" ? "Focus sprint complete. Take a five-minute break." : "Break complete. Ready for another focus sprint?"; } }, 1000); } function pauseTimer() { stopTimer(); status.textContent = "Timer paused. Start again when you are ready."; } function resetTimer() { stopTimer(); timeLeft = DURATIONS[mode]; status.textContent = mode === "focus" ? "Focus timer reset." : "Break timer reset."; render(); } focusMode.addEventListener("click", function () { chooseMode("focus"); }); breakMode.addEventListener("click", function () { chooseMode("break"); }); start.addEventListener("click", startTimer); pause.addEventListener("click", pauseTimer); reset.addEventListener("click", resetTimer); render(); </script></body></html>
Step 3: Test it quickly
Save the file and open index.html in a browser. To test without waiting 25 minutes, temporarily change 25 * 60 to 10 and 5 * 60 to 5. Test Start, Pause, Reset, both modes, keyboard navigation, and the mobile layout—then restore the real times.
How the JavaScript works
DURATIONSstores the focus and break lengths in seconds.mode,timeLeft, andtimerIdtrack the app’s current state.setIntervalsubtracts one second every 1,000 milliseconds.render()updates the visible time, accessible label, browser title, and progress ring.stopTimer()clears the interval so multiple timers never run at once.- Button events call small functions for starting, pausing, resetting, and changing modes.
Quick debugging checklist
- If the buttons do nothing, confirm the
<script>section is just before</body>. - If the countdown moves too fast, make sure Start cannot create a second interval.
- If the timer shows
NaN, check every duration and variable name for typing differences. - Open the browser console and read the first error before changing several lines at once.
Customization challenge ladder
Start here
- Change the focus and break lengths.
- Choose a new color palette.
- Rewrite the status messages in your own voice.
Stretch your skills
- Add a third “Long Break” mode.
- Count completed focus sessions.
- Add keyboard shortcuts for Start and Pause.
Advanced extension
- Save only the session count with
localStorage. - Add a settings panel with custom minutes.
- Explain what data should never be collected.
Teacher-ready lesson plan
- 5 minutes — Notice: What makes a timer motivating instead of stressful?
- 10 minutes — Predict: Identify the HTML, CSS, and JavaScript sections before running the code.
- 25 minutes — Build: Paste, save, test, and solve any typing errors.
- 15 minutes — Personalize: Complete at least two customization challenges.
- 10 minutes — Explain: Partners trace what happens from clicking Start to seeing the next second.
- 5 minutes — Reflect: Complete the exit ticket.
Accessibility and responsible-design checklist
- Every action is a real keyboard-operable button.
- The active timer mode uses
aria-pressed, not color alone. - Status changes are announced politely without reading every second aloud.
- Keyboard focus has a clear, high-contrast outline.
- Reduced-motion preferences are respected.
- The starter project stores no name, schedule, task, account, or personal data.
- Students should be encouraged to pause or stop if a timer increases stress.
Frequently asked questions
Is this a real Pomodoro timer?
It uses the familiar 25-minute focus and 5-minute break pattern, but students can change the values. “Study Sprint” keeps the goal simple: choose one manageable task, focus, then reset.
Does it work without internet?
Yes. After the code is saved, the timer runs locally in a modern browser. It uses no framework, API, account, tracking, or external library.
How do students test a 25-minute timer in class?
Temporarily set the durations to 10 and 5 seconds, test every state, then restore 25 and 5 minutes before sharing the finished project.
Exit ticket
- What does
setIntervaldo in this project? - Why must the old interval be cleared before starting another one?
- Which variable tells the page whether it is in focus or break mode?
- What did you change to make the timer useful for you?
- What responsible-design rule would you keep in a future productivity app?
Keep building
Want a simpler first project? Build the HTML & CSS Kindness Card. Ready for another JavaScript interaction? Try the Mood Check-In. Or browse every free coding project.
