Open demo

CSS code

HTML
<article class="cdown">
  <time class="top" datetime="2026-11-14T09:30:00Z"><span class="mon">November</span><span class="day">14</span><span class="yr">Saturday, 2026</span></time>
  <div class="cd" role="timer" aria-label="Time until the conference starts">
    <div><b data-unit="d">--</b><span>Days</span></div>
    <div><b data-unit="h">--</b><span>Hours</span></div>
    <div><b data-unit="m">--</b><span>Mins</span></div>
    <div class="secs"><b data-unit="s">--</b><span>Secs</span></div>
    <p class="done" hidden>Happening now</p>
  </div>
  <div class="body">
    <h2>Frontend Conf 2026</h2>
    <p>One day, two tracks, and the hallway that everyone actually comes for.</p>
    <ul class="meta">
      <li><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg> 09:30 – 18:00 GMT</li>
      <li><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s7-5.3 7-11a7 7 0 10-14 0c0 5.7 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg> Barbican Centre, London</li>
      <li><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="9" cy="8" r="3.2"/><path d="M3 20c0-3.2 2.7-5.2 6-5.2M17 8v6M20 11h-6"/></svg> 640 attending · 60 tickets left</li>
    </ul>
    <button class="btn" type="button">Add to calendar</button>
  </div>
</article>
CSS
/* Event countdown card. Under prefers-reduced-motion the seconds column is hidden and the
   numbers change once a minute (the script reads the same media query). */
.cdown{--cd-accent:#6d28d9;--cd-muted:#5f6b7c;
  box-sizing:border-box;width:100%;max-width:420px;overflow:hidden;background:#fff;border-radius:16px;
  box-shadow:0 14px 34px -18px rgba(18,24,40,.25)}
.cdown .top{display:block;background:linear-gradient(140deg,#6d28d9,#9333ea);color:#fff;padding:24px 24px 22px;text-align:center}
.cdown .top span{display:block}
.cdown .mon{font-size:.72rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#f3e8ff}
.cdown .day{font-size:3.1rem;font-weight:800;line-height:1;letter-spacing:-.03em;margin:4px 0 2px}
.cdown .yr{font-size:.82rem;color:#f3e8ff}
.cdown .cd{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:#eef1f5}
.cdown .cd > div{background:#fff;padding:14px 0;text-align:center}
.cdown .cd b{display:block;font-size:1.5rem;font-weight:800;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.cdown .cd span{font-size:.64rem;font-weight:800;letter-spacing:.09em;text-transform:uppercase;color:var(--cd-muted)}
.cdown .done{grid-column:1 / -1;margin:0;background:#fff;padding:16px 0;text-align:center;font-size:.86rem;font-weight:700;color:var(--cd-accent)}
.cdown .body{padding:20px 24px 24px}
.cdown h2{font-size:1.02rem;margin:0 0 5px;letter-spacing:-.01em}
.cdown .body p{font-size:.83rem;color:var(--cd-muted);line-height:1.6;margin:0}
.cdown .meta{display:flex;flex-direction:column;gap:9px;margin:16px 0 18px;padding:0;list-style:none;font-size:.82rem;color:#4b5567}
.cdown .meta li{display:flex;align-items:center;gap:9px}
.cdown .meta svg{width:16px;height:16px;stroke:#7c8798;fill:none;stroke-width:2;flex-shrink:0}
.cdown .btn{display:block;width:100%;border:0;background:var(--cd-accent);color:#fff;font:inherit;font-size:.88rem;font-weight:700;
  padding:12px;border-radius:10px;cursor:pointer;text-align:center}
.cdown .btn:hover{background:#5b21b6}
.cdown .btn:focus-visible{outline:2px solid var(--cd-accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.cdown .cd{grid-template-columns:repeat(3,1fr)}.cdown .cd .secs{display:none}}
JavaScript
/* The target comes from the datetime attribute, never from the visible text. The timer ticks every
   second, or once a minute with the seconds hidden when the visitor prefers reduced motion, and
   stops once the event has started. */
document.querySelectorAll('.cdown').forEach(root => {
  const target = new Date(root.querySelector('time[datetime]').dateTime);
  const box = root.querySelector('[role=timer]'), done = box.querySelector('.done');
  const out = {}; box.querySelectorAll('[data-unit]').forEach(b => { out[b.dataset.unit] = b; });
  const calm = matchMedia('(prefers-reduced-motion: reduce)');
  const pad = n => String(n).padStart(2, '0');
  let timer;
  const tick = () => {
    let left = Math.floor((target - Date.now()) / 1000);
    if (left <= 0) {
      clearInterval(timer);
      Object.values(out).forEach(b => { b.parentElement.hidden = true; });
      done.hidden = false;
      return;
    }
    const d = Math.floor(left / 86400); left %= 86400;
    const h = Math.floor(left / 3600); left %= 3600;
    out.d.textContent = d; out.h.textContent = pad(h);
    out.m.textContent = pad(Math.floor(left / 60)); out.s.textContent = pad(left % 60);
  };
  const start = () => { clearInterval(timer); timer = setInterval(tick, calm.matches ? 60000 : 1000); tick(); };
  calm.addEventListener('change', start);
  start();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<article class="cdown card w-100">
  <time class="top d-block text-center text-white" datetime="2026-11-14T09:30:00Z"><span class="mon d-block fw-bolder text-uppercase">November</span><span class="day d-block fw-bolder">14</span><span class="yr d-block">Saturday, 2026</span></time>
  <div class="cd" role="timer" aria-label="Time until the conference starts">
    <div class="text-center"><b class="d-block fw-bolder" data-unit="d">--</b><span class="fw-bolder text-uppercase">Days</span></div>
    <div class="text-center"><b class="d-block fw-bolder" data-unit="h">--</b><span class="fw-bolder text-uppercase">Hours</span></div>
    <div class="text-center"><b class="d-block fw-bolder" data-unit="m">--</b><span class="fw-bolder text-uppercase">Mins</span></div>
    <div class="secs text-center"><b class="d-block fw-bolder" data-unit="s">--</b><span class="fw-bolder text-uppercase">Secs</span></div>
    <p class="done fw-bold text-center m-0" hidden>Happening now</p>
  </div>
  <div class="card-body" style="padding:20px 24px 24px">
    <h2 class="fw-bold" style="font-size:1.02rem;margin-bottom:5px;letter-spacing:-.01em">Frontend Conf 2026</h2>
    <p class="m-0" style="font-size:.83rem;line-height:1.6;color:var(--cd-muted)">One day, two tracks, and the hallway that everyone actually comes for.</p>
    <ul class="meta list-unstyled d-flex flex-column" style="gap:9px;margin:16px 0 18px;font-size:.82rem;color:#4b5567">
      <li class="d-flex align-items-center" style="gap:9px"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg> 09:30 – 18:00 GMT</li>
      <li class="d-flex align-items-center" style="gap:9px"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s7-5.3 7-11a7 7 0 10-14 0c0 5.7 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg> Barbican Centre, London</li>
      <li class="d-flex align-items-center" style="gap:9px"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="9" cy="8" r="3.2"/><path d="M3 20c0-3.2 2.7-5.2 6-5.2M17 8v6M20 11h-6"/></svg> 640 attending · 60 tickets left</li>
    </ul>
    <button class="btn w-100" type="button">Add to calendar</button>
  </div>
</article>
CSS
/* Bootstrap card and .btn, restyled through --bs-btn-* variables. The countdown strip is custom. */
.cdown{--cd-accent:#6d28d9;--cd-muted:#5f6b7c;
  max-width:420px;overflow:hidden;border:0;border-radius:16px;box-shadow:0 14px 34px -18px rgba(18,24,40,.25)}
.cdown .top{background:linear-gradient(140deg,#6d28d9,#9333ea);padding:24px 24px 22px}
.cdown .mon{font-size:.72rem;letter-spacing:.14em;color:#f3e8ff}
.cdown .day{font-size:3.1rem;line-height:1;letter-spacing:-.03em;margin:4px 0 2px}
.cdown .yr{font-size:.82rem;color:#f3e8ff}
.cdown .cd{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:#eef1f5}
.cdown .cd > div{background:#fff;padding:14px 0}
.cdown .cd b{font-size:1.5rem;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.cdown .cd span{font-size:.64rem;letter-spacing:.09em;color:var(--cd-muted)}
.cdown .done{grid-column:1 / -1;background:#fff;padding:16px 0;font-size:.86rem;color:var(--cd-accent)}
.cdown .meta svg{width:16px;height:16px;stroke:#7c8798;fill:none;stroke-width:2;flex-shrink:0}
.cdown .btn{--bs-btn-bg:var(--cd-accent);--bs-btn-border-color:var(--cd-accent);--bs-btn-color:#fff;--bs-btn-hover-bg:#5b21b6;
  --bs-btn-hover-border-color:#5b21b6;--bs-btn-hover-color:#fff;--bs-btn-font-size:.88rem;--bs-btn-font-weight:700;
  --bs-btn-padding-y:12px;--bs-btn-border-radius:10px;line-height:normal}
.cdown .btn:focus-visible{box-shadow:none;outline:2px solid var(--cd-accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.cdown .cd{grid-template-columns:repeat(3,1fr)}.cdown .cd .secs{display:none!important}}
JavaScript
/* The target comes from the datetime attribute, never from the visible text. The timer ticks every
   second, or once a minute with the seconds hidden when the visitor prefers reduced motion, and
   stops once the event has started. */
document.querySelectorAll('.cdown').forEach(root => {
  const target = new Date(root.querySelector('time[datetime]').dateTime);
  const box = root.querySelector('[role=timer]'), done = box.querySelector('.done');
  const out = {}; box.querySelectorAll('[data-unit]').forEach(b => { out[b.dataset.unit] = b; });
  const calm = matchMedia('(prefers-reduced-motion: reduce)');
  const pad = n => String(n).padStart(2, '0');
  let timer;
  const tick = () => {
    let left = Math.floor((target - Date.now()) / 1000);
    if (left <= 0) {
      clearInterval(timer);
      Object.values(out).forEach(b => { b.parentElement.hidden = true; });
      done.hidden = false;
      return;
    }
    const d = Math.floor(left / 86400); left %= 86400;
    const h = Math.floor(left / 3600); left %= 3600;
    out.d.textContent = d; out.h.textContent = pad(h);
    out.m.textContent = pad(Math.floor(left / 60)); out.s.textContent = pad(left % 60);
  };
  const start = () => { clearInterval(timer); timer = setInterval(tick, calm.matches ? 60000 : 1000); tick(); };
  calm.addEventListener('change', start);
  start();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<article class="cdown w-full max-w-[420px] overflow-hidden rounded-2xl bg-white shadow-[0_14px_34px_-18px_rgba(18,24,40,.25)]">
  <time class="block bg-linear-[140deg] from-[#6d28d9] to-[#9333ea] px-6 pt-6 pb-[22px] text-center text-white" datetime="2026-11-14T09:30:00Z"><span class="block text-[.72rem] font-extrabold tracking-[.14em] text-[#f3e8ff] uppercase">November</span><span class="mt-1 mb-0.5 block text-[3.1rem] leading-none font-extrabold tracking-[-.03em]">14</span><span class="block text-[.82rem] text-[#f3e8ff]">Saturday, 2026</span></time>
  <div class="grid grid-cols-4 gap-px bg-[#eef1f5] motion-reduce:grid-cols-3" role="timer" aria-label="Time until the conference starts">
    <div class="bg-white py-3.5 text-center"><b class="block text-2xl font-extrabold tracking-[-.02em] tabular-nums" data-unit="d">--</b><span class="text-[.64rem] font-extrabold tracking-[.09em] text-cd-muted uppercase">Days</span></div>
    <div class="bg-white py-3.5 text-center"><b class="block text-2xl font-extrabold tracking-[-.02em] tabular-nums" data-unit="h">--</b><span class="text-[.64rem] font-extrabold tracking-[.09em] text-cd-muted uppercase">Hours</span></div>
    <div class="bg-white py-3.5 text-center"><b class="block text-2xl font-extrabold tracking-[-.02em] tabular-nums" data-unit="m">--</b><span class="text-[.64rem] font-extrabold tracking-[.09em] text-cd-muted uppercase">Mins</span></div>
    <div class="bg-white py-3.5 text-center motion-reduce:hidden"><b class="block text-2xl font-extrabold tracking-[-.02em] tabular-nums" data-unit="s">--</b><span class="text-[.64rem] font-extrabold tracking-[.09em] text-cd-muted uppercase">Secs</span></div>
    <p class="done col-span-full bg-white py-4 text-center text-[.86rem] font-bold text-cd-accent" hidden>Happening now</p>
  </div>
  <div class="px-6 pt-5 pb-6">
    <h2 class="mb-[5px] text-[1.02rem] font-bold tracking-[-.01em]">Frontend Conf 2026</h2>
    <p class="text-[.83rem] leading-[1.6] text-cd-muted">One day, two tracks, and the hallway that everyone actually comes for.</p>
    <ul class="mt-4 mb-[18px] flex flex-col gap-[9px] text-[.82rem] text-[#4b5567]">
      <li class="flex items-center gap-[9px]"><svg class="size-4 shrink-0 fill-none stroke-[#7c8798] stroke-2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg> 09:30 – 18:00 GMT</li>
      <li class="flex items-center gap-[9px]"><svg class="size-4 shrink-0 fill-none stroke-[#7c8798] stroke-2" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s7-5.3 7-11a7 7 0 10-14 0c0 5.7 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg> Barbican Centre, London</li>
      <li class="flex items-center gap-[9px]"><svg class="size-4 shrink-0 fill-none stroke-[#7c8798] stroke-2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="9" cy="8" r="3.2"/><path d="M3 20c0-3.2 2.7-5.2 6-5.2M17 8v6M20 11h-6"/></svg> 640 attending · 60 tickets left</li>
    </ul>
    <button class="block w-full cursor-pointer rounded-[10px] bg-cd-accent p-3 text-center text-[.88rem] font-bold text-white hover:bg-[#5b21b6] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cd-accent" type="button">Add to calendar</button>
  </div>
</article>
CSS
@theme {
  --color-cd-accent: #6d28d9;
  --color-cd-muted: #5f6b7c;
}
JavaScript
/* The target comes from the datetime attribute, never from the visible text. The timer ticks every
   second, or once a minute with the seconds hidden when the visitor prefers reduced motion, and
   stops once the event has started. */
document.querySelectorAll('.cdown').forEach(root => {
  const target = new Date(root.querySelector('time[datetime]').dateTime);
  const box = root.querySelector('[role=timer]'), done = box.querySelector('.done');
  const out = {}; box.querySelectorAll('[data-unit]').forEach(b => { out[b.dataset.unit] = b; });
  const calm = matchMedia('(prefers-reduced-motion: reduce)');
  const pad = n => String(n).padStart(2, '0');
  let timer;
  const tick = () => {
    let left = Math.floor((target - Date.now()) / 1000);
    if (left <= 0) {
      clearInterval(timer);
      Object.values(out).forEach(b => { b.parentElement.hidden = true; });
      done.hidden = false;
      return;
    }
    const d = Math.floor(left / 86400); left %= 86400;
    const h = Math.floor(left / 3600); left %= 3600;
    out.d.textContent = d; out.h.textContent = pad(h);
    out.m.textContent = pad(Math.floor(left / 60)); out.s.textContent = pad(left % 60);
  };
  const start = () => { clearInterval(timer); timer = setInterval(tick, calm.matches ? 60000 : 1000); tick(); };
  calm.addEventListener('change', start);
  start();
});

About this set

An event card with a large date header, a live countdown in days, hours, minutes and seconds, a short description, time, venue and attendance details, and an Add to calendar button. Use it for conference and webinar landing pages, product launches, ticketed events and any page that leads up to a date. The date header is a time element, and the script reads the target from its datetime attribute rather than parsing the visible text, so the display can be written in any language or format. The countdown has role=”timer” with a label, and the script clears its interval and shows Happening now once the start time passes. When the visitor prefers reduced motion, the seconds column is hidden by CSS and the script ticks once a minute instead of every second, so nothing changes constantly on the page. The header gradient ends on a darker violet than the original so the light header text keeps its contrast, and the grey labels were darkened to 4.5:1. The Bootstrap version uses .card and .btn, and the Tailwind version uses motion-reduce utilities for the reduced-motion layout.

What’s included

  • Target read from a datetime attribute
  • Stops and shows "Happening now" when the event starts
  • Reduced motion hides seconds and ticks once a minute
  • role="timer" with an accessible label
  • Plain CSS, Bootstrap and Tailwind versions of the same design

Accessibility

The countdown is a role="timer" region with an aria-label; a timer role is not a live region by default, so it is not read out every second. Under prefers-reduced-motion the seconds are hidden and the numbers change once a minute. The button has a 2px violet focus outline and the icons are hidden from assistive tech.

Customise it

Change the datetime attribute on the time element to set the target. Colours are --cd-accent and --cd-muted on .cdown in plain CSS and Bootstrap and the --color-cd-* theme colours in Tailwind; the header gradient is on .top.