A flip clock whose seconds cards fold down every second, top half falling to reveal the next digit. The digits come from a registered integer and a cyclic @counter-style, so 9 wraps to 0.
Also known as: flip clock css, flip counter, split flap clock.
<div class="txt-flip-clock" aria-label="Flip clock"> <span class="txt-flip-clock-card">1</span> <span class="txt-flip-clock-card">0</span> <span class="txt-flip-clock-colon" aria-hidden="true"></span> <span class="txt-flip-clock-card txt-flip-clock-tens" style="--n: 6; --p: 10s; --at: -58s" aria-hidden="true"><i></i><i></i><i></i><i></i></span> <span class="txt-flip-clock-card txt-flip-clock-ones" style="--n: 10; --p: 1s; --at: -7s" aria-hidden="true"><i></i><i></i><i></i><i></i></span> </div>
@counter-style txt-flip-clock-ten {
system: cyclic;
symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
}
@counter-style txt-flip-clock-six {
system: cyclic;
symbols: "0" "1" "2" "3" "4" "5";
}
@property --txt-flip-clock-d {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
.txt-flip-clock {
--c: var(--txt-color, #3b82f6);
font-size: var(--txt-size, 32px);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
display: inline-flex;
align-items: center;
gap: 0.1em;}
.txt-flip-clock-card {
position: relative;
width: 0.74em;
height: 1.12em;
font-size: 1.9em;
line-height: 1.12em;
text-align: center;
font-variant-numeric: tabular-nums;
color: #f8fafc;
background: color-mix(in srgb, var(--c) 22%, #111827);
border-radius: 0.1em;
perspective: 2.5em;
box-shadow: 0 0.05em 0.12em rgb(0 0 0 / 0.3);
}
/* the hinge line across the middle */
.txt-flip-clock-card::before {
content: "";
position: absolute;
inset: calc(50% - 0.02em) 0 auto;
height: 0.04em;
background: rgb(0 0 0 / 0.45);
z-index: 2;
}
.txt-flip-clock-colon {
width: 0.24em;
height: 0.95em;
background:
radial-gradient(circle closest-side, var(--c) 92%, transparent) top / 0.24em 0.24em no-repeat,
radial-gradient(circle closest-side, var(--c) 92%, transparent) bottom / 0.24em 0.24em no-repeat;
}
/* four layers: static top (next), static bottom (current),
falling top flap (current), landing bottom flap (next) */
.txt-flip-clock-card i {
position: absolute;
inset: 0;
font-style: normal;
border-radius: inherit;
background: inherit;
backface-visibility: hidden;
}
.txt-flip-clock-card i:nth-child(odd) { clip-path: inset(0 0 50% 0); }
.txt-flip-clock-card i:nth-child(even) { clip-path: inset(50% 0 0 0); }
.txt-flip-clock-tens,
.txt-flip-clock-ones {
counter-reset: txt-flip-clock-cur calc(var(--txt-flip-clock-d) + 1) txt-flip-clock-nxt calc(var(--txt-flip-clock-d) + 2);
animation: txt-flip-clock-count calc(var(--p) * var(--n)) steps(var(--n)) var(--at) infinite;
}
.txt-flip-clock-ones i:is(:nth-child(1), :nth-child(4))::after { content: counter(txt-flip-clock-nxt, txt-flip-clock-ten); }
.txt-flip-clock-ones i:is(:nth-child(2), :nth-child(3))::after { content: counter(txt-flip-clock-cur, txt-flip-clock-ten); }
.txt-flip-clock-tens i:is(:nth-child(1), :nth-child(4))::after { content: counter(txt-flip-clock-nxt, txt-flip-clock-six); }
.txt-flip-clock-tens i:is(:nth-child(2), :nth-child(3))::after { content: counter(txt-flip-clock-cur, txt-flip-clock-six); }
.txt-flip-clock-card i:nth-child(3) { z-index: 1; }
.txt-flip-clock-card i:nth-child(4) { z-index: 1; transform: rotateX(90deg); }
.txt-flip-clock-ones i:nth-child(3) { animation: txt-flip-clock-fall 1s var(--at) infinite; }
.txt-flip-clock-ones i:nth-child(4) { animation: txt-flip-clock-land 1s var(--at) infinite; }
.txt-flip-clock-tens i:nth-child(3) { animation: txt-flip-clock-fall-slow 10s var(--at) infinite; }
.txt-flip-clock-tens i:nth-child(4) { animation: txt-flip-clock-land-slow 10s var(--at) infinite; }
@keyframes txt-flip-clock-count {
to { --txt-flip-clock-d: var(--n); }
}
/* each flip takes 0.6s: 0.3s for the top to fall, 0.3s for the bottom to land */
@keyframes txt-flip-clock-fall {
0% { transform: rotateX(0); filter: brightness(1); animation-timing-function: cubic-bezier(0.55, 0, 0.9, 0.5); }
30%, 100% { transform: rotateX(-90deg); filter: brightness(0.55); }
}
@keyframes txt-flip-clock-land {
0%, 30% { transform: rotateX(90deg); filter: brightness(0.55); animation-timing-function: cubic-bezier(0.2, 0.8, 0.35, 1.25); }
60%, 100% { transform: rotateX(0); filter: brightness(1); }
}
@keyframes txt-flip-clock-fall-slow {
0% { transform: rotateX(0); filter: brightness(1); animation-timing-function: cubic-bezier(0.55, 0, 0.9, 0.5); }
3%, 100% { transform: rotateX(-90deg); filter: brightness(0.55); }
}
@keyframes txt-flip-clock-land-slow {
0%, 3% { transform: rotateX(90deg); filter: brightness(0.55); animation-timing-function: cubic-bezier(0.2, 0.8, 0.35, 1.25); }
6%, 100% { transform: rotateX(0); filter: brightness(1); }
}
@media (prefers-reduced-motion: reduce) {
.txt-flip-clock, .txt-flip-clock *, .txt-flip-clock::before, .txt-flip-clock::after, .txt-flip-clock *::before, .txt-flip-clock *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.