> Source URL: /demos/pomodoro.tags
---
title: Pomodoro Tag
parseTags: true
---

<div class="p-4 flex relative flex-col rounded-2xl bg-black/10 dark:bg-white/10 
justify-center items-center">
<span class="text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 font-bold font-mono absolute top-2 left-2  text-center">Pomodoro Tag</span>
<pomodoro />
</div>

---

# Pomodoro

A circular countdown timer that alternates between 25-minute work sessions and 5-minute breaks. An SVG ring fills as time elapses, changing color when the mode switches. Pass an `accent` prop with any hex color to change the work-mode ring and button — defaults to Tailwind red-500 (`#ef4444`).

The display is two concentric SVG circles — a gray background track and a colored foreground ring. The SVG is rotated -90° so the ring starts from 12 o'clock rather than 3 o'clock. Inside the ring, absolutely positioned text shows the countdown and current mode. Two buttons below: Start (toggles to Pause/Resume) and Reset.

The container sets `--pom-accent` from the `accent` prop. The ring and start button both reference this variable, so changing it recolors the whole component.

```html
<div
  style="--pom-accent: {{ accent: #ef4444 }}"
  class="inline-flex flex-col items-center gap-3 p-6 rounded-2xl bg-gray-100 dark:bg-gray-800 select-none"
>
  <div class="relative w-40 h-40">
    <svg class="w-full h-full -rotate-90" viewBox="0 0 100 100">
      <circle
        cx="50"
        cy="50"
        r="45"
        fill="none"
        stroke="currentColor"
        stroke-width="4"
        class="text-gray-300 dark:text-gray-600"
      />
      <circle
        data-pom="ring"
        cx="50"
        cy="50"
        r="45"
        fill="none"
        stroke-width="4"
        stroke-dasharray="282.74"
        stroke-dashoffset="0"
        stroke-linecap="round"
      />
    </svg>
    <div class="absolute inset-0 flex flex-col items-center justify-center">
      <span
        data-pom="time"
        class="text-3xl font-mono font-semibold text-gray-800 dark:text-gray-100"
        >25:00</span
      >
      <span
        data-pom="label"
        class="text-xs uppercase tracking-wider text-gray-500 dark:text-gray-400 mt-1"
        >work</span
      >
    </div>
  </div>
  <div class="flex gap-2">
    <button
      data-pom="start"
      class="px-4 py-1.5 text-sm font-medium rounded-lg text-white transition-colors"
    >
      Start
    </button>
    <button
      data-pom="reset"
      class="px-4 py-1.5 text-sm font-medium rounded-lg bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-200 transition-colors"
    >
      Reset
    </button>
  </div>
</div>
```

The ring and button both pull their color from `--pom-accent`. A second variable, `--pom-break`, defines the break-mode color (green). The ring transitions both its `stroke-dashoffset` (the countdown sweep) and `stroke` (the color change between modes) so neither jumps abruptly. The button uses `filter` on hover so it darkens regardless of what accent color is set.

```css
[data-tag="pomodoro"] {
  --pom-break: #22c55e;
}

[data-pom="ring"] {
  stroke: var(--pom-accent);
  transition:
    stroke-dashoffset 500ms ease,
    stroke 300ms ease;
}

[data-pom="start"] {
  background-color: var(--pom-accent);
}

[data-pom="start"]:hover {
  filter: brightness(0.9);
}
```

The timer is a small state machine: `remaining` seconds, `total` duration for the current mode, an `interval` handle, and a work/break boolean. Each tick decrements `remaining` and re-renders. When it hits zero, the mode flips and the duration resets.

At init, the script finds all uninitialized pomodoro instances on the page and sets up each one independently — so multiple timers can run side by side with different accent colors. For each instance it reads `--pom-accent` and `--pom-break` from the computed styles to know which colors to swap between. The `render` function calculates how much ring to reveal — circumference times the fraction elapsed — and sets that as `strokeDashoffset`. It also swaps the ring's stroke between the accent and break colors.

```js
const WORK = 25 * 60
const BREAK = 5 * 60
const CIRCUMFERENCE = 2 * Math.PI * 45

document
  .querySelectorAll('[data-tag="pomodoro"]:not([data-pom-init])')
  .forEach(function (el) {
    el.setAttribute("data-pom-init", "")

    const styles = getComputedStyle(el)
    const accentColor = styles.getPropertyValue("--pom-accent").trim()
    const breakColor = styles.getPropertyValue("--pom-break").trim()

    const ring = el.querySelector('[data-pom="ring"]')
    const time = el.querySelector('[data-pom="time"]')
    const label = el.querySelector('[data-pom="label"]')
    const btn = el.querySelector('[data-pom="start"]')
    const resetBtn = el.querySelector('[data-pom="reset"]')

    let remaining = WORK
    let total = WORK
    let interval = null
    let isWork = true

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

    function render() {
      time.textContent = fmt(remaining)
      const offset = CIRCUMFERENCE * (1 - remaining / total)
      ring.style.strokeDashoffset = offset
      ring.style.stroke = isWork ? accentColor : breakColor
      label.textContent = isWork ? "work" : "break"
    }

    function tick() {
      remaining--
      if (!(remaining > 0)) {
        clearInterval(interval)
        interval = null
        isWork = !isWork
        total = isWork ? WORK : BREAK
        remaining = total
        btn.textContent = "Start"
        render()
        return
      }
      render()
    }

    btn.addEventListener("click", function () {
      if (interval) {
        clearInterval(interval)
        interval = null
        btn.textContent = "Resume"
      } else {
        interval = setInterval(tick, 1000)
        btn.textContent = "Pause"
      }
    })

    resetBtn.addEventListener("click", function () {
      clearInterval(interval)
      interval = null
      isWork = true
      total = WORK
      remaining = WORK
      btn.textContent = "Start"
      render()
    })

    ring.style.strokeDasharray = CIRCUMFERENCE
    render()
  })
```

---

### Demo

<div class="flex  gap-4 items-start">
  <pomodoro />
  <pomodoro accent="#0ea5e9" />
  <pomodoro accent="#ec4899" />
</div>


---

## Backlinks

The following sources link to this document:

- [Pomodoro Timers](/index.path.llm.md)
