AI CSS Animations

Revenue Stat Count Up

A $1.2M ARR stat rolls up from $0.0M with the tenths spinning past, a +38% chip springs in and a sparkline draws beneath. Digit strips and an SVG path with pathLength 100.

Also known as: number animation css, count up stat css, animated kpi number, arr counter animation.

HTML

<div class="txt-revenue-stat-count-up" aria-label="$1.2M annual recurring revenue, up 38% year over year">
  <span class="txt-revenue-stat-count-up-label" aria-hidden="true">Annual recurring revenue</span>
  <span class="txt-revenue-stat-count-up-row" aria-hidden="true">
    <span class="txt-revenue-stat-count-up-num">$<span class="txt-revenue-stat-count-up-col"><span class="txt-revenue-stat-count-up-ones">01</span></span>.<span class="txt-revenue-stat-count-up-col"><span class="txt-revenue-stat-count-up-tenths">0123456789012</span></span>M</span>
    <span class="txt-revenue-stat-count-up-chip">▲ 38%</span>
  </span>
  <svg class="txt-revenue-stat-count-up-spark" viewBox="0 0 120 26" aria-hidden="true"><path pathLength="100" d="M2 22 L16 19 L28 21 L42 15 L56 17 L70 11 L84 12 L98 6 L114 4"/><circle cx="114" cy="4" r="2.6"/></svg>
</div>

CSS

.txt-revenue-stat-count-up {
  --c: var(--txt-color, #3b82f6);
  font-size: calc(var(--txt-size, 32px) * 0.9);
  font-family: system-ui, sans-serif;
  font-weight: 700;
  line-height: 1.2;
  display: inline-grid;
  gap: 0.2em;
  padding: 0.75em 1em 0.7em;
  border: 1px solid color-mix(in srgb, currentColor 12%, transparent);
  border-radius: 0.7em;
  background: color-mix(in srgb, currentColor 3%, transparent);
  --t: 5.6s;}
.txt-revenue-stat-count-up-label {
  font-size: 0.42em;
  font-weight: 500;
  letter-spacing: 0.01em;
  opacity: 0.6;
}
.txt-revenue-stat-count-up-row {
  display: flex;
  align-items: center;
  gap: 0.35em;
  animation: txt-revenue-stat-count-up-row var(--t) ease infinite;
}
.txt-revenue-stat-count-up-num {
  display: inline-flex;
  font-size: 1.9em;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.txt-revenue-stat-count-up-col {
  display: inline-block;
  height: 1.1em;
  overflow: hidden;
  line-height: 1.1em;
  -webkit-mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent);
  mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent);
}
.txt-revenue-stat-count-up-col > span {
  display: block;
  width: 1ch;
  text-align: center;
  word-break: break-all;
}
.txt-revenue-stat-count-up-ones { animation: txt-revenue-stat-count-up-ones var(--t) infinite; }
.txt-revenue-stat-count-up-tenths { animation: txt-revenue-stat-count-up-tenths var(--t) infinite; }
.txt-revenue-stat-count-up-chip {
  padding: 0.25em 0.5em;
  border-radius: 99em;
  font-size: 0.42em;
  font-weight: 650;
  color: #16a34a;
  background: color-mix(in srgb, #22c55e 14%, transparent);
  animation: txt-revenue-stat-count-up-chip var(--t) infinite;
}
.txt-revenue-stat-count-up-spark {
  width: 5.4em;
  height: 1.15em;
  overflow: visible;
  fill: none;
  stroke: var(--c);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.txt-revenue-stat-count-up-spark path {
  stroke-dasharray: 100;
  animation: txt-revenue-stat-count-up-draw var(--t) infinite;
}
.txt-revenue-stat-count-up-spark circle {
  fill: var(--c);
  stroke: none;
  transform-box: fill-box;
  transform-origin: center;
  animation: txt-revenue-stat-count-up-dot var(--t) infinite;
}
@keyframes txt-revenue-stat-count-up-row {
  0% { opacity: 0; filter: blur(0.08em); }
  5%, 86% { opacity: 1; filter: none; }
  94%, 100% { opacity: 0; filter: blur(0.08em); }
}
@keyframes txt-revenue-stat-count-up-ones {
  0% { transform: translateY(0); }
  5.36% { transform: translateY(0); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  28.57% { transform: translateY(-1.1em); }
  95.54% { transform: translateY(-1.1em); }
  95.89%, 100% { transform: translateY(0); }
}
@keyframes txt-revenue-stat-count-up-tenths {
  0% { transform: translateY(0); }
  5.36% { transform: translateY(0); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  35.71% { transform: translateY(-13.2em); }
  95.54% { transform: translateY(-13.2em); }
  95.89%, 100% { transform: translateY(0); }
}
@keyframes txt-revenue-stat-count-up-chip {
  0%, 28% { opacity: 0; transform: scale(0.6); }
  33% { opacity: 1; transform: scale(1.08); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  38%, 86% { opacity: 1; transform: none; }
  94%, 100% { opacity: 0; transform: none; }
}
@keyframes txt-revenue-stat-count-up-draw {
  0%, 6% { stroke-dashoffset: 100; opacity: 1; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  40%, 86% { stroke-dashoffset: 0; opacity: 1; }
  94%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes txt-revenue-stat-count-up-dot {
  0%, 30% { opacity: 0; transform: scale(0); }
  36% { opacity: 1; transform: scale(1.5); }
  42%, 86% { opacity: 1; transform: scale(1); }
  94%, 100% { opacity: 0; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .txt-revenue-stat-count-up, .txt-revenue-stat-count-up *, .txt-revenue-stat-count-up *::before, .txt-revenue-stat-count-up *::after { animation: none !important; }
  .txt-revenue-stat-count-up-ones { transform: translateY(-1.1em); }
  .txt-revenue-stat-count-up-tenths { transform: translateY(-13.2em); }
}

@media (prefers-reduced-motion: reduce) {
  .txt-revenue-stat-count-up, .txt-revenue-stat-count-up *, .txt-revenue-stat-count-up::before, .txt-revenue-stat-count-up::after, .txt-revenue-stat-count-up *::before, .txt-revenue-stat-count-up *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Customizing this snippet

  • Color: set --txt-color on the element or a parent. Currently previewing #3b82f6.
  • Font size: set --txt-size. Every other dimension is in em, so the whole thing scales together. Currently previewing 40px.
  • Speed: change the duration on the transition or animation shorthand. Staggered delays are relative, so they keep their rhythm.
  • Reduced motion: the rule at the end of the CSS turns the motion off for people who asked their OS for less of it. Keep it.

Similar text animations