Free HTML countdown timer embed widget

A countdown widget is one small page plus an iframe. Hosting it yourself removes the branding, the expiry and the third party script that a free hosted widget usually brings with it.

A free HTML countdown timer embed widget is a small self-hosted page containing the timer, placed on other pages with an iframe.

Hosted widget services do the same thing and add three dependencies: their branding, their script domain, and their continued existence. Writing the page yourself takes about ten minutes and removes all three.

A countdown widget embedded inside a longer page, sitting in its own box.
A countdown widget embedded inside a longer page, sitting in its own box.

A free HTML countdown timer embed widget versus a hosted one

Hosted free widget Page you host
Branding line Usually present None
Extra script domain Yes None
Styling control Their options Full
Works if provider shuts down No Yes
Time to set up Minutes Minutes
Analytics on the widget Theirs None, unless you add it

Neither choice is wrong. If you need a widget for one weekend campaign, a hosted one is fine. If it is going on a page that stays up, own the file.

The widget page

A widget page is a normal countdown page with the page furniture removed. No headings, no margins, nothing that assumes it is being read on its own.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
  html,body{margin:0;padding:0;background:#111;color:#fff;
            font-family:system-ui,sans-serif;}
  .box{display:flex;gap:12px;justify-content:center;padding:16px;}
  .u{min-width:64px;text-align:center;}
  .n{font-size:30px;line-height:1;}
  .l{font-size:11px;opacity:.7;text-transform:uppercase;}
</style>
</head>
<body>
  <div class="box" id="cd"></div>
  <script>
    var target = new Date('2026-12-01T09:00:00Z').getTime();
    var el = document.getElementById('cd');
    function unit(n, label) {
      return '<div class="u"><div class="n">' + n +
             '</div><div class="l">' + label + '</div></div>';
    }
    var tick = setInterval(function () {
      var left = target - Date.now();
      if (left <= 0) {
        clearInterval(tick);
        el.innerHTML = '<div class="u"><div class="n">Open now</div></div>';
        return;
      }
      var s = Math.floor(left / 1000);
      el.innerHTML = unit(Math.floor(s / 86400), 'days') +
        unit(Math.floor((s % 86400) / 3600), 'hours') +
        unit(Math.floor((s % 3600) / 60), 'min') +
        unit(s % 60, 'sec');
    }, 1000);
  </script>
</body>
</html>

The margin:0 line matters more than it looks. Default body margins inside an iframe are the usual reason a scrollbar appears in a box that should fit.

Giving the widget an address

An iframe needs a URL. The widget page has to be served from somewhere.

Paste the file into a NOS document. It renders as written, script included, as a page of its own. Then Share, Share link, Create link, and that address is your widget source.

Creating the share link for the widget page. This address goes into the iframe.
Creating the share link for the widget page. This address goes into the iframe.

Tick Public on the web if the host page is public, so nothing about the embed depends on who holds the link.

The embed

On the host page, one element:

<iframe src="https://your-widget-address"
        title="Countdown to launch"
        width="420" height="90"
        style="border:0;max-width:100%;"
        loading="lazy"></iframe>

Four attributes earn their place. title is read by screen readers, border:0 removes the default frame, max-width:100% stops it overflowing on a phone, and loading="lazy" avoids fetching it before it is scrolled to.

If the frame and the host page are on different origins, they cannot read each other's contents. That isolation is the point. Iframe covers the boundary in more detail.

What the widget page should not contain

A widget is framed inside someone else's layout, so a few habits from full pages become bugs.

Do not set a page background that fights the host. Either match the host colour, or make the widget background transparent and let the host show through.

Do not include navigation, headings or footers. The frame is a component, not a page, and anything extra is dead space you are paying for in iframe height.

Do not load fonts from elsewhere. A web font request inside a frame delays the first render of the numbers, which is the one thing the widget exists to show.

Do not assume a fixed viewport width. The host may place the frame in a narrow column, so the widget layout has to hold at around 300 pixels.

Styling it to match the host

Two practical routes, and the choice depends on how many pages embed it.

For a single host, hard code the colours in the widget page to match. Simplest, and nothing else to maintain.

For several hosts, read the colours from the frame address. Parse the query string in the widget and apply the values, so one page serves a dark version and a light one.

var params = new URLSearchParams(location.search);
document.body.style.background = '#' + (params.get('bg') || '111');
document.body.style.color = '#' + (params.get('fg') || 'fff');

Then the embed becomes ?bg=ffffff&amp;fg=111111 on the end of the iframe source. Note the &amp; inside HTML markup.

Sizing without a scrollbar

Work top down:

  1. Open the widget address on its own and note how tall the content actually is.
  2. Set the iframe height to that number plus a few pixels.
  3. Resize the browser to phone width and check the widget does not wrap into a second row.
  4. If it wraps, reduce the unit widths inside the widget rather than growing the frame.

A fixed height iframe cannot grow with its contents across origins, so the widget layout has to be stable at every width you support.

The widget before and after removing the body margin, with the scrollbar gone in the second.
The widget before and after removing the body margin, with the scrollbar gone in the second.

Embedding in tools rather than pages

Many places you would want the timer are not pages you write.

  • Notion and similar documents. They accept an embed by URL. Paste the widget address into an embed block. Embedding HTML in Notion covers the general case.
  • A CMS with a raw HTML block. Paste the iframe markup directly.
  • A CMS that strips iframes. Nothing to do at the page level. Link to the timer page instead.
  • Mail. Scripts never run. Use the served image approach.

Keeping it correct after launch

Dates move. That is the real argument for owning the page.

The widget lives at one address, so changing the target string in the document updates every page that embeds it at once. No re-upload, no cache busting on a file name, and no hunting for the pages that used it.

Decide the end state before you publish. A widget that sits at zero forever reads as abandoned, so swap in the live link or the post deadline message when the count runs out.

A short checklist before you embed it

Run these once. They catch nearly every reported problem with an embedded countdown.

  • Open the widget address directly. It must render on its own, outside any host page.
  • Confirm the target string carries a time zone, either a trailing Z or an explicit offset.
  • Check the widget at 320 pixels wide and at 800, so the host can place it anywhere.
  • Confirm no scrollbar appears at the height you set on the frame.
  • Watch it pass a minute boundary, so you know the interval is actually running.
  • Decide and test what the widget shows after the target time passes.

The fifth item catches a surprising number of broken timers, because a static first render looks correct until you watch it.

For the arithmetic, the time zone rule and the formatting, see HTML countdown timer. If the widget is going on a page you are also building, HTML for event invites covers what usually surrounds it.

Questions people ask

What is the catch with a free countdown timer widget?

Usually three things: a small branding line, a script loaded from the provider's domain, and no guarantee the service still exists next year. If the provider goes away or changes their free tier, the widget on your page stops rendering.

How do I embed a countdown timer in a page?

Put the timer on its own page at its own address, then place an iframe on the host page pointing at that address. Set width, height, a title attribute and a border of zero. The iframe keeps the widget isolated from the host page styles.

Can I embed a countdown in Notion or a CMS?

If the tool accepts an embed by URL, yes. Give the timer page a public address and paste that address into the embed block. Tools that only accept a whitelist of providers will not take an arbitrary page, and the answer there is a link instead.

Why does my embedded timer show a scrollbar?

The iframe is shorter than the content inside it. Set an explicit height that fits the widget, remove default body margins inside the widget page, and keep the widget layout fixed rather than letting it wrap at narrow widths.

Keep reading