pomodoro timer focus productivity countdown session ring svg pomodoro focus timer countdown progress ring sessions productivity work timer build a pomodoro timer show countdown with progress ring focus session tracker
Focus Timer
Fetch pattern JSON:
curl https://webspire.de/patterns/focus-timer/base.json base.html
<!-- Focus Timer — Pomodoro-style countdown with SVG ring -->
<!-- SVG ring: r=44, circumference ≈ 276.5, stroke-dashoffset = circ × (remaining/total) -->
<div class="ws-focus-timer bg-[var(--ws-color-surface)] rounded-2xl border border-slate-200 shadow-sm p-6 flex flex-col items-center gap-5 w-64" id="focus-timer">
<!-- Label -->
<div class="text-center w-full">
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-400">Fokus</p>
<p class="text-sm text-slate-600 mt-1 truncate max-w-[200px]" id="task-label">API-Dokumentation schreiben</p>
</div>
<!-- Ring -->
<div class="relative w-32 h-32 flex items-center justify-center">
<svg class="absolute inset-0 w-full h-full -rotate-90" viewBox="0 0 100 100" aria-hidden="true">
<!-- Track -->
<circle cx="50" cy="50" r="44"
fill="none"
stroke="currentColor"
class="text-slate-100"
stroke-width="6"/>
<!-- Progress -->
<circle cx="50" cy="50" r="44"
fill="none"
stroke="currentColor"
class="text-blue-500"
stroke-width="6"
stroke-linecap="round"
stroke-dasharray="276.46"
stroke-dashoffset="0"
id="ring-progress"/>
</svg>
<!-- Time display -->
<span class="text-3xl font-semibold tabular-nums text-slate-900 relative" id="timer-display"
aria-live="polite" aria-label="Verbleibende Zeit">25:00</span>
</div>
<!-- Session dots -->
<div class="flex items-center gap-2" aria-label="Abgeschlossene Sessions">
<div class="w-2.5 h-2.5 rounded-full bg-blue-500" id="dot-1" title="Session 1"></div>
<div class="w-2.5 h-2.5 rounded-full bg-slate-200" id="dot-2" title="Session 2"></div>
<div class="w-2.5 h-2.5 rounded-full bg-slate-200" id="dot-3" title="Session 3"></div>
<div class="w-2.5 h-2.5 rounded-full bg-slate-200" id="dot-4" title="Session 4"></div>
</div>
<!-- Controls -->
<div class="flex items-center gap-2">
<button id="reset-btn"
class="w-9 h-9 rounded-full border border-slate-200 text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors flex items-center justify-center text-sm"
aria-label="Zurücksetzen">
↺
</button>
<button id="toggle-btn"
class="px-6 py-2.5 rounded-full bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold transition-colors min-w-[90px]"
aria-label="Timer starten">
Starten
</button>
</div>
</div>
<script>
const DURATION = 25 * 60; // seconds
const CIRCUMFERENCE = 2 * Math.PI * 44; // ≈ 276.46
let remaining = DURATION;
let running = false;
let interval = null;
let sessions = 0;
const display = document.getElementById('timer-display');
const ring = document.getElementById('ring-progress');
const toggleBtn= document.getElementById('toggle-btn');
const resetBtn = document.getElementById('reset-btn');
function render() {
const m = String(Math.floor(remaining / 60)).padStart(2, '0');
const s = String(remaining % 60).padStart(2, '0');
display.textContent = `${m}:${s}`;
ring.style.strokeDashoffset = CIRCUMFERENCE * (remaining / DURATION);
toggleBtn.textContent = running ? 'Pause' : (remaining < DURATION ? 'Weiter' : 'Starten');
}
function tick() {
if (remaining <= 0) {
clearInterval(interval);
running = false;
sessions = Math.min(sessions + 1, 4);
for (let i = 1; i <= 4; i++) {
const dot = document.getElementById(`dot-${i}`);
if (dot) dot.className = i <= sessions
? 'w-2.5 h-2.5 rounded-full bg-emerald-500'
: 'w-2.5 h-2.5 rounded-full bg-slate-200';
}
remaining = DURATION;
render();
return;
}
remaining--;
render();
}
toggleBtn?.addEventListener('click', () => {
running = !running;
if (running) {
interval = setInterval(tick, 1000);
} else {
clearInterval(interval);
}
render();
});
resetBtn?.addEventListener('click', () => {
clearInterval(interval);
running = false;
remaining = DURATION;
render();
});
render();
</script>
Details
Responsive Dark Mode Tailwind Only Copy & Paste Requires JS
Stable Published
pomodorotimerfocusproductivitycountdownsessionringsvg
Slots
| Name | Required | Description |
|---|---|---|
| task-label | No | Current task name shown above the ring. Falls back to a placeholder. |
| ring | Yes | SVG circle ring — stroke-dashoffset encodes elapsed progress. |
| controls | Yes | Start/Pause and Reset buttons below the ring. |
| sessions | No | Row of dots showing completed Pomodoro sessions. |
Props
| Name | Type | Default | Description |
|---|---|---|---|
| duration | number | 1500 | Session duration in seconds (default 25 minutes). |
| breakDuration | number | 300 | Short break duration in seconds (default 5 minutes). |
A self-contained focus timer implementing the Pomodoro Technique. An SVG circle ring shrinks as time elapses. Completed sessions are tracked with dot indicators. Controls let the user start, pause, and reset the countdown. Optional task label shows what is being worked on.