Countdown Timer
A countdown on flipping cards, to a real end date: it stops at zero, shows "Finished" and tells your code.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Utilities has 43 effects, including 3 free. Explore the category →
3 usage examples



How it works
Each figure is a .cdt-card split into two halves; when the value changes, the top half swings 90° in 0.3 s (cdt-flip-top) before the new number settles in. The units shown are the cards present in the HTML, marked with data-cdt-unit: d (days), h, m, s. The largest unit holds the remainder: with no days card, 26 hours read 26:00:00.
The end is set with data-cdt-end, an ISO date (2026-12-31T20:00:00+01:00; with no time zone, it is the visitor's local time). Without that attribute, the countdown starts from the duration written in the cards when the page loads (12:34:56 by default). The time left is recomputed from the clock every second: a tab left in the background never falls behind.
At zero, the countdown stops on 00:00:00. The root gets the cdt-done class, the colons stop blinking, the data-cdt-done text shows under the cards and the cdt-done event fires on the root. root.fx0738.setEnd(date) restarts the countdown towards another end (a Date object, a number of milliseconds or ISO text). Each root has its own timer.
Accessibility
- Readable time, never chatty: the cards are hidden from screen readers (
aria-hidden); a hiddenrole="timer"text reads "12 hours 34 minutes 56 seconds". The timer role is not announced by itself: people read it when they want, with no announcement every second. - End announced once: the
data-cdt-donemessage is written into arole="status"region, read politely a single time. - Reduced motion: under
prefers-reduced-motion: reduce, figures change without the flip and the colons stay still; the countdown carries on as usual. - Not a legal clock: the time comes from the visitor's computer. For a deadline that matters (end of an auction, close of registrations), your server is the reference; the countdown is only a display.
Browser compatibility
Dependency-free JavaScript (Date.parse, setTimeout, CustomEvent, padStart) and a light 3D CSS animation (perspective, rotateX). Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two copies (HTML duration and real end date), code pasted twice, stop at zero with the event, setEnd to 26 h, 390 px, reduced motion, English page, no name added to window, 0 errors. Playwright's WebKit flattens 3D effects: the flip was not checked by eye in Safari.
Without JavaScript, the cards show the value written in the HTML, standing still. A badly written data-cdt-end is ignored: the countdown then starts from the cards' duration.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0738-root" data-cdt-end="2026-12-31T20:00:00+01:00" data-cdt-done="Finished">
<div class="cdt-board" aria-hidden="true">
<div class="cdt-unit">
<div class="cdt-card" data-cdt-unit="h">
<div class="cdt-card-inner"><div class="cdt-front">12</div><div class="cdt-back">12</div></div>
</div>
<span class="cdt-label">Hours</span>
</div>
<span class="cdt-sep">:</span>
<!-- same block for data-cdt-unit="m" and "s" (and "d" first for days) -->
</div>
<p class="cdt-sr" role="timer"></p>
<p class="cdt-status" role="status"></p>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
End date (HTML — data-cdt-end) |
absent: duration written in the cards | ISO date with a time zone (2026-12-31T20:00:00+01:00) for a deadline that is the same for every visitor. |
Units (HTML — data-cdt-unit cards) |
h, m, s |
Add a d card for days, remove h for a minutes:seconds timer. The largest unit holds the remainder. |
End message (HTML — data-cdt-done) |
"Finished" | Text shown under the cards and announced once at zero: "Sale closed", "Registrations are open"… |
New end (JS — root.fx0738.setEnd(date)) |
— | Restarts the countdown towards another date (Date, milliseconds or ISO text), for instance an end received from your server after loading. |
React to the end (JS — cdt-done event) |
— | root.addEventListener('cdt-done', …): show a button, reload a price, move to the next question. |
Card size (CSS — .cdt-card, .cdt-front) |
60×70px, 2rem figures |
Enlarge the card and the figure size together; both halves follow. |
Colours (CSS — .cdt-board, .cdt-sep, .cdt-status) |
#1a1a2e background, #6366f1 colons, #a5b4fc message |
Your brand hues; the finished state is styled through .cdt-done. |
FAQ
+01:00, Z). Without one, it is read in each visitor's local time: 8 pm in Paris and 8 pm in Montreal are not the same moment. The count uses the visitor's computer clock; a clock that is off shifts the display, not your real deadline.data-cdt-done message and fires cdt-done. It never restarts by itself; to restart it, call setEnd().min-width for some breathing room. For a wait of several days, a d card reads better.