A pure CSS countdown animates a registered integer custom property from the start number to zero and prints it with a counter. Register --n with @property as an integer, animate it with steps() so it ticks once a second, and show it with counter-reset: n var(--n) and content: counter(n). A ring adds an animated stroke-dashoffset.
| Piece | CSS | Job |
|---|---|---|
| Typed variable | @property --n { syntax: "<integer>" } | Lets the browser animate the number |
| Ticking | animation: count 3s steps(3) forwards | One jump per second, holds the last value |
| Printing | counter-reset: n var(--n) | Turns the variable into a counter |
| Showing | ::after { content: counter(n) } | Draws the current number |
| Ring | stroke-dashoffset: 0 to circumference | Empties a circle over the same time |
Updated
A splash countdown, a quiz timer or a launch moment does not need a script. This page builds a 3-2-1 number countdown and a ring countdown with CSS alone, lets you generate one with your own start number and speed, and gives a fallback for older browsers. For a countdown to a calendar date, use our countdown timer generator instead.
<style>
@property --n { syntax: "<integer>"; inherits: false; initial-value: 0; }
.countdown { position: relative; width: 120px; height: 120px; display: grid; place-items: center;
font: 700 48px system-ui; color: #0f172a }
.countdown .num { animation: count 3s steps(3) forwards; counter-reset: n var(--n) }
.countdown .num::after { content: counter(n) }
@keyframes count { from { --n: 3 } to { --n: 0 } }
.ring { position: absolute; inset: 0; transform: rotate(-90deg) }
.ring circle { fill: none; stroke-width: 8 }
.ring .track { stroke: #e2e8f0 }
.ring .bar { stroke: #4F98FB; stroke-linecap: round; stroke-dasharray: 283;
animation: empty 3s linear forwards }
@keyframes empty { to { stroke-dashoffset: 283 } }
@media (prefers-reduced-motion: reduce) { .ring .bar { animation: none } }
</style>
<div class="countdown" role="timer" aria-label="Countdown from 3">
<svg class="ring" viewBox="0 0 100 100" aria-hidden="true"><circle class="track" cx="50" cy="50" r="45"/><circle class="bar" cx="50" cy="50" r="45"/></svg>
<span class="num"></span>
</div>Edit the code to rerun it. The number drops once a second and stays at 0.
<style>
@property --n { syntax: "<integer>"; inherits: false; initial-value: 0; }
.countdown { position: relative; width: 120px; height: 120px; display: grid; place-items: center;
font: 700 48px system-ui; color: #0f172a }
.countdown .num { animation: count 3s steps(3) forwards; counter-reset: n var(--n) }
.countdown .num::after { content: counter(n) }
@keyframes count { from { --n: 3 } to { --n: 0 } }
</style>
<div class="countdown" role="timer" aria-label="Countdown from 3">
<span class="num"></span>
</div>The ring uses stroke-dashoffset on an SVG circle and stops moving when reduced motion is requested.
<style>
@property --n { syntax: "<integer>"; inherits: false; initial-value: 0; }
.countdown { position: relative; width: 120px; height: 120px; display: grid; place-items: center;
font: 700 48px system-ui; color: #0f172a }
.countdown .num { animation: count 5s steps(5) forwards; counter-reset: n var(--n) }
.countdown .num::after { content: counter(n) }
@keyframes count { from { --n: 5 } to { --n: 0 } }
.ring { position: absolute; inset: 0; transform: rotate(-90deg) }
.ring circle { fill: none; stroke-width: 8 }
.ring .track { stroke: #e2e8f0 }
.ring .bar { stroke: #4F98FB; stroke-linecap: round; stroke-dasharray: 283;
animation: empty 5s linear forwards }
@keyframes empty { to { stroke-dashoffset: 283 } }
@media (prefers-reduced-motion: reduce) { .ring .bar { animation: none } }
</style>
<div class="countdown" role="timer" aria-label="Countdown from 5">
<svg class="ring" viewBox="0 0 100 100" aria-hidden="true"><circle class="track" cx="50" cy="50" r="45"/><circle class="bar" cx="50" cy="50" r="45"/></svg>
<span class="num"></span>
</div>Each number is its own element and is shown for one second in turn. It works in any browser that supports CSS animations.
<style>
.stack { position: relative; width: 80px; height: 64px; font: 700 48px system-ui; text-align: center }
.stack span { position: absolute; inset: 0; opacity: 0; animation: show 1s steps(1) forwards }
.stack span:nth-child(2) { animation-delay: 1s }
.stack span:nth-child(3) { animation-name: stay; animation-delay: 2s }
@keyframes show { from { opacity: 1 } to { opacity: 0 } }
@keyframes stay { from, to { opacity: 1 } }
</style>
<div class="stack" role="timer"><span>3</span><span>2</span><span>1</span></div>
Registering --n as an integer gives it a type, so keyframes from 3 to 0 produce in-between whole numbers instead of a sudden swap. counter-reset accepts that integer through var(), and counter() turns it into text in a pseudo-element.
steps(3) splits a three second animation into three equal jumps. Without it you get the same numbers, but timing follows the easing curve and the ticks feel uneven.
An SVG circle's outline can be drawn as one dash as long as the circumference, 2 times pi times the radius. Moving stroke-dashoffset from zero to that length slides the dash out of view, so the ring empties smoothly.
Rotate the SVG by minus 90 degrees so the ring starts emptying from the top, and set stroke-linecap: round for a softer end.
The custom property is not registered. An unregistered custom property cannot be interpolated, so the browser flips from the first value to the last. Add an @property rule with syntax "<integer>", inherits and an initial-value.
MDN lists @property as Baseline 2024, newly available across current browsers since July 2024. In older browsers the number does not tick, which is why a stacked-number fallback is shown on this page.
Add a second element with the word, start it at opacity 0 and give it an animation that fades in with an animation-delay equal to the countdown length. Hide the number at the same moment.
No. A date countdown needs the current time, which CSS cannot read, so it requires JavaScript. Our countdown timer generator builds that kind. This page is for short fixed countdowns like 3-2-1.
Related: Countdown timer generator (to a date), CSS animation generator, CSS variables guide, CSS transitions playground, prefers-reduced-motion guide.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.