Open demo

CSS code

HTML
<main class="ct ct-focus-theme"><div class="ct-panel"><p class="ct-kicker">A little room for focused work</p><h1>Make time for one thing.</h1><section class="ct-timer" data-duration-timer><h2>Focus session</h2><label>Minutes<input data-minutes type="number" min="1" max="180" step="1" value="25"></label><p class="ct-duration" data-duration role="timer" aria-live="off">00:00:00</p><div class="ct-actions"><button type="button" data-start>Start</button><button type="button" class="secondary" data-pause disabled>Pause</button><button type="button" class="secondary" data-reset>Reset</button></div><p class="ct-status" data-duration-status role="status"></p></section><p class="ct-note">Choose 1–180 minutes. Pause retains the remaining duration; Reset uses the selected duration. This timer is not a background alarm service.</p></div></main>
CSS
.ct,.ct *{box-sizing:border-box}.ct [hidden]{display:none!important}.ct{font:17px/1.6 system-ui,-apple-system,'Segoe UI',sans-serif;color:#24323d;width:100%;max-width:850px;margin:auto;--accent:#235749;--paper:#fff;--line:#c9d4d0}.ct h1,.ct h2,.ct p{margin:0}.ct h1{font-size:clamp(30px,5vw,48px);line-height:1.13;letter-spacing:-.035em;margin-bottom:18px}.ct h2{font-size:23px;line-height:1.3;margin-bottom:12px}.ct p+p{margin-top:12px}.ct a{color:inherit;text-underline-offset:4px}.ct-kicker{font-size:14px;text-transform:uppercase;letter-spacing:.1em;font-weight:700;margin-bottom:14px!important}.ct-note{font-size:14px;margin-top:18px!important;color:#53636d}.ct-status{min-height:28px;margin-top:16px!important;font-size:16px}.ct :is(button,input,select){font:inherit;min-height:46px}.ct button{border:1px solid var(--accent);background:var(--accent);color:white;padding:9px 17px;border-radius:3px;cursor:pointer}.ct button.secondary{background:white;color:var(--accent)}.ct button:disabled{opacity:.6;cursor:not-allowed}.ct input,.ct select{width:100%;min-width:0;border:1px solid #7a8d96;background:white;color:#24323d;padding:8px 12px;border-radius:2px}.ct label{display:grid;gap:7px;font-weight:600}.ct :focus-visible{outline:3px solid #c77310;outline-offset:4px}.ct-units{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-top:25px}.ct-unit{min-width:0;padding:18px 8px;background:var(--paper);border:1px solid var(--line);text-align:center;color:#24323d}.ct-unit b{display:block;font-size:clamp(23px,4vw,42px);font-weight:600;line-height:1.2;font-variant-numeric:tabular-nums}.ct-unit span{display:block;margin-top:5px;font-size:14px}.ct-date{font-size:15px;margin-top:17px!important}.ct-panel{padding:38px;background:var(--paper);border:1px solid var(--line)}.ct-progress{width:100%;height:14px;accent-color:var(--accent);margin-top:30px}.ct-percentage{font-size:15px;margin-top:10px!important}.ct-events{display:flex;flex-wrap:wrap;gap:10px;margin:25px 0}.ct-events button[aria-pressed=true]{background:var(--accent);color:white}.ct-events button[aria-pressed=false]{background:white;color:var(--accent)}.ct-form{display:grid;grid-template-columns:1.4fr 1fr auto;gap:16px;align-items:end;margin-top:25px}.ct-registration fieldset{display:grid;grid-template-columns:1fr auto;align-items:end;gap:14px;border:0;padding:0;margin:25px 0 0;min-width:0}.ct-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}.ct-duration{font-size:clamp(38px,8vw,66px);font-weight:600;font-variant-numeric:tabular-nums;line-height:1.2;letter-spacing:-.035em;margin:20px 0!important}.ct-timer{padding:25px;border:1px solid var(--line);background:#fff}.ct-timer label{max-width:210px}.ct-timers{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.ct-timers .ct-duration{font-size:32px}.ct-timers .ct-actions{gap:7px}.ct-timers button{padding:7px 10px;font-size:15px}.ct-timers .ct-status{font-size:14px}.ct [data-phase=ended] .ct-units{opacity:.68}.ct-ended-copy{display:none}.ct [data-phase=ended] .ct-ended-copy{display:block}.ct [data-phase=ended] .ct-upcoming-copy{display:none}.ct-banner{display:grid;grid-template-columns:1fr auto;gap:22px;background:#203a45;color:#fff;padding:28px;border-left:6px solid #d6bb7a}.ct-banner .ct-units{margin-top:18px;gap:10px;max-width:540px}.ct-banner .ct-unit{background:transparent;border-color:#8dabb6;color:white;padding:10px}.ct-banner .ct-unit b{font-size:27px}.ct-banner button{background:transparent;color:white;border-color:#8dabb6;min-width:46px;align-self:start}.ct-banner .ct-date{color:#e0e9ec}.ct-show-banner{margin-top:20px}.ct-progress-theme{--accent:#73512a;--paper:#fffdf8;--line:#d7cab5}.ct-progress-theme h1{font-family:Georgia,serif;font-weight:400}.ct-agenda-theme{--accent:#354c79;--paper:#fff;--line:#cbd3e1}.ct-zone-theme{--accent:#1e625f;--paper:#f8fcfa}.ct-registration-theme{--accent:#742d3f;--paper:#fffaf8;--line:#ddc4bd}.ct-registration-theme h1{font-family:Georgia,serif;font-weight:400}.ct-focus-theme{--accent:#343c5b;--paper:#f4f5fa}.ct-kitchen-theme{--accent:#315e47;--paper:#fafaf4}.ct-state-theme{--accent:#7a4b2f;--paper:#fffaf3}.ct-state-theme h1{font-family:Georgia,serif;font-weight:400}@media(max-width:650px){.ct-panel{padding:23px}.ct-units{gap:7px}.ct-unit{padding:13px 4px}.ct-unit b{font-size:21px}.ct-unit span{font-size:12px}.ct-form{grid-template-columns:1fr}.ct-registration fieldset{grid-template-columns:1fr}.ct-timers{grid-template-columns:1fr}.ct-banner{padding:22px;gap:12px}.ct-banner .ct-unit{padding:9px 3px}.ct-banner .ct-unit b{font-size:20px}.ct-banner .ct-units{grid-template-columns:repeat(2,minmax(0,1fr))}.ct-actions button{min-width:85px}.ct h1{font-size:32px}}@media(prefers-reduced-motion:reduce){.ct *{animation:none!important;transition:none!important}}
JavaScript
(() => {
  const format = seconds => {const s=Math.max(0,Math.ceil(seconds));return [Math.floor(s/86400),Math.floor(s/3600)%24,Math.floor(s/60)%60,s%60];};
  const valid = value => typeof value==='string' && /(?:Z|[+-]\d\d:\d\d)$/.test(value) && Number.isFinite(Date.parse(value));
  document.querySelectorAll('[data-countdown]').forEach(root => {
    let ended=false; const status=root.querySelector('[data-status]');
    function draw(){const raw=root.dataset.deadline;if(!valid(raw)){root.dataset.phase='invalid';const f=root.querySelector('[data-registration] fieldset');if(f)f.disabled=true;root.querySelectorAll('[data-value]').forEach(e=>e.textContent='—');status.textContent='Set a valid ISO deadline with Z or an explicit UTC offset.';return;}
      const target=Date.parse(raw),left=Math.max(0,target-Date.now()),values=format(left/1000);root.querySelectorAll('[data-value]').forEach(el=>el.textContent=String(values[Number(el.dataset.value)]).padStart(2,'0'));
      const timer=root.querySelector('[role=timer]');if(timer)timer.setAttribute('aria-label',values[0]+' days, '+values[1]+' hours, '+values[2]+' minutes, '+values[3]+' seconds remaining');
      const date=root.querySelector('[data-date]');if(date){let zone=root.dataset.zone||'UTC';try{date.textContent=new Intl.DateTimeFormat('en-GB',{dateStyle:'long',timeStyle:'short',timeZone:zone}).format(new Date(target))+' · '+zone;}catch{root.dataset.zone='UTC';date.textContent=new Date(target).toISOString()+' · UTC';}}
      const progress=root.querySelector('progress');if(progress){const start=Date.parse(root.dataset.start||'');if(!Number.isFinite(start)||start>=target){progress.value=0;root.querySelector('[data-percent]').textContent='Check the start and end dates.';}else{const percent=Math.max(0,Math.min(100,(Date.now()-start)/(target-start)*100));progress.value=percent;root.querySelector('[data-percent]').textContent=Math.round(percent)+'% of the example window elapsed';}}
      const form=root.querySelector('[data-registration]');if(form)form.querySelector('fieldset').disabled=left===0;
      root.dataset.phase=left===0?'ended':'upcoming';if(left===0&&!ended){status.textContent=root.dataset.endMessage||'The example deadline has passed.';ended=true;}else if(left>0&&ended){status.textContent='Countdown updated.';ended=false;}
    }
    root.querySelectorAll('[data-event-target]').forEach(button=>button.addEventListener('click',()=>{root.dataset.deadline=button.dataset.eventTarget;root.querySelector('[data-event-title]').textContent=button.textContent.trim();root.querySelectorAll('[data-event-target]').forEach(e=>e.setAttribute('aria-pressed',String(e===button)));status.textContent='Selected '+button.textContent.trim()+'.';ended=false;draw();}));
    const configure=root.querySelector('[data-configure]');if(configure)configure.addEventListener('submit',event=>{event.preventDefault();if(!configure.reportValidity())return;const value=configure.elements.deadline.value;if(!/^\d{4}-\d\d-\d\dT\d\d:\d\d$/.test(value)){status.textContent='Enter a complete UTC date and time.';return;}const iso=value+':00Z';if(!valid(iso)){status.textContent='Enter a valid UTC date and time.';return;}root.dataset.deadline=iso;root.dataset.zone=configure.elements.zone.value;status.textContent='Deadline updated. Only the display time zone changes.';ended=false;draw();});
    const close=root.querySelector('[data-dismiss]'),banner=root.querySelector('[data-banner]'),show=root.querySelector('[data-show-banner]');if(close&&banner&&show){close.addEventListener('click',()=>{banner.hidden=true;show.hidden=false;show.focus();});show.addEventListener('click',()=>{banner.hidden=false;show.hidden=true;close.focus();});}
    const registration=root.querySelector('[data-registration]');if(registration)registration.addEventListener('submit',event=>{event.preventDefault();draw();if(root.dataset.phase==='ended'||!registration.reportValidity())return;status.textContent='Demo validation passed. No email was sent or registration stored.';});
    root.querySelectorAll('[data-preview-state]').forEach(button=>button.addEventListener('click',()=>{root.dataset.deadline=new Date(Date.now()+(button.dataset.previewState==='ended'?-1000:3600000)).toISOString();status.textContent='Previewing the '+button.dataset.previewState+' state with a sample deadline.';ended=false;draw();}));
    draw();setInterval(draw,1000);document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw();});
  });
  document.querySelectorAll('[data-duration-timer]').forEach(root=>{
    const input=root.querySelector('[data-minutes]'),readout=root.querySelector('[data-duration]'),start=root.querySelector('[data-start]'),pause=root.querySelector('[data-pause]'),reset=root.querySelector('[data-reset]'),status=root.querySelector('[data-duration-status]');let remaining=Number(input.value)*60000,deadline=0,running=false,finished=false;
    function draw(){if(running)remaining=Math.max(0,deadline-Date.now());const sec=Math.ceil(remaining/1000);readout.textContent=[Math.floor(sec/3600),Math.floor(sec/60)%60,sec%60].map(n=>String(n).padStart(2,'0')).join(':');if(running&&remaining===0){running=false;finished=true;status.textContent='Timer finished.';}start.disabled=running||finished;pause.disabled=!running;input.disabled=running;start.textContent=remaining<Number(input.value)*60000&&!finished?'Resume':'Start';}
    start.addEventListener('click',()=>{if(!input.reportValidity())return;if(!running&&!finished){deadline=Date.now()+remaining;running=true;status.textContent='Timer running.';draw();}});pause.addEventListener('click',()=>{if(running){remaining=Math.max(0,deadline-Date.now());running=false;status.textContent='Timer paused.';draw();}});function clear(){running=false;finished=false;remaining=Number(input.value)*60000;status.textContent='Timer reset.';draw();}reset.addEventListener('click',clear);input.addEventListener('change',()=>{if(input.checkValidity())clear();});draw();setInterval(draw,250);document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw();});
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<main class="ct ct-focus-theme mx-auto"><div class="ct-panel"><p class="ct-kicker text-uppercase">A little room for focused work</p><h1>Make time for one thing.</h1><section class="ct-timer" data-duration-timer><h2>Focus session</h2><label>Minutes<input data-minutes type="number" min="1" max="180" step="1" value="25"></label><p class="ct-duration" data-duration role="timer" aria-live="off">00:00:00</p><div class="ct-actions d-flex flex-wrap"><button type="button" data-start>Start</button><button type="button" class="secondary" data-pause disabled>Pause</button><button type="button" class="secondary" data-reset>Reset</button></div><p class="ct-status" data-duration-status role="status"></p></section><p class="ct-note">Choose 1–180 minutes. Pause retains the remaining duration; Reset uses the selected duration. This timer is not a background alarm service.</p></div></main>
CSS
.ct,.ct *{box-sizing:border-box}.ct [hidden]{display:none!important}.ct{font:17px/1.6 system-ui,-apple-system,'Segoe UI',sans-serif;color:#24323d;width:100%;max-width:850px;margin:auto;--accent:#235749;--paper:#fff;--line:#c9d4d0}.ct h1,.ct h2,.ct p{margin:0}.ct h1{font-size:clamp(30px,5vw,48px);line-height:1.13;letter-spacing:-.035em;margin-bottom:18px}.ct h2{font-size:23px;line-height:1.3;margin-bottom:12px}.ct p+p{margin-top:12px}.ct a{color:inherit;text-underline-offset:4px}.ct-kicker{font-size:14px;text-transform:uppercase;letter-spacing:.1em;font-weight:700;margin-bottom:14px!important}.ct-note{font-size:14px;margin-top:18px!important;color:#53636d}.ct-status{min-height:28px;margin-top:16px!important;font-size:16px}.ct :is(button,input,select){font:inherit;min-height:46px}.ct button{border:1px solid var(--accent);background:var(--accent);color:white;padding:9px 17px;border-radius:3px;cursor:pointer}.ct button.secondary{background:white;color:var(--accent)}.ct button:disabled{opacity:.6;cursor:not-allowed}.ct input,.ct select{width:100%;min-width:0;border:1px solid #7a8d96;background:white;color:#24323d;padding:8px 12px;border-radius:2px}.ct label{display:grid;gap:7px;font-weight:600}.ct :focus-visible{outline:3px solid #c77310;outline-offset:4px}.ct-units{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-top:25px}.ct-unit{min-width:0;padding:18px 8px;background:var(--paper);border:1px solid var(--line);text-align:center;color:#24323d}.ct-unit b{display:block;font-size:clamp(23px,4vw,42px);font-weight:600;line-height:1.2;font-variant-numeric:tabular-nums}.ct-unit span{display:block;margin-top:5px;font-size:14px}.ct-date{font-size:15px;margin-top:17px!important}.ct-panel{padding:38px;background:var(--paper);border:1px solid var(--line)}.ct-progress{width:100%;height:14px;accent-color:var(--accent);margin-top:30px}.ct-percentage{font-size:15px;margin-top:10px!important}.ct-events{display:flex;flex-wrap:wrap;gap:10px;margin:25px 0}.ct-events button[aria-pressed=true]{background:var(--accent);color:white}.ct-events button[aria-pressed=false]{background:white;color:var(--accent)}.ct-form{display:grid;grid-template-columns:1.4fr 1fr auto;gap:16px;align-items:end;margin-top:25px}.ct-registration fieldset{display:grid;grid-template-columns:1fr auto;align-items:end;gap:14px;border:0;padding:0;margin:25px 0 0;min-width:0}.ct-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}.ct-duration{font-size:clamp(38px,8vw,66px);font-weight:600;font-variant-numeric:tabular-nums;line-height:1.2;letter-spacing:-.035em;margin:20px 0!important}.ct-timer{padding:25px;border:1px solid var(--line);background:#fff}.ct-timer label{max-width:210px}.ct-timers{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.ct-timers .ct-duration{font-size:32px}.ct-timers .ct-actions{gap:7px}.ct-timers button{padding:7px 10px;font-size:15px}.ct-timers .ct-status{font-size:14px}.ct [data-phase=ended] .ct-units{opacity:.68}.ct-ended-copy{display:none}.ct [data-phase=ended] .ct-ended-copy{display:block}.ct [data-phase=ended] .ct-upcoming-copy{display:none}.ct-banner{display:grid;grid-template-columns:1fr auto;gap:22px;background:#203a45;color:#fff;padding:28px;border-left:6px solid #d6bb7a}.ct-banner .ct-units{margin-top:18px;gap:10px;max-width:540px}.ct-banner .ct-unit{background:transparent;border-color:#8dabb6;color:white;padding:10px}.ct-banner .ct-unit b{font-size:27px}.ct-banner button{background:transparent;color:white;border-color:#8dabb6;min-width:46px;align-self:start}.ct-banner .ct-date{color:#e0e9ec}.ct-show-banner{margin-top:20px}.ct-progress-theme{--accent:#73512a;--paper:#fffdf8;--line:#d7cab5}.ct-progress-theme h1{font-family:Georgia,serif;font-weight:400}.ct-agenda-theme{--accent:#354c79;--paper:#fff;--line:#cbd3e1}.ct-zone-theme{--accent:#1e625f;--paper:#f8fcfa}.ct-registration-theme{--accent:#742d3f;--paper:#fffaf8;--line:#ddc4bd}.ct-registration-theme h1{font-family:Georgia,serif;font-weight:400}.ct-focus-theme{--accent:#343c5b;--paper:#f4f5fa}.ct-kitchen-theme{--accent:#315e47;--paper:#fafaf4}.ct-state-theme{--accent:#7a4b2f;--paper:#fffaf3}.ct-state-theme h1{font-family:Georgia,serif;font-weight:400}@media(max-width:650px){.ct-panel{padding:23px}.ct-units{gap:7px}.ct-unit{padding:13px 4px}.ct-unit b{font-size:21px}.ct-unit span{font-size:12px}.ct-form{grid-template-columns:1fr}.ct-registration fieldset{grid-template-columns:1fr}.ct-timers{grid-template-columns:1fr}.ct-banner{padding:22px;gap:12px}.ct-banner .ct-unit{padding:9px 3px}.ct-banner .ct-unit b{font-size:20px}.ct-banner .ct-units{grid-template-columns:repeat(2,minmax(0,1fr))}.ct-actions button{min-width:85px}.ct h1{font-size:32px}}@media(prefers-reduced-motion:reduce){.ct *{animation:none!important;transition:none!important}}
JavaScript
(() => {
  const format = seconds => {const s=Math.max(0,Math.ceil(seconds));return [Math.floor(s/86400),Math.floor(s/3600)%24,Math.floor(s/60)%60,s%60];};
  const valid = value => typeof value==='string' && /(?:Z|[+-]\d\d:\d\d)$/.test(value) && Number.isFinite(Date.parse(value));
  document.querySelectorAll('[data-countdown]').forEach(root => {
    let ended=false; const status=root.querySelector('[data-status]');
    function draw(){const raw=root.dataset.deadline;if(!valid(raw)){root.dataset.phase='invalid';const f=root.querySelector('[data-registration] fieldset');if(f)f.disabled=true;root.querySelectorAll('[data-value]').forEach(e=>e.textContent='—');status.textContent='Set a valid ISO deadline with Z or an explicit UTC offset.';return;}
      const target=Date.parse(raw),left=Math.max(0,target-Date.now()),values=format(left/1000);root.querySelectorAll('[data-value]').forEach(el=>el.textContent=String(values[Number(el.dataset.value)]).padStart(2,'0'));
      const timer=root.querySelector('[role=timer]');if(timer)timer.setAttribute('aria-label',values[0]+' days, '+values[1]+' hours, '+values[2]+' minutes, '+values[3]+' seconds remaining');
      const date=root.querySelector('[data-date]');if(date){let zone=root.dataset.zone||'UTC';try{date.textContent=new Intl.DateTimeFormat('en-GB',{dateStyle:'long',timeStyle:'short',timeZone:zone}).format(new Date(target))+' · '+zone;}catch{root.dataset.zone='UTC';date.textContent=new Date(target).toISOString()+' · UTC';}}
      const progress=root.querySelector('progress');if(progress){const start=Date.parse(root.dataset.start||'');if(!Number.isFinite(start)||start>=target){progress.value=0;root.querySelector('[data-percent]').textContent='Check the start and end dates.';}else{const percent=Math.max(0,Math.min(100,(Date.now()-start)/(target-start)*100));progress.value=percent;root.querySelector('[data-percent]').textContent=Math.round(percent)+'% of the example window elapsed';}}
      const form=root.querySelector('[data-registration]');if(form)form.querySelector('fieldset').disabled=left===0;
      root.dataset.phase=left===0?'ended':'upcoming';if(left===0&&!ended){status.textContent=root.dataset.endMessage||'The example deadline has passed.';ended=true;}else if(left>0&&ended){status.textContent='Countdown updated.';ended=false;}
    }
    root.querySelectorAll('[data-event-target]').forEach(button=>button.addEventListener('click',()=>{root.dataset.deadline=button.dataset.eventTarget;root.querySelector('[data-event-title]').textContent=button.textContent.trim();root.querySelectorAll('[data-event-target]').forEach(e=>e.setAttribute('aria-pressed',String(e===button)));status.textContent='Selected '+button.textContent.trim()+'.';ended=false;draw();}));
    const configure=root.querySelector('[data-configure]');if(configure)configure.addEventListener('submit',event=>{event.preventDefault();if(!configure.reportValidity())return;const value=configure.elements.deadline.value;if(!/^\d{4}-\d\d-\d\dT\d\d:\d\d$/.test(value)){status.textContent='Enter a complete UTC date and time.';return;}const iso=value+':00Z';if(!valid(iso)){status.textContent='Enter a valid UTC date and time.';return;}root.dataset.deadline=iso;root.dataset.zone=configure.elements.zone.value;status.textContent='Deadline updated. Only the display time zone changes.';ended=false;draw();});
    const close=root.querySelector('[data-dismiss]'),banner=root.querySelector('[data-banner]'),show=root.querySelector('[data-show-banner]');if(close&&banner&&show){close.addEventListener('click',()=>{banner.hidden=true;show.hidden=false;show.focus();});show.addEventListener('click',()=>{banner.hidden=false;show.hidden=true;close.focus();});}
    const registration=root.querySelector('[data-registration]');if(registration)registration.addEventListener('submit',event=>{event.preventDefault();draw();if(root.dataset.phase==='ended'||!registration.reportValidity())return;status.textContent='Demo validation passed. No email was sent or registration stored.';});
    root.querySelectorAll('[data-preview-state]').forEach(button=>button.addEventListener('click',()=>{root.dataset.deadline=new Date(Date.now()+(button.dataset.previewState==='ended'?-1000:3600000)).toISOString();status.textContent='Previewing the '+button.dataset.previewState+' state with a sample deadline.';ended=false;draw();}));
    draw();setInterval(draw,1000);document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw();});
  });
  document.querySelectorAll('[data-duration-timer]').forEach(root=>{
    const input=root.querySelector('[data-minutes]'),readout=root.querySelector('[data-duration]'),start=root.querySelector('[data-start]'),pause=root.querySelector('[data-pause]'),reset=root.querySelector('[data-reset]'),status=root.querySelector('[data-duration-status]');let remaining=Number(input.value)*60000,deadline=0,running=false,finished=false;
    function draw(){if(running)remaining=Math.max(0,deadline-Date.now());const sec=Math.ceil(remaining/1000);readout.textContent=[Math.floor(sec/3600),Math.floor(sec/60)%60,sec%60].map(n=>String(n).padStart(2,'0')).join(':');if(running&&remaining===0){running=false;finished=true;status.textContent='Timer finished.';}start.disabled=running||finished;pause.disabled=!running;input.disabled=running;start.textContent=remaining<Number(input.value)*60000&&!finished?'Resume':'Start';}
    start.addEventListener('click',()=>{if(!input.reportValidity())return;if(!running&&!finished){deadline=Date.now()+remaining;running=true;status.textContent='Timer running.';draw();}});pause.addEventListener('click',()=>{if(running){remaining=Math.max(0,deadline-Date.now());running=false;status.textContent='Timer paused.';draw();}});function clear(){running=false;finished=false;remaining=Number(input.value)*60000;status.textContent='Timer reset.';draw();}reset.addEventListener('click',clear);input.addEventListener('change',()=>{if(input.checkValidity())clear();});draw();setInterval(draw,250);document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw();});
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<main class="ct ct-focus-theme mx-auto w-full font-sans"><div class="ct-panel"><p class="ct-kicker uppercase tracking-widest">A little room for focused work</p><h1>Make time for one thing.</h1><section class="ct-timer" data-duration-timer><h2>Focus session</h2><label>Minutes<input data-minutes type="number" min="1" max="180" step="1" value="25"></label><p class="ct-duration" data-duration role="timer" aria-live="off">00:00:00</p><div class="ct-actions flex flex-wrap"><button type="button" data-start>Start</button><button type="button" class="secondary" data-pause disabled>Pause</button><button type="button" class="secondary" data-reset>Reset</button></div><p class="ct-status" data-duration-status role="status"></p></section><p class="ct-note">Choose 1–180 minutes. Pause retains the remaining duration; Reset uses the selected duration. This timer is not a background alarm service.</p></div></main>
CSS
.ct,.ct *{box-sizing:border-box}.ct [hidden]{display:none!important}.ct{font:17px/1.6 system-ui,-apple-system,'Segoe UI',sans-serif;color:#24323d;width:100%;max-width:850px;margin:auto;--accent:#235749;--paper:#fff;--line:#c9d4d0}.ct h1,.ct h2,.ct p{margin:0}.ct h1{font-size:clamp(30px,5vw,48px);line-height:1.13;letter-spacing:-.035em;margin-bottom:18px}.ct h2{font-size:23px;line-height:1.3;margin-bottom:12px}.ct p+p{margin-top:12px}.ct a{color:inherit;text-underline-offset:4px}.ct-kicker{font-size:14px;text-transform:uppercase;letter-spacing:.1em;font-weight:700;margin-bottom:14px!important}.ct-note{font-size:14px;margin-top:18px!important;color:#53636d}.ct-status{min-height:28px;margin-top:16px!important;font-size:16px}.ct :is(button,input,select){font:inherit;min-height:46px}.ct button{border:1px solid var(--accent);background:var(--accent);color:white;padding:9px 17px;border-radius:3px;cursor:pointer}.ct button.secondary{background:white;color:var(--accent)}.ct button:disabled{opacity:.6;cursor:not-allowed}.ct input,.ct select{width:100%;min-width:0;border:1px solid #7a8d96;background:white;color:#24323d;padding:8px 12px;border-radius:2px}.ct label{display:grid;gap:7px;font-weight:600}.ct :focus-visible{outline:3px solid #c77310;outline-offset:4px}.ct-units{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-top:25px}.ct-unit{min-width:0;padding:18px 8px;background:var(--paper);border:1px solid var(--line);text-align:center;color:#24323d}.ct-unit b{display:block;font-size:clamp(23px,4vw,42px);font-weight:600;line-height:1.2;font-variant-numeric:tabular-nums}.ct-unit span{display:block;margin-top:5px;font-size:14px}.ct-date{font-size:15px;margin-top:17px!important}.ct-panel{padding:38px;background:var(--paper);border:1px solid var(--line)}.ct-progress{width:100%;height:14px;accent-color:var(--accent);margin-top:30px}.ct-percentage{font-size:15px;margin-top:10px!important}.ct-events{display:flex;flex-wrap:wrap;gap:10px;margin:25px 0}.ct-events button[aria-pressed=true]{background:var(--accent);color:white}.ct-events button[aria-pressed=false]{background:white;color:var(--accent)}.ct-form{display:grid;grid-template-columns:1.4fr 1fr auto;gap:16px;align-items:end;margin-top:25px}.ct-registration fieldset{display:grid;grid-template-columns:1fr auto;align-items:end;gap:14px;border:0;padding:0;margin:25px 0 0;min-width:0}.ct-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}.ct-duration{font-size:clamp(38px,8vw,66px);font-weight:600;font-variant-numeric:tabular-nums;line-height:1.2;letter-spacing:-.035em;margin:20px 0!important}.ct-timer{padding:25px;border:1px solid var(--line);background:#fff}.ct-timer label{max-width:210px}.ct-timers{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.ct-timers .ct-duration{font-size:32px}.ct-timers .ct-actions{gap:7px}.ct-timers button{padding:7px 10px;font-size:15px}.ct-timers .ct-status{font-size:14px}.ct [data-phase=ended] .ct-units{opacity:.68}.ct-ended-copy{display:none}.ct [data-phase=ended] .ct-ended-copy{display:block}.ct [data-phase=ended] .ct-upcoming-copy{display:none}.ct-banner{display:grid;grid-template-columns:1fr auto;gap:22px;background:#203a45;color:#fff;padding:28px;border-left:6px solid #d6bb7a}.ct-banner .ct-units{margin-top:18px;gap:10px;max-width:540px}.ct-banner .ct-unit{background:transparent;border-color:#8dabb6;color:white;padding:10px}.ct-banner .ct-unit b{font-size:27px}.ct-banner button{background:transparent;color:white;border-color:#8dabb6;min-width:46px;align-self:start}.ct-banner .ct-date{color:#e0e9ec}.ct-show-banner{margin-top:20px}.ct-progress-theme{--accent:#73512a;--paper:#fffdf8;--line:#d7cab5}.ct-progress-theme h1{font-family:Georgia,serif;font-weight:400}.ct-agenda-theme{--accent:#354c79;--paper:#fff;--line:#cbd3e1}.ct-zone-theme{--accent:#1e625f;--paper:#f8fcfa}.ct-registration-theme{--accent:#742d3f;--paper:#fffaf8;--line:#ddc4bd}.ct-registration-theme h1{font-family:Georgia,serif;font-weight:400}.ct-focus-theme{--accent:#343c5b;--paper:#f4f5fa}.ct-kitchen-theme{--accent:#315e47;--paper:#fafaf4}.ct-state-theme{--accent:#7a4b2f;--paper:#fffaf3}.ct-state-theme h1{font-family:Georgia,serif;font-weight:400}@media(max-width:650px){.ct-panel{padding:23px}.ct-units{gap:7px}.ct-unit{padding:13px 4px}.ct-unit b{font-size:21px}.ct-unit span{font-size:12px}.ct-form{grid-template-columns:1fr}.ct-registration fieldset{grid-template-columns:1fr}.ct-timers{grid-template-columns:1fr}.ct-banner{padding:22px;gap:12px}.ct-banner .ct-unit{padding:9px 3px}.ct-banner .ct-unit b{font-size:20px}.ct-banner .ct-units{grid-template-columns:repeat(2,minmax(0,1fr))}.ct-actions button{min-width:85px}.ct h1{font-size:32px}}@media(prefers-reduced-motion:reduce){.ct *{animation:none!important;transition:none!important}}
JavaScript
(() => {
  const format = seconds => {const s=Math.max(0,Math.ceil(seconds));return [Math.floor(s/86400),Math.floor(s/3600)%24,Math.floor(s/60)%60,s%60];};
  const valid = value => typeof value==='string' && /(?:Z|[+-]\d\d:\d\d)$/.test(value) && Number.isFinite(Date.parse(value));
  document.querySelectorAll('[data-countdown]').forEach(root => {
    let ended=false; const status=root.querySelector('[data-status]');
    function draw(){const raw=root.dataset.deadline;if(!valid(raw)){root.dataset.phase='invalid';const f=root.querySelector('[data-registration] fieldset');if(f)f.disabled=true;root.querySelectorAll('[data-value]').forEach(e=>e.textContent='—');status.textContent='Set a valid ISO deadline with Z or an explicit UTC offset.';return;}
      const target=Date.parse(raw),left=Math.max(0,target-Date.now()),values=format(left/1000);root.querySelectorAll('[data-value]').forEach(el=>el.textContent=String(values[Number(el.dataset.value)]).padStart(2,'0'));
      const timer=root.querySelector('[role=timer]');if(timer)timer.setAttribute('aria-label',values[0]+' days, '+values[1]+' hours, '+values[2]+' minutes, '+values[3]+' seconds remaining');
      const date=root.querySelector('[data-date]');if(date){let zone=root.dataset.zone||'UTC';try{date.textContent=new Intl.DateTimeFormat('en-GB',{dateStyle:'long',timeStyle:'short',timeZone:zone}).format(new Date(target))+' · '+zone;}catch{root.dataset.zone='UTC';date.textContent=new Date(target).toISOString()+' · UTC';}}
      const progress=root.querySelector('progress');if(progress){const start=Date.parse(root.dataset.start||'');if(!Number.isFinite(start)||start>=target){progress.value=0;root.querySelector('[data-percent]').textContent='Check the start and end dates.';}else{const percent=Math.max(0,Math.min(100,(Date.now()-start)/(target-start)*100));progress.value=percent;root.querySelector('[data-percent]').textContent=Math.round(percent)+'% of the example window elapsed';}}
      const form=root.querySelector('[data-registration]');if(form)form.querySelector('fieldset').disabled=left===0;
      root.dataset.phase=left===0?'ended':'upcoming';if(left===0&&!ended){status.textContent=root.dataset.endMessage||'The example deadline has passed.';ended=true;}else if(left>0&&ended){status.textContent='Countdown updated.';ended=false;}
    }
    root.querySelectorAll('[data-event-target]').forEach(button=>button.addEventListener('click',()=>{root.dataset.deadline=button.dataset.eventTarget;root.querySelector('[data-event-title]').textContent=button.textContent.trim();root.querySelectorAll('[data-event-target]').forEach(e=>e.setAttribute('aria-pressed',String(e===button)));status.textContent='Selected '+button.textContent.trim()+'.';ended=false;draw();}));
    const configure=root.querySelector('[data-configure]');if(configure)configure.addEventListener('submit',event=>{event.preventDefault();if(!configure.reportValidity())return;const value=configure.elements.deadline.value;if(!/^\d{4}-\d\d-\d\dT\d\d:\d\d$/.test(value)){status.textContent='Enter a complete UTC date and time.';return;}const iso=value+':00Z';if(!valid(iso)){status.textContent='Enter a valid UTC date and time.';return;}root.dataset.deadline=iso;root.dataset.zone=configure.elements.zone.value;status.textContent='Deadline updated. Only the display time zone changes.';ended=false;draw();});
    const close=root.querySelector('[data-dismiss]'),banner=root.querySelector('[data-banner]'),show=root.querySelector('[data-show-banner]');if(close&&banner&&show){close.addEventListener('click',()=>{banner.hidden=true;show.hidden=false;show.focus();});show.addEventListener('click',()=>{banner.hidden=false;show.hidden=true;close.focus();});}
    const registration=root.querySelector('[data-registration]');if(registration)registration.addEventListener('submit',event=>{event.preventDefault();draw();if(root.dataset.phase==='ended'||!registration.reportValidity())return;status.textContent='Demo validation passed. No email was sent or registration stored.';});
    root.querySelectorAll('[data-preview-state]').forEach(button=>button.addEventListener('click',()=>{root.dataset.deadline=new Date(Date.now()+(button.dataset.previewState==='ended'?-1000:3600000)).toISOString();status.textContent='Previewing the '+button.dataset.previewState+' state with a sample deadline.';ended=false;draw();}));
    draw();setInterval(draw,1000);document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw();});
  });
  document.querySelectorAll('[data-duration-timer]').forEach(root=>{
    const input=root.querySelector('[data-minutes]'),readout=root.querySelector('[data-duration]'),start=root.querySelector('[data-start]'),pause=root.querySelector('[data-pause]'),reset=root.querySelector('[data-reset]'),status=root.querySelector('[data-duration-status]');let remaining=Number(input.value)*60000,deadline=0,running=false,finished=false;
    function draw(){if(running)remaining=Math.max(0,deadline-Date.now());const sec=Math.ceil(remaining/1000);readout.textContent=[Math.floor(sec/3600),Math.floor(sec/60)%60,sec%60].map(n=>String(n).padStart(2,'0')).join(':');if(running&&remaining===0){running=false;finished=true;status.textContent='Timer finished.';}start.disabled=running||finished;pause.disabled=!running;input.disabled=running;start.textContent=remaining<Number(input.value)*60000&&!finished?'Resume':'Start';}
    start.addEventListener('click',()=>{if(!input.reportValidity())return;if(!running&&!finished){deadline=Date.now()+remaining;running=true;status.textContent='Timer running.';draw();}});pause.addEventListener('click',()=>{if(running){remaining=Math.max(0,deadline-Date.now());running=false;status.textContent='Timer paused.';draw();}});function clear(){running=false;finished=false;remaining=Number(input.value)*60000;status.textContent='Timer reset.';draw();}reset.addEventListener('click',clear);input.addEventListener('change',()=>{if(input.checkValidity())clear();});draw();setInterval(draw,250);document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw();});
  });
})();

About this set

A duration-based focus timer with start, pause, resume and reset. Remaining time is derived from an absolute clock deadline while running, rather than subtracting interval ticks. Invalid durations cannot start.

What’s included

  • Explicit deadline or duration configuration
  • Clock-derived remaining time; no interval drift accumulation
  • Responsive controls and keyboard focus
  • Expiry and reset behavior covered by tests

Accessibility

Ticking values use role="timer" with aria-live="off". Status messages announce user actions and completion, not every second. Controls have visible labels and focus outlines. No motion is required.

Customise it

Edit the data-deadline/data-start ISO values, or the default duration input. Change the scoped .ct theme variables. The Bootstrap and Tailwind variants share scoped component CSS and vanilla logic with framework utility markup.