← All templates

Pomodoro Timer Chrome extension

Focus timer with work and break cycles, running in the background with a desktop notification.

How to install

  1. Download and unzip the file.
  2. Open chrome://extensions and turn on Developer mode.
  3. Click "Load unpacked" and pick the unzipped folder.

Source code (7 files)

manifest.json
{
  "manifest_version": 3,
  "name": "Pomodoro Timer",
  "version": "1.0.0",
  "description": "Focus timer with work and break cycles, running in the background with a desktop notification.",
  "icons": {
    "16": "icons/icon.svg",
    "48": "icons/icon.svg",
    "128": "icons/icon.svg"
  },
  "action": {
    "default_popup": "popup.html",
    "default_title": "Pomodoro Timer",
    "default_icon": {
      "128": "icons/icon.svg"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [
    "alarms",
    "notifications",
    "storage"
  ]
}
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Pomodoro Timer</title>
<link rel="stylesheet" href="popup.css" />
</head>
<body>
<header><h1>Pomodoro Timer</h1><p class="sub">Built with ManifestGo</p></header>
<main>
<div class="big" id="clock">25:00</div>
  <div class="row"><button class="primary" id="start">Start</button><button id="reset">Reset</button></div>
  <div class="row"><button data-min="25">25m</button><button data-min="5">5m</button><button data-min="15">15m</button></div>
</main>
<script src="popup.js"></script>
</body>
</html>
popup.css
:root { color-scheme: dark; }
* { box-sizing: border-box; }
body {
  margin: 0;
  width: 320px;
  font-family: Inter, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: #1b1b1b;
  color: #f2efe9;
}
header { padding: 14px 16px 8px; }
h1 { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
p.sub { margin: 4px 0 0; font-size: 11px; color: #9a958d; }
main { padding: 10px 16px 16px; display: grid; gap: 10px; }
button {
  font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  border: 1px solid #ffffff1f; background: #ffffff0d; color: #f2efe9;
  border-radius: 10px; padding: 9px 12px; transition: background .15s ease;
}
button:hover { background: #ffffff17; }
button.primary { background: #ef4444; border-color: #ef4444; color: #101010; }
button.primary:hover { filter: brightness(1.08); }
input, select, textarea {
  font: inherit; font-size: 12px; width: 100%; padding: 9px 10px; border-radius: 10px;
  border: 1px solid #ffffff1f; background: #ffffff08; color: #f2efe9; outline: none;
}
input:focus, textarea:focus { border-color: #ef4444; }
ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; max-height: 260px; overflow: auto; }
li { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 8px 10px; border-radius: 10px; background: #ffffff08; }
li span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row { display: flex; gap: 8px; }
.row > * { flex: 1; }
.big { font-size: 34px; font-weight: 700; text-align: center; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.muted { font-size: 11px; color: #9a958d; }
popup.js
const clock = document.getElementById("clock");
let endsAt = 0, minutes = 25, tick;

function fmt(ms) {
  const s = Math.max(0, Math.round(ms / 1000));
  return String(Math.floor(s / 60)).padStart(2, "0") + ":" + String(s % 60).padStart(2, "0");
}

function paint() {
  chrome.storage.local.get({ endsAt: 0, minutes: 25 }, (d) => {
    endsAt = d.endsAt; minutes = d.minutes;
    clock.textContent = endsAt > Date.now() ? fmt(endsAt - Date.now()) : fmt(minutes * 60000);
  });
}

document.getElementById("start").addEventListener("click", () => {
  const at = Date.now() + minutes * 60000;
  chrome.storage.local.set({ endsAt: at });
  chrome.alarms.create("pomodoro", { when: at });
  paint();
});

document.getElementById("reset").addEventListener("click", () => {
  chrome.alarms.clear("pomodoro");
  chrome.storage.local.set({ endsAt: 0 });
  paint();
});

document.querySelectorAll("[data-min]").forEach((b) =>
  b.addEventListener("click", () => {
    chrome.storage.local.set({ minutes: Number(b.dataset.min), endsAt: 0 });
    chrome.alarms.clear("pomodoro");
    paint();
  }),
);

tick = setInterval(paint, 1000);
paint();
window.addEventListener("unload", () => clearInterval(tick));
icons/icon.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128"><rect width="128" height="128" rx="26" fill="#ef4444"/><text x="64" y="90" font-family="Inter, Arial, sans-serif" font-size="66" font-weight="700" text-anchor="middle" fill="#ffffff">P</text></svg>
README.md
# Pomodoro Timer

Focus timer with work and break cycles, running in the background with a desktop notification.

## Install (unpacked)
1. Open `chrome://extensions`.
2. Enable **Developer mode**.
3. Click **Load unpacked** and select this folder.

## Files
- `manifest.json` — Manifest V3 configuration
- `popup.html` / `popup.css` / `popup.js` — toolbar popup UI and logic
- `icons/icon.svg` — source icon (rasterized to PNG on download)
background.js
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name !== "pomodoro") return;
  chrome.storage.local.set({ endsAt: 0 });
  chrome.notifications.create({
    type: "basic",
    iconUrl: "icons/icon.svg",
    title: "Pomodoro complete",
    message: "Time is up — take a break.",
  });
});

More free templates