Countdown

The launch sale ends in

12Days 00Hours 00Minutes 00Seconds

Get 40% off
@use 'sprucecss/scss/spruce' as *;

.countdown {
  background-color: color('surface');
  border: config('border-width-base', $display) solid color('border');
  border-radius: radius('lg');
  padding: spacer-clamp('m', 'l');
  text-align: center;
  @include layout-stack('m');

  &__title {
    font-size: font-size('h4');
    margin: 0;
  }

  &__timer {
    display: flex;
    flex-wrap: wrap;
    gap: spacer('s');
    justify-content: center;
  }

  &__unit {
    background-color: color('surface-muted');
    border-radius: radius('md');
    display: flex;
    flex-direction: column;
    min-inline-size: 5rem;
    padding: spacer('s');
  }

  &__value {
    color: color('heading');
    font-family: config('font-family-heading', $typography);
    font-size: font-size('h2');
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    line-height: 1.1;
  }

  &__label {
    color: color('muted');
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
}
<section class="countdown" aria-labelledby="countdown-title" data-countdown data-countdown-days="12">
    <h2 class="countdown__title" id="countdown-title">The launch sale ends in</h2>
    <p class="countdown__timer" role="timer" aria-live="off" data-countdown-timer>
        <span class="countdown__unit"><span class="countdown__value" data-unit="days">12</span><span class="countdown__label">Days</span></span>
        <span class="countdown__unit"><span class="countdown__value" data-unit="hours">00</span><span class="countdown__label">Hours</span></span>
        <span class="countdown__unit"><span class="countdown__value" data-unit="minutes">00</span><span class="countdown__label">Minutes</span></span>
        <span class="countdown__unit"><span class="countdown__value" data-unit="seconds">00</span><span class="countdown__label">Seconds</span></span>
    </p>
    <p class="sr-only" data-countdown-text></p>
    <a class="btn btn--primary" href="#">Get 40% off</a>
</section>
(() => {
  document.querySelectorAll('[data-countdown]').forEach((countdown) => {
    const end = countdown.dataset.countdownEnd
      ? new Date(countdown.dataset.countdownEnd).getTime()
      : Date.now() + Number(countdown.dataset.countdownDays || 0) * 864e5;
    const units = Object.fromEntries([...countdown.querySelectorAll('[data-unit]')].map((element) => [element.dataset.unit, element]));
    const text = countdown.querySelector('[data-countdown-text]');
    let lastMinute = null;

    const tick = () => {
      const remaining = Math.max(0, end - Date.now());
      const values = {
        days: Math.floor(remaining / 864e5),
        hours: Math.floor(remaining / 36e5) % 24,
        minutes: Math.floor(remaining / 6e4) % 60,
        seconds: Math.floor(remaining / 1e3) % 60
      };

      Object.entries(values).forEach(([unit, value]) => {
        units[unit].textContent = String(value).padStart(2, '0');
      });

      if (values.minutes !== lastMinute) {
        lastMinute = values.minutes;
        text.textContent = `${values.days} days, ${values.hours} hours and ${values.minutes} minutes left.`;
      }

      if (remaining === 0) {
        clearInterval(timer);
        countdown.dispatchEvent(new CustomEvent('countdownend'));
      }
    };

    const timer = setInterval(tick, 1000);
    tick();
  });
})();

Countdown

On this page

A countdown timer for launches, sales and events.

Technical Details

  • Set the end with data-countdown-end (an ISO date, like 2026-12-31T23:59:59Z); the demo uses data-countdown-days relative to now.
  • The visible timer is a role="timer" region with aria-live="off", so screen readers aren't interrupted every second. A visually hidden text with the remaining time (in words) updates once a minute.
  • font-variant-numeric: tabular-nums keeps the digits from jumping while they change.
  • When the time is up, the component dispatches a countdownend event and shows zeros; swap the content there (for example, "The sale has ended").

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.