{"id":8762,"date":"2026-03-02T09:10:45","date_gmt":"2026-03-02T14:10:45","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=8762"},"modified":"2026-03-02T09:10:46","modified_gmt":"2026-03-02T14:10:46","slug":"the-odometer-effect-in-css","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/the-odometer-effect-in-css\/","title":{"rendered":"The Odometer Effect (without JavaScript)"},"content":{"rendered":"\n<p>With CSS, we can fill numbers into HTML elements now, thanks to the <code>attr()<\/code> function and a bit of trickery. This allows design effects to be applied to those numbers. Today, we&#8217;ll look at an odometer effect, meaning numbers that &#8220;spin&#8221; vertically, like the mileage meter on a vehicle. This effect is useful for dynamically displaying numeric values and drawing the user\u2019s attention when the values change, such as a rolling number of online users, a tracked price, or a timer.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_myEZKME\" src=\"\/\/codepen.io\/anon\/embed\/myEZKME?height=450&amp;theme-id=1&amp;slug-hash=myEZKME&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed myEZKME\" title=\"CodePen Embed myEZKME\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The above example shows an amount upto the place value of millions. I\u2019ll include more examples as we go.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">data<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"amount\"<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"3284915\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Millions --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Hundred Thousands --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Ten Thousands --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Thousands --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Hundreds --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Tens --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- Ones --&gt;<\/span> <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">data<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The amount is in the\u00a0<code>value<\/code>\u00a0attribute of the\u00a0<code>&lt;data><\/code>\u00a0element. You can use any other suitable element and attribute combination, like\u00a0<code>&lt;div data-price=\"60589\"><\/code>. I\u2019ve not included the comma separator in the HTML now; we\u2019ll get to that later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"autofill-numbers\">Autofill Numbers<\/h2>\n\n\n\n<p>Let\u2019s first <em>get<\/em> the number from the HTML attribute into a CSS variable using the\u00a0<code>attr(&lt;attr-name> &lt;attr-type>)<\/code>\u00a0function.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-id\">#amount<\/span> {\n  <span class=\"hljs-attribute\">--amt<\/span>: <span class=\"hljs-built_in\">attr<\/span>(value number);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We\u2019ll also need each&nbsp;<code>.digit<\/code>\u2019s position, for which we use&nbsp;<code>sibling-index()<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-id\">#amount<\/span> {\n  <span class=\"hljs-attribute\">--amt<\/span>: <span class=\"hljs-built_in\">attr<\/span>(value number);\n\n  .digit {\n    <span class=\"hljs-attribute\">--si<\/span>: <span class=\"hljs-built_in\">sibling-index<\/span>();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, we fill each&nbsp;<code>.digit<\/code>\u2019s pseudo-elements with each digit from the number. To extract the digits from the number one by one, we use the&nbsp;<code>mod()<\/code>&nbsp;function.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.amt<\/span> {\n  <span class=\"hljs-attribute\">--amt<\/span>: <span class=\"hljs-built_in\">attr<\/span>(value number);\n\n  .digit {\n    <span class=\"hljs-attribute\">--si<\/span>: <span class=\"hljs-built_in\">sibling-index<\/span>();\n\n    <span class=\"hljs-comment\">\/* autofill digits *\/<\/span>\n    &amp;::after {\n       <span class=\"hljs-comment\">\/* Divide the number by the power of 10, round down, \n          and use mod() to isolate a single integer (0-9) *\/<\/span>\n      <span class=\"hljs-attribute\">counter-set<\/span>: n <span class=\"hljs-built_in\">mod<\/span>(round(down,var(--amt)\/(<span class=\"hljs-number\">10000<\/span>\/<span class=\"hljs-built_in\">pow<\/span>(<span class=\"hljs-number\">10<\/span>,var(--si)<span class=\"hljs-built_in\">-1<\/span>))),<span class=\"hljs-number\">10<\/span>);\n      <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">counter<\/span>(n);\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"learn-more\">The CSS <code>mod()<\/code> function returns the remainder of a division.<\/p>\n\n\n\n<p>To make it easier to demonstrate, here\u2019s an example of autofilling digits for a three-digit number:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">data<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"weight\"<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"420\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  gms\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">data<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-id\">#weight<\/span> {\n  <span class=\"hljs-attribute\">--wgt<\/span>: <span class=\"hljs-built_in\">attr<\/span>(value number);\n\n  .digit {\n    <span class=\"hljs-attribute\">--si<\/span>: <span class=\"hljs-built_in\">sibling-index<\/span>();\n\n    &amp;::after {\n      <span class=\"hljs-attribute\">counter-set<\/span>: n <span class=\"hljs-built_in\">mod<\/span>(round(down,var(--wgt)\/(<span class=\"hljs-number\">100<\/span>\/<span class=\"hljs-built_in\">pow<\/span>(<span class=\"hljs-number\">10<\/span>,var(--si)<span class=\"hljs-built_in\">-1<\/span>))),<span class=\"hljs-number\">10<\/span>);\n      <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">counter<\/span>(n);\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here\u2019s how the math works:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sibling-index() = 1<br><br>mod(round(down, 420\/(100\/pow(10,1-1))), 10)<br>mod(round(down, 420\/(100\/pow(10, 0))), 10)<br>mod(round(down, 420\/(100\/1)), 10)<br>mod(round(down, 420\/100), 10)<br>mod(round(down, 4.2), 10)<br>mod(4, 10)<br><strong>= 4<br><\/strong><br><br>sibling-index() = 2<br><br>mod(round(down, 420\/(100\/pow(10,2-1))), 10)<br>mod(round(down, 420\/(100\/pow(10, 1))), 10)<br>mod(round(down, 420\/(100\/10)), 10)<br>mod(round(down, 420\/10), 10)<br>mod(round(down, 42), 10)<br>mod(42, 10)<br><strong>= 2<br><\/strong><br><br>sibling-index() = 3<br><br>mod(round(down, 420\/(100\/pow(10,3-1))), 10)<br>mod(round(down, 420\/(100\/pow(10, 2))), 10)<br>mod(round(down, 420\/(100\/100)), 10)<br>mod(round(down, 420\/1), 10)<br>mod(round(down, 420), 10)<br>mod(420, 10)<br><strong>= 0<\/strong><\/pre>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_dPpbGNJ\" src=\"\/\/codepen.io\/anon\/embed\/dPpbGNJ?height=450&amp;theme-id=1&amp;slug-hash=dPpbGNJ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dPpbGNJ\" title=\"CodePen Embed dPpbGNJ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"adding-separators\">Adding Separators<\/h2>\n\n\n\n<p>When we add a separator character in the mix, using&nbsp;<code>sibling-index()<\/code>&nbsp;alone won\u2019t give the right position of the digits following the separator. We have to exclude the separators from the math. Here\u2019s an example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">data<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"amount\"<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"7459328\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"separator\"<\/span>&gt;<\/span>,<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"separator\"<\/span>&gt;<\/span>,<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"digit\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n  KRW\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">data<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.digit<\/span> {\n  <span class=\"hljs-attribute\">--si<\/span>: <span class=\"hljs-built_in\">sibling-index<\/span>();\n\n  &amp;::after {\n    <span class=\"hljs-attribute\">counter-set<\/span>: n <span class=\"hljs-built_in\">mod<\/span>(round(down,var(--amt)\/(<span class=\"hljs-number\">1000000<\/span>\/<span class=\"hljs-built_in\">pow<\/span>(<span class=\"hljs-number\">10<\/span>,var(--i)))),<span class=\"hljs-number\">10<\/span>);\n    <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">counter<\/span>(n);\n  }\n\n  <span class=\"hljs-comment\">\/* first two digits  *\/<\/span>\n  &amp;<span class=\"hljs-selector-pseudo\">:nth-child(-n+2)<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> { \n    <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-built_in\">var<\/span>(--si) - <span class=\"hljs-number\">1<\/span>; \n  }\n\n  <span class=\"hljs-comment\">\/* third and fourth digits *\/<\/span>\n  &amp;<span class=\"hljs-selector-pseudo\">:where(<\/span><span class=\"hljs-selector-pseudo\">:nth-child(3<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-class\">.digit<\/span>),<span class=\"hljs-selector-pseudo\">:nth-child(4<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-class\">.digit<\/span>))<span class=\"hljs-selector-pseudo\">::after<\/span> { \n    <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-built_in\">var<\/span>(--si) - <span class=\"hljs-number\">2<\/span>; \n  }\n\n  <span class=\"hljs-comment\">\/* last three digits *\/<\/span>\n  &amp;<span class=\"hljs-selector-pseudo\">:nth-last-child(-n+3)<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> { \n    <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-built_in\">var<\/span>(--si) - <span class=\"hljs-number\">3<\/span>; \n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For each separator break, decrement the sibling index by 1 for the following digits.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_azmodJJ\" src=\"\/\/codepen.io\/anon\/embed\/azmodJJ?height=450&amp;theme-id=1&amp;slug-hash=azmodJJ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed azmodJJ\" title=\"CodePen Embed azmodJJ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-animation\">The Animation<\/h2>\n\n\n\n<p>Now that the digits can be automatically separated into distinct elements, we can apply any animation we want to them individually. For the odometer effect, I\u2019m adding animations that slide the digits up and down as the count decreases, mimicking the rolling style.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@property<\/span> --n {\n  <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">integer<\/span>&gt;\";\n  <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n  <span class=\"hljs-selector-tag\">initial-value<\/span>: 0;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> count {\n  <span class=\"hljs-selector-tag\">from<\/span> { <span class=\"hljs-attribute\">--n<\/span>: <span class=\"hljs-number\">9<\/span>;  } \n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">--n<\/span>: <span class=\"hljs-number\">0<\/span>; }\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> slideDown {\n  <span class=\"hljs-selector-tag\">from<\/span> { <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateY<\/span>(-<span class=\"hljs-number\">100%<\/span>); }\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateY<\/span>(<span class=\"hljs-number\">100%<\/span>); }\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> slideUp {\n  <span class=\"hljs-selector-tag\">from<\/span> { <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateY<\/span>(<span class=\"hljs-number\">100%<\/span>); }\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateY<\/span>(-<span class=\"hljs-number\">100%<\/span>); }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The&nbsp;<code>--n<\/code>&nbsp;variable, of integer type, is animated in the&nbsp;<code>@keyframes<\/code>&nbsp;animation&nbsp;<code>count<\/code>, decrementing from 9 to 0.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">&amp;<span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-comment\">\/* Save the digit in a variable *\/<\/span>\n  <span class=\"hljs-attribute\">--digit<\/span>: <span class=\"hljs-built_in\">mod<\/span>(round(down, var(--amt) \/ (<span class=\"hljs-number\">1000000<\/span>\/<span class=\"hljs-built_in\">pow<\/span>(<span class=\"hljs-number\">10<\/span>, var(--i)))), <span class=\"hljs-number\">10<\/span>);\n\n  <span class=\"hljs-comment\">\/* Show whichever is higher: the active countdown value (--n) or the target digit. Prevents the counter from dropping below the final value. *\/<\/span>\n  <span class=\"hljs-attribute\">counter-set<\/span>: n <span class=\"hljs-built_in\">max<\/span>(var(--n), <span class=\"hljs-built_in\">var<\/span>(--digit));\n  <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">counter<\/span>(n);\n\n  <span class=\"hljs-comment\">\/* The 1s is the countdown animation. \n     The 0.11s (1\/9) slide animation repeats until countdown hits the target digit. *\/<\/span>\n  <span class=\"hljs-attribute\">animation<\/span>: linear <span class=\"hljs-number\">1s<\/span>, linear <span class=\"hljs-number\">0.11s<\/span> <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">9<\/span> - var(--digit)) ;\n}\n&amp;<span class=\"hljs-selector-pseudo\">:nth-of-type(even)<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">animation-name<\/span>: count, slideUp;\n}\n&amp;<span class=\"hljs-selector-pseudo\">:nth-of-type(odd)<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">animation-name<\/span>: count, slideDown;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The demo from before:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_myEZKME\" src=\"\/\/codepen.io\/anon\/embed\/myEZKME?height=450&amp;theme-id=1&amp;slug-hash=myEZKME&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed myEZKME\" title=\"CodePen Embed myEZKME\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"varying-speed-and-style\">Varying Speed and Style<\/h2>\n\n\n\n<p>Since the animation uses repeated vertical displacement to create the rolling effect, to speed up, pause, or slow down the digits, either by count or position (sibling index), set any animation&#8217;s time, delay, or repetition based on the count, position, or both.<\/p>\n\n\n\n<p>Here\u2019s an example where the later counts are slightly slower:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">&amp;<span class=\"hljs-selector-pseudo\">:after<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-number\">1.4s<\/span> linear, \n    <span class=\"hljs-number\">0.11s<\/span> linear <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">5<\/span> - var(--digit)), \n    <span class=\"hljs-number\">0.22s<\/span> linear <span class=\"hljs-number\">0.55s<\/span> <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">4<\/span> - var(--digit)); \n}\n&amp;<span class=\"hljs-selector-pseudo\">:nth-of-type(even)<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">animation-name<\/span>: count, slideUp, slideUp;\n}\n&amp;<span class=\"hljs-selector-pseudo\">:nth-of-type(odd)<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">animation-name<\/span>: count, slideDown, slideDown;\n} <\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MYjgreW\" src=\"\/\/codepen.io\/anon\/embed\/MYjgreW?height=450&amp;theme-id=1&amp;slug-hash=MYjgreW&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MYjgreW\" title=\"CodePen Embed MYjgreW\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Here\u2019s one where there\u2019s no count or rolling, just a jittery effect.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">animation<\/span>: 0<span class=\"hljs-selector-class\">.1s<\/span> <span class=\"hljs-selector-tag\">linear<\/span> <span class=\"hljs-selector-tag\">calc<\/span>(0<span class=\"hljs-selector-class\">.1s<\/span> * <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--si<\/span>));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_pvEzpRz\" src=\"\/\/codepen.io\/anon\/embed\/pvEzpRz?height=450&amp;theme-id=1&amp;slug-hash=pvEzpRz&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed pvEzpRz\" title=\"CodePen Embed pvEzpRz\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Although this post covered the odometer effect, its concept can be applied to other graphic effects involving numbers. Being able to autofill numbers into individual elements, and compute and animate them, all in CSS, simplifies designing visual changes for dynamic numeric values on screen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We can take a value set in an HTML attribute and use it in CSS, even extracting each individual digit in order to animate separately. <\/p>\n","protected":false},"author":20,"featured_media":8770,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[134,300,7,30],"class_list":["post-8762","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-property","tag-attr","tag-css","tag-numbers"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/02\/odometer.jpg?fit=2100%2C1321&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8762","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=8762"}],"version-history":[{"count":5,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8762\/revisions"}],"predecessor-version":[{"id":8777,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8762\/revisions\/8777"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/8770"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=8762"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=8762"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=8762"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}