About this snippet

Three headline statistics count up from zero when the page loads, each with its own duration, and settle on the real figures.

CSS can animate a custom property once @property has declared its type, and the property is set to inherit so the child that prints it sees the animated value. Each figure stores its target in a custom property, the animation runs a registered integer from zero to that target, and counter-reset reads the integer so counter() can print it through a pseudo-element. Because the property is an integer the number ticks through whole values. The percent and plus suffixes come from a second pseudo-element, and the integer prints without a thousands separator because counter styles cannot insert one.

@property is Baseline since 2024. An older browser that cannot register the property shows the final figure straight away, which is the right fallback for a number.

Size
2.0 KB
Uses
HTML, CSS
Loads
Nothing external
Views
131
<section class="stats">
  <div class="stat" style="--target: 12480;">
    <span class="figure" aria-hidden="true"></span>
    <span class="sr-only">12480</span>
    <p>Developers on the platform</p>
  </div>
  <div class="stat" style="--target: 98;">
    <span class="figure percent" aria-hidden="true"></span>
    <span class="sr-only">98 percent</span>
    <p>Uptime over the last year</p>
  </div>
  <div class="stat" style="--target: 340;">
    <span class="figure plus" aria-hidden="true"></span>
    <span class="sr-only">340 plus</span>
    <p>Integrations available</p>
  </div>
</section>
@property --count {
  syntax: "<integer>";
  initial-value: 0;
  inherits: true;
}

*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  background: #0f172a;
  color: #f8fafc;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 20px;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 20px;
  width: 100%;
  max-width: 720px;
}

.stat {
  background: #1e293b;
  border-radius: 18px;
  padding: 28px 24px;
  text-align: center;
  --count: var(--target);
  animation: count-up 2s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.stat:nth-child(2) {
  animation-duration: 1.4s;
}

.stat:nth-child(3) {
  animation-duration: 1.7s;
}

@keyframes count-up {
  from { --count: 0; }
}

.figure {
  display: block;
  font-size: 2.75rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  color: #ffffff;
  counter-reset: figure var(--count);
  font-variant-numeric: tabular-nums;
}

.figure::before {
  content: counter(figure);
}

.figure.percent::after {
  content: "%";
  color: #38bdf8;
}

.figure.plus::after {
  content: "+";
  color: #38bdf8;
}

.stat p {
  margin: 12px 0 0;
  font-size: 0.9375rem;
  color: #94a3b8;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save