{"id":1113,"date":"2025-11-21T07:00:00","date_gmt":"2025-11-21T08:00:00","guid":{"rendered":"https:\/\/computercoursesonline.com\/?p=1113"},"modified":"2025-11-27T21:09:22","modified_gmt":"2025-11-27T21:09:22","slug":"keyframes-tokens-standardizing-animation-across-projects","status":"publish","type":"post","link":"https:\/\/computercoursesonline.com\/index.php\/2025\/11\/21\/keyframes-tokens-standardizing-animation-across-projects\/","title":{"rendered":"Keyframes Tokens: Standardizing Animation Across Projects"},"content":{"rendered":"

Keyframes Tokens: Standardizing Animation Across Projects<\/title><\/p>\n<article>\n<header>\n<h1>Keyframes Tokens: Standardizing Animation Across Projects<\/h1>\n<address>Amit Sheen<\/address>\n<p> 2025-11-21T08:00:00+00:00<br \/>\n 2025-11-27T20:33:09+00:00<br \/>\n <\/header>\n<p>Picture this: you join a new project, dive into the codebase, and within the first few hours, you discover something frustratingly familiar. Scattered throughout the stylesheets, you find multiple <code>@keyframes<\/code> definitions for the same basic animations. Three different fade-in effects, two or three slide variations, a handful of zoom animations, and at least two different spin animations because, well, why not?<\/p>\n<pre><code class=\"language-css\">@keyframes pulse {\n from {\n scale: 1;\n }\n to {\n scale: 1.1;\n }\n}\n\n@keyframes bigger-pulse {\n 0%, 20%, 100% { \n scale: 1; \n }\n 10%, 40% { \n scale: 1.2; \n }\n}\n<\/code><\/pre>\n<p>If this scenario sounds familiar, you\u2019re not alone. In my experience across various projects, one of the most consistent quick wins I can deliver is <strong>consolidating and standardizing keyframes<\/strong>. It\u2019s become such a reliable pattern that I now look forward to this cleanup as one of my first tasks on any new codebase.<\/p>\n<h2 id=\"the-logic-behind-the-chaos\">The Logic Behind The Chaos<\/h2>\n<p>This redundancy makes perfect sense when you think about it. We all use the same fundamental animations in our day-to-day work: fades, slides, zooms, spins, and other common effects. These animations are pretty straightforward, and it’s easy to whip up a quick <code>@keyframes<\/code> definition to get the job done.<\/p>\n<p>Without a centralized animation system, developers naturally write these keyframes from scratch, unaware that similar animations already exist elsewhere in the codebase. This is especially common when working in component-based architectures (which most of us do these days), as teams often work in parallel across different parts of the application.<\/p>\n<p>The result? Animation chaos.<\/p>\n<div data-audience=\"non-subscriber\" data-remove=\"true\" class=\"feature-panel-container\">\n<aside class=\"feature-panel\">\n<div class=\"feature-panel-left-col\">\n<div class=\"feature-panel-description\">\n<p>Meet <strong><a data-instant href=\"https:\/\/www.smashingconf.com\/online-workshops\/\">Smashing Workshops<\/a><\/strong> on <strong>front-end, design & UX<\/strong>, with practical takeaways, live sessions, <strong>video recordings<\/strong> and a friendly Q&A. With Brad Frost, St\u00e9ph Walter and <a href=\"https:\/\/smashingconf.com\/online-workshops\/workshops\">so many others<\/a>.<\/p>\n<p><a data-instant href=\"smashing-workshops\" class=\"btn btn--green btn--large\">Jump to the workshops \u21ac<\/a><\/div>\n<\/div>\n<div class=\"feature-panel-right-col\"><a data-instant href=\"smashing-workshops\" class=\"feature-panel-image-link\"><\/p>\n<div class=\"feature-panel-image\">\n<img loading=\"lazy\" class=\"feature-panel-image-img lazyload\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Feature Panel\" width=\"257\" height=\"355\" data-src=\"\/images\/smashing-cat\/cat-scubadiving-panel.svg\"><\/p>\n<\/div>\n<p><\/a>\n<\/div>\n<\/aside>\n<\/div>\n<h2 id=\"the-small-problem\">The Small Problem<\/h2>\n<p>The most obvious issues with keyframes duplication are wasted development time and unnecessary code bloat. Multiple keyframe definitions mean multiple places to update when requirements change. Need to adjust the timing of your fade animation? You\u2019ll need to hunt down every instance across your codebase. Want to standardize easing functions? Good luck finding all the variations. This multiplication of maintenance points makes even simple animation updates a time-consuming task.<\/p>\n<h2 id=\"the-bigger-problem\">The Bigger Problem<\/h2>\n<p>This keyframes duplication creates a much more insidious problem lurking beneath the surface: <strong>the global scope trap.<\/strong> Even when working with component-based architectures, CSS keyframes are always defined in the global scope. This means all keyframes apply to all components. Always. Yes, your animation doesn’t necessarily use the keyframes you defined in your component. It uses the last keyframes that match that exact same name that were loaded into the global scope.<\/p>\n<p>As long as all your keyframes are identical, this might seem like a minor issue. But the moment you want to customize an animation for a specific use case, you\u2019re in trouble, or worse, you\u2019ll be the one causing them.<\/p>\n<p>Either your animation won\u2019t work because another component loaded after yours, overwriting your keyframes, or your component loads last and accidentally changes the animation behavior for every other component using that keyframe’s name, and you may not even realize it.<\/p>\n<p>Here\u2019s a simple example that demonstrates the problem:<\/p>\n<pre><code class=\"language-css\">.component-one {\n \/* component styles *\/\n animation: pulse 1s ease-in-out infinite alternate;\n}\n\n\/* this @keyframes definition will not work *\/\n@keyframes pulse {\n from {\n scale: 1;\n }\n to {\n scale: 1.1;\n }\n} \n\n\/* later in the code... *\/\n\n.component-two {\n \/* component styles *\/\n animation: pulse 1s ease-in-out infinite;\n}\n\n\/* this keyframes will apply to both components *\/\n@keyframes pulse {\n 0%, 20%, 100% { \n scale: 1; \n }\n 10%, 40% { \n scale: 1.2; \n }\n}\n<\/code><\/pre>\n<p>Both components use the same animation name, but the second <code>@keyframes<\/code> definition overwrites the first one. Now both <code>component-one<\/code> and <code>component-two<\/code> will use the second keyframes, regardless of which component defined which keyframes.<\/p>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"JoXrOqz\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 1 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/JoXrOqz) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/JoXrOqz\">Keyframes Tokens – Demo 1 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>The worst part? This often works perfectly in local development but breaks mysteriously in production when build processes change the loading order of your stylesheets. You end up with animations that behave differently depending on which components are loaded and in what sequence.<\/p>\n<h2 id=\"the-solution-unified-keyframes\">The Solution: Unified Keyframes<\/h2>\n<p>The answer to this chaos is surprisingly simple: <strong>predefined dynamic keyframes stored in a shared stylesheet<\/strong>. Instead of letting every component define its own animations, we create centralized keyframes that are well-documented, easy to use, maintainable, and tailored to the specific needs of your project.<\/p>\n<p>Think of it as <strong>keyframes tokens<\/strong>. Just as we <a href=\"https:\/\/www.smashingmagazine.com\/2024\/05\/naming-best-practices\/\">use tokens for colors and spacing<\/a>, and many of us already use tokens for animation properties, like duration and easing functions, why not use tokens for keyframes as well?<\/p>\n<p>This approach can integrate naturally with any current design token workflow you\u2019re using, while solving both the small problem (code duplication) and the bigger problem (global scope conflicts) in one go.<\/p>\n<p><strong>The idea is straightforward:<\/strong> create a single source of truth for all our common animations. This shared stylesheet contains carefully crafted keyframes that cover the animation patterns our project actually uses. No more guessing whether a fade animation already exists somewhere in our codebase. No more accidentally overwriting animations from other components.<\/p>\n<p>But here\u2019s the key: these aren\u2019t just static copy-paste animations. They\u2019re designed to be dynamic and customizable through CSS custom properties, allowing us to maintain consistency while still having the flexibility to adapt animations to specific use cases, like if you need a slightly bigger \u201cpulse\u201d animation in one place.<\/p>\n<h2 id=\"building-the-first-keyframes-token\">Building The First Keyframes Token<\/h2>\n<p>One of the first low-hanging fruits we should tackle is the \u201cfade-in\u201d animation. In one of my recent projects, I found over a dozen separate fade-in definitions, and yes, they all simply animated the <code>opacity<\/code> from <code>0<\/code> to <code>1<\/code>.<\/p>\n<p>So, let\u2019s create a new stylesheet, call it <code>kf-tokens.css<\/code>, import it into our project, and place our keyframes with proper comments inside of it.<\/p>\n<pre><code class=\"language-css\">\/* keyframes-tokens.css *\/\n\n\/*\n * Fade In - fade entrance animation\n * Usage: animation: kf-fade-in 0.3s ease-out;\n *\/\n@keyframes kf-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n<\/code><\/pre>\n<p>This single <code>@keyframes<\/code> declaration replaces all those scattered fade-in animations across our codebase. Clean, simple, and globally applicable. And now that we have this token defined, we can use it from any component throughout our project:<\/p>\n<pre><code class=\"language-css\">.modal {\n animation: kf-fade-in 0.3s ease-out;\n}\n\n.tooltip {\n animation: kf-fade-in 0.2s ease-in-out;\n}\n\n.notification {\n animation: kf-fade-in 0.5s ease-out;\n}\n<\/code><\/pre>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"yyOzPdv\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 2 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/yyOzPdv) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/yyOzPdv\">Keyframes Tokens – Demo 2 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p><strong>Note:<\/strong> <em>We\u2019re using a <code>kf-<\/code> prefix in all our <code>@keyframes<\/code> names. This prefix serves as a namespace that prevents naming conflicts with existing animations in the project and makes it immediately clear that these keyframes come from our keyframes tokens file.<\/em><\/p>\n<h2 id=\"making-a-dynamic-slide\">Making A Dynamic Slide<\/h2>\n<p>The <code>kf-fade-in<\/code> keyframes work great because it’s simple and there’s little room to mess things up. In other animations, however, we need to be much more dynamic, and here we can leverage the enormous power of <a href=\"https:\/\/www.smashingmagazine.com\/2017\/04\/start-using-css-custom-properties\/\">CSS custom properties<\/a>. This is where keyframes tokens really shine compared to scattered static animations.<\/p>\n<p>Let\u2019s take a common scenario: \u201cslide-in\u201d animations. But slide in from where? <code>100px<\/code> from the right? <code>50%<\/code> from the left? Should it enter from the top of the screen? Or maybe float in from the bottom? So many possibilities, but instead of creating separate keyframes for each direction and each variation, we can build one flexible token that adapts to all scenarios:<\/p>\n<pre><code class=\"language-css\">\/*\n * Slide In - directional slide animation\n * Use --kf-slide-from to control direction\n * Default: slides in from left (-100%)\n * Usage: \n * animation: kf-slide-in 0.3s ease-out;\n * --kf-slide-from: -100px 0; \/\/ slide from left\n * --kf-slide-from: 100px 0; \/\/ slide from right\n * --kf-slide-from: 0 -50px; \/\/ slide from top\n *\/\n\n@keyframes kf-slide-in {\n from {\n translate: var(--kf-slide-from, -100% 0);\n }\n to {\n translate: 0 0;\n }\n}\n<\/code><\/pre>\n<p>Now we can use this single <code>@keyframes<\/code> token for any slide direction simply by changing the <code>--kf-slide-from<\/code> custom property:<\/p>\n<pre><code class=\"language-css\">.sidebar {\n animation: kf-slide-in 0.3s ease-out;\n \/* Uses default value: slides from left *\/\n}\n\n.notification {\n animation: kf-slide-in 0.4s ease-out;\n --kf-slide-from: 0 -50px; \/* slide from top *\/\n}\n\n.modal {\n animation:\n kf-fade-in 0.5s,\n kf-slide-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);\n --kf-slide-from: 50px 50px; \/* slide from bottom-right *\/\n}\n<\/code><\/pre>\n<p>This approach gives us incredible flexibility while maintaining consistency. One keyframe declaration, infinite possibilities.<\/p>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"raeGYXr\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 3 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/raeGYXr) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/raeGYXr\">Keyframes Tokens – Demo 3 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>And if we want to make our animations even more flexible, allowing for \u201cslide-out\u201d effects as well, we can simply add a <code>--kf-slide-to<\/code> custom property, similar to what we\u2019ll see in the next section.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"bidirectional-zoom-keyframes\">Bidirectional Zoom Keyframes<\/h2>\n<p>Another common animation that gets duplicated across projects is \u201czoom\u201d effects. Whether it\u2019s a subtle scale-up for toast messages, a dramatic zoom-in for modals, or a gentle scale-down effect for headings, zoom animations are everywhere.<\/p>\n<p>Instead of creating separate keyframes for each scale value, let\u2019s build one flexible set of <code>kf-zoom<\/code> keyframes:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">\/*\n * Zoom - scale animation\n * Use --kf-zoom-from and --kf-zoom-to to control scale values\n * Default: zooms from 80% to 100% (0.8 to 1)\n * Usage:\n * animation: kf-zoom 0.2s ease-out;\n * --kf-zoom-from: 0.5; --kf-zoom-to: 1; \/\/ zoom from 50% to 100%\n * --kf-zoom-from: 1; --kf-zoom-to: 0; \/\/ zoom from 100% to 0%\n * --kf-zoom-from: 1; --kf-zoom-to: 1.1; \/\/ zoom from 100% to 110%\n *\/\n\n@keyframes kf-zoom {\n from {\n scale: var(--kf-zoom-from, 0.8);\n }\n to {\n scale: var(--kf-zoom-to, 1);\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>With one definition, we can achieve any zoom variation we need:<\/p>\n<pre><code class=\"language-css\">.toast {\n animation:\n kf-slide-in 0.2s,\n kf-zoom 0.4s ease-out;\n --kf-slide-from: 0 100%; \/* slide from top *\/\n \/* Uses default zoom: scales from 80% to 100% *\/\n}\n\n.modal {\n animation: kf-zoom 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n --kf-zoom-from: 0; \/* dramatic zoom from 0% to 100% *\/\n}\n\n.heading {\n animation:\n kf-fade-in 2s,\n kf-zoom 2s ease-in;\n --kf-zoom-from: 1.2; \n --kf-zoom-to: 0.8; \/* gentle scale down *\/\n}\n<\/code><\/pre>\n<p>The default of <code>0.8<\/code> (80%) works perfectly for most UI elements, like toast messages and cards, while still being easy to customize for special cases.<\/p>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"WbwZdQZ\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 4 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/WbwZdQZ) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/WbwZdQZ\">Keyframes Tokens – Demo 4 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>You might have noticed something interesting in the recent examples: we’ve been <strong>combining animations<\/strong>. One of the key advantages of working with <code>@keyframes<\/code> tokens is that they\u2019re designed to integrate seamlessly with each other. This smooth composition is intentional, not accidental.<\/p>\n<p>We\u2019ll discuss animation composition in more detail later, including where they can become problematic, but most combinations are straightforward and easy to implement.<\/p>\n<p><strong>Note:<\/strong> <em>While writing this article, and maybe because of writing it, I found myself rethinking the whole idea of entrance animations. With all the recent advances in CSS, do we still need them at all? Luckily, Adam Argyle explored the same questions and expressed them brilliantly <a href=\"https:\/\/nerdy.dev\/using-starting-style-and-transition-behavior-for-enter-and-exit-stage-effects\">in his blog<\/a>. This doesn\u2019t contradict what\u2019s written here, but it does present an approach worth considering, especially if your projects rely heavily on entrance animations.<\/em><\/p>\n<h2 id=\"continuous-animations\">Continuous Animations<\/h2>\n<p>While entrance animations, like \u201cfade\u201d, \u201cslide\u201d, and \u201czoom\u201d happen once and then stop, continuous animations loop indefinitely to draw attention or indicate ongoing activity. The two most common continuous animations I encounter are \u201cspin\u201d (for loading indicators) and \u201cpulse\u201d (for highlighting important elements).<\/p>\n<p>These animations present unique challenges when it comes to creating keyframes tokens. Unlike entrance animations that typically go from one state to another, continuous animations need to be highly customizable in their behavior patterns.<\/p>\n<h3 id=\"the-spin-doctor\">The Spin Doctor<\/h3>\n<p>Every project seems to use multiple spin animations. Some spin clockwise, others counterclockwise. Some do a single 360-degree rotation, others do multiple turns for a faster effect. Instead of creating separate keyframes for each variation, let\u2019s build one flexible spin that handles all scenarios:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">\/*\n * Spin - rotation animation\n * Use --kf-spin-from and --kf-spin-to to control rotation range\n * Use --kf-spin-turns to control rotation amount\n * Default: rotates from 0deg to 360deg (1 full rotation)\n * Usage:\n * animation: kf-spin 1s linear infinite;\n * --kf-spin-turns: 2; \/\/ 2 full rotations\n * --kf-spin-from: 0deg; --kf-spin-to: 180deg; \/\/ half rotation\n * --kf-spin-from: 0deg; --kf-spin-to: -360deg; \/\/ counterclockwise\n *\/\n\n@keyframes kf-spin {\n from {\n rotate: var(--kf-spin-from, 0deg);\n }\n to {\n rotate: calc(var(--kf-spin-from, 0deg) + var(--kf-spin-to, 360deg) * var(--kf-spin-turns, 1));\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>Now we can create any spin variation we like:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">.loading-spinner {\n animation: kf-spin 1s linear infinite;\n \/* Uses default: rotates from 0deg to 360deg *\/\n} \n\n.fast-loader {\n animation: kf-spin 1.2s ease-in-out infinite alternate;\n --kf-spin-turns: 3; \/* 3 full rotations for each direction per cycle *\/\n}\n\n.steped-reverse {\n animation: kf-spin 1.5s steps(8) infinite;\n --kf-spin-to: -360deg; \/* counterclockwise *\/\n}\n\n.subtle-wiggle {\n animation: kf-spin 2s ease-in-out infinite alternate;\n --kf-spin-from: -16deg;\n --kf-spin-to: 32deg; \/* wiggle 36 deg: between -18deg and +18deg *\/\n}\n<\/code><\/pre>\n<\/div>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"MYyErbq\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 5 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/MYyErbq) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/MYyErbq\">Keyframes Tokens – Demo 5 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>The beauty of this approach is that the same keyframes work for loading spinners, rotating icons, wiggle effects, and even complex multi-turn animations.<\/p>\n<h2 id=\"the-pulse-paradox\">The Pulse Paradox<\/h2>\n<p>Pulse animations are trickier because they can \u201cpulse\u201d different properties. Some pulse the <code>scale<\/code>, others pulse the <code>opacity<\/code>, and some pulse <code>color<\/code> properties like brightness or saturation. Rather than creating separate keyframes for each property, we can create keyframes that work with any CSS property.<\/p>\n<p>Here’s an example of a pulse keyframe with <code>scale<\/code> and <code>opacity<\/code> options:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">\/* \n * Pulse - pulsing animation\n * Use --kf-pulse-scale-from and --kf-pulse-scale-to to control scale range\n * Use --kf-pulse-opacity-from and --kf-pulse-opacity-to to control opacity range\n * Default: no pulse (all values 1)\n * Usage:\n * animation: kf-pulse 2s ease-in-out infinite alternate;\n * --kf-pulse-scale-from: 0.95; --kf-pulse-scale-to: 1.05; \/\/ scale pulse\n * --kf-pulse-opacity-from: 0.7; --kf-pulse-opacity-to: 1; \/\/ opacity pulse\n *\/\n\n@keyframes kf-pulse {\n from {\n scale: var(--kf-pulse-scale-from, 1);\n opacity: var(--kf-pulse-opacity-from, 1);\n }\n to {\n scale: var(--kf-pulse-scale-to, 1);\n opacity: var(--kf-pulse-opacity-to, 1);\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>This creates a flexible pulse that can animate multiple properties:<\/p>\n<pre><code class=\"language-css\">.call-to-action { \n animation: kf-pulse 0.6s infinite alternate;\n --kf-pulse-opacity-from: 0.5; \/* opacity pulse *\/\n}\n\n.notification-dot {\n animation: kf-pulse 0.6s ease-in-out infinite alternate;\n --kf-pulse-scale-from: 0.9; \n --kf-pulse-scale-to: 1.1; \/* scale pulse *\/\n}\n\n.text-highlight {\n animation: kf-pulse 1.5s ease-out infinite;\n --kf-pulse-scale-from: 0.8;\n --kf-pulse-opacity-from: 0.2;\n \/* scale and opacity pulse *\/\n}\n<\/code><\/pre>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"xbVXpRo\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 6 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/xbVXpRo) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/xbVXpRo\">Keyframes Tokens – Demo 6 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>This single <code>kf-pulse<\/code> keyframe can handle everything from subtle attention grabs to dramatic highlights, all while being easy to customize.<\/p>\n<h2 id=\"advanced-easing\">Advanced Easing<\/h2>\n<p>One of the great things about using keyframes tokens is how easy it is to expand our animation library and provide effects that most developers would not bother to write from scratch, like <em>elastic<\/em> or <em>bounce<\/em>.<\/p>\n<p>Here is an example of a simple \u201cbounce\u201d keyframes token that uses a <code>--kf-bounce-from<\/code> custom property to control the jump height.<\/p>\n<pre><code class=\"language-css\">\/*\n * Bounce - bouncing entrance animation\n * Use --kf-bounce-from to control jump height\n * Default: jumps from 100vh (off screen)\n * Usage:\n * animation: kf-bounce 3s ease-in;\n * --kf-bounce-from: 200px; \/\/ jump from 200px height\n *\/\n\n@keyframes kf-bounce {\n 0% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -1);\n }\n\n 34% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -0.4);\n }\n\n 55% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -0.2);\n }\n\n 72% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -0.1);\n }\n\n 85% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -0.05);\n }\n\n 94% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -0.025);\n }\n\n 99% {\n translate: 0 calc(var(--kf-bounce-from, 100vh) * -0.0125);\n }\n\n 22%, 45%, 64%, 79%, 90%, 97%, 100% {\n translate: 0 0;\n animation-timing-function: ease-out;\n }\n}\n<\/code><\/pre>\n<p>Animations like \u201celastic\u201d are a bit trickier because of the calculations inside the keyframes. We need to define <code>--kf-elastic-from-X<\/code> and <code>--kf-elastic-from-Y<\/code> separately (both are optional), and together they let us create an elastic entrance from any point on the screen.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">\/*\n * Elastic In - elastic entrance animation\n * Use --kf-elastic-from-X and --kf-elastic-from-Y to control start position\n * Default: enters from top center (0, -100vh)\n * Usage:\n * animation: kf-elastic-in 2s ease-in-out both;\n * --kf-elastic-from-X: -50px;\n * --kf-elastic-from-Y: -200px; \/\/ enter from (-50px, -200px)\n *\/\n\n@keyframes kf-elastic-in {\n 0% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * 1) calc(var(--kf-elastic-from-Y, 0px) * 1);\n }\n\n 16% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * -0.3227) calc(var(--kf-elastic-from-Y, 0px) * -0.3227);\n }\n\n 28% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * 0.1312) calc(var(--kf-elastic-from-Y, 0px) * 0.1312);\n }\n\n 44% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * -0.0463) calc(var(--kf-elastic-from-Y, 0px) * -0.0463);\n }\n\n 59% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * 0.0164) calc(var(--kf-elastic-from-Y, 0px) * 0.0164);\n }\n\n 73% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * -0.0058) calc(var(--kf-elastic-from-Y, 0px) * -0.0058);\n }\n\n 88% {\n translate: calc(var(--kf-elastic-from-X, -50vw) * 0.0020) calc(var(--kf-elastic-from-Y, 0px) * 0.0020);\n }\n\n 100% {\n translate: 0 0;\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>This approach makes it easy to reuse and customize advanced keyframes across our project, just by changing a single custom property.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">.bounce-and-zoom {\n animation:\n kf-bounce 3s ease-in,\n kf-zoom 3s linear;\n --kf-zoom-from: 0;\n}\n\n.bounce-and-slide {\n animation-composition: add; \/* Both animations use `translate` *\/\n animation:\n kf-bounce 3s ease-in,\n kf-slide-in 3s ease-out;\n --kf-slide-from: -200px;\n}\n\n.elastic-in {\n animation: kf-elastic-in 2s ease-in-out both;\n}\n<\/code><\/pre>\n<\/div>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"QwNqadQ\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 7 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/QwNqadQ) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/QwNqadQ\">Keyframes Tokens – Demo 7 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>Up to this point, we\u2019ve seen how we can consolidate keyframes in a smart and efficient way. Of course, you might want to tweak things to better fit your project\u2019s needs, but we\u2019ve covered examples of several common animations and everyday use cases. And with these keyframes tokens in place, we now have powerful building blocks for creating consistent, maintainable animations across the entire project. No more duplicated keyframes, no more global scope conflicts. Just a clean, convenient way to handle all our animation needs.<\/p>\n<p>But the real question is: <strong>How do we compose these building blocks together?<\/strong><\/p>\n<h2 id=\"putting-it-all-together\">Putting It All Together<\/h2>\n<p>We\u2019ve seen that combining basic keyframes tokens is simple. We don\u2019t need anything special but to define the first animation, define the second one, set the variables as needed, and that\u2019s it.<\/p>\n<pre><code class=\"language-css\">\/* Fade in + slide in *\/\n.toast {\n animation:\n kf-fade-in 0.4s,\n kf-slide-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);\n --kf-slide-from: 0 40px;\n}\n\n\/* Zoom in + fade in *\/\n.modal {\n animation:\n kf-fade-in 0.3s,\n kf-zoom 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n --kf-zoom-from: 0.7;\n --kf-zoom-to: 1;\n}\n\n\/* Slide in + pulse *\/\n.notification {\n animation:\n kf-slide-in 0.5s,\n kf-pulse 1.2s ease-in-out infinite alternate;\n --kf-slide-from: -100px 0;\n --kf-pulse-scale-from: 0.95;\n --kf-pulse-scale-to: 1.05;\n}\n<\/code><\/pre>\n<p>These combinations work beautifully because each animation targets a different property: <code>opacity<\/code>, <code>transform<\/code> (<code>translate<\/code>\/<code>scale<\/code>), etc. But sometimes there are conflicts, and we need to know why and how to deal with them.<\/p>\n<p>When two animations try to animate the same property — for example, both animating <code>scale<\/code> or both animating <code>opacity<\/code> — the result will not be what you expect. By default, only one of the animations is actually applied to that property, which is the last one in the <code>animation<\/code> list. This is a limitation of how CSS handles multiple animations on the same property.<\/p>\n<p>For example, this will not work as intended because only the <code>kf-pulse<\/code> animation will apply.<\/p>\n<pre><code class=\"language-css\">.bad-combo {\n animation:\n kf-zoom 0.5s forwards,\n kf-pulse 1.2s infinite alternate;\n --kf-zoom-from: 0.5;\n --kf-zoom-to: 1.2;\n --kf-pulse-scale-from: 0.8;\n --kf-pulse-scale-to: 1.1;\n}\n<\/code><\/pre>\n<h2 id=\"animation-addition\">Animation Addition<\/h2>\n<p>The simplest and most direct way to handle multiple animations that affect the same property is to use the <code>animation-composition<\/code> property. In the last example above, the <code>kf-pulse<\/code> animation replaces the <code>kf-zoom<\/code> animation, so we will not see the initial zoom and will not get the expected <code>scale<\/code> <code>to<\/code> of <code>1.2<\/code>.<\/p>\n<p>By setting the <code>animation-composition<\/code> to <code>add<\/code>, we tell the browser to <em>combine<\/em> both animations. This gives us the result we want.<\/p>\n<pre><code class=\"language-css\">.component-two {\n animation-composition: add;\n}\n<\/code><\/pre>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"YPqrYZw\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 8 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/YPqrYZw) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/YPqrYZw\">Keyframes Tokens – Demo 8 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>This approach works well for most cases where we want to combine effects on the same property. It is also useful when we need to combine animations with static property values.<\/p>\n<p>For example, if we have an element that uses the <code>translate<\/code> property to position it exactly where we want, and then we want to animate it in with the <code>kf-slide-in<\/code> keyframes, we get a nasty visible jump without <code>animation-composition<\/code>.<\/p>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"myPBpWr\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 9 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/myPBpWr) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/myPBpWr\">Keyframes Tokens – Demo 9 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>With <code>animation-composition<\/code> set to <code>add<\/code>, the animation is smoothly combined with the existing transform, so the element stays in place and animates as expected.<\/p>\n<h2 id=\"animation-stagger\">Animation Stagger<\/h2>\n<p>Another way of handling multiple animations is to \u201cstagger\u201d them — that is, start the second animation slightly after the first one finishes. It is not a solution that works for every case, but it is useful when we have an entrance animation followed by a continuous animation.<\/p>\n<pre><code class=\"language-css\">\/* fade in + opacity pulse *\/\n.notification {\n animation:\n kf-fade-in 2s ease-out,\n kf-pulse 0.5s 2s ease-in-out infinite alternate;\n --kf-pulse-opacity-to: 0.5;\n}\n<\/code><\/pre>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"bNpoaqo\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 10 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/bNpoaqo) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/bNpoaqo\">Keyframes Tokens – Demo 10 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<h2 id=\"order-matters\">Order Matters<\/h2>\n<p>A large part of the animations we work with use the <code>transform<\/code> property. In most cases, this is simply more convenient. It also has a performance advantage as transform animations can be GPU-accelerated. But if we use transforms, we need to accept that the order in which we perform our transformations matters. <em>A lot<\/em>.<\/p>\n<p>In our keyframes so far, we\u2019ve used <em>individual transforms<\/em>. According to the specs, these are always applied in a fixed order: first, the element gets <code>translate<\/code>, then <code>rotate<\/code>, then <code>scale<\/code>. This makes sense and is what most of us expect.<\/p>\n<p>However, if we use the <code>transform<\/code> property, the order in which the functions are written is the order in which they are applied. In this case, if we move something 100 pixels on the X-axis and then rotate it by 45 degrees, it is <em>not<\/em> the same as first rotating it by 45 degrees and then moving it 100 pixels.<\/p>\n<pre><code class=\"language-css\">\/* Pink square: First translate, then rotate *\/ \n.example-one {\n transform: translateX(100px) rotate(45deg);\n}\n\n\/* Green square: First rotate, then translate *\/\n.example-two { \n transform: rotate(45deg) translateX(100px);\n}\n<\/code><\/pre>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"zxqEpZb\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 11 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/zxqEpZb) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/zxqEpZb\">Keyframes Tokens – Demo 11 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>But according to the <code>transform<\/code> order, all individual transforms — everything we\u2019ve used for the keyframes tokens — happens before the transform functions. That means anything you set in the <code>transform<\/code> property will happen <em>after<\/em> the animations. But if you set, for example, <code>translate<\/code> together with the <code>kf-spin<\/code> keyframes, the <code>translate<\/code> will happen <em>before<\/em> the animation. Confused yet?!<\/p>\n<p>This leads to situations where static values can cause different results for the same animation, like in the following case:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">\/* Common animation for both spinners *\/ \n.spinner {\n animation: kf-spin 1s linear infinite;\n}\n\n\/* Pink spinner: translate before rotate (individual transform) *\/\n.spinner-pink {\n translate: 100% 50%;\n}\n\n\/* Green spinner: rotate then translate (function order) *\/\n.spinner-green {\n transform: translate(100%, 50%);\n}\n<\/code><\/pre>\n<\/div>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"NPNaXjw\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Keyframes Tokens – Demo 12 [forked]](https:\/\/codepen.io\/smashingmag\/pen\/NPNaXjw) by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/NPNaXjw\">Keyframes Tokens – Demo 12 [forked]<\/a> by <a href=\"https:\/\/codepen.io\/amit_sheen\">Amit Sheen<\/a>.<\/figcaption><\/figure>\n<p>You can see that the first spinner (pink) gets a <code>translate<\/code> that happens before the <code>rotate<\/code> of <code>kf-spin<\/code>, so it first moves to its place and then spins. The second spinner (green) gets a <code>translate()<\/code> function that happens after the individual transform, so the element first spins, then moves relative to its current angle, and we get that wide orbit effect.<\/p>\n<p><strong>No, this is not a bug<\/strong>. It is just one of those things we need to know about CSS and keep in mind when working with multiple animations or multiple transforms. If needed, you can also create an additional set of <code>kf-spin-alt<\/code> keyframes that rotate elements using the <code>rotate()<\/code> function.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"reduced-motion\">Reduced Motion<\/h2>\n<p>And while we\u2019re talking about alternative keyframes, we cannot ignore the \u201cno animation\u201d option. One of the biggest advantages of using keyframes tokens is that <strong>accessibility<\/strong> can be baked in, and it is actually quite easy to do. By designing our keyframes with accessibility in mind, we can ensure that <a href=\"https:\/\/www.smashingmagazine.com\/2021\/10\/respecting-users-motion-preferences\/\">users who prefer reduced motion get a smoother, less distracting experience<\/a>, without extra work or code duplication.<\/p>\n<p>The exact meaning of \u201cReduced Motion\u201d can change a bit from one animation to another, and from project to project, but here are a few important points to keep in mind:<\/p>\n<h3 id=\"muting-keyframes\">Muting Keyframes<\/h3>\n<p>While some animations can be softened or slowed down, there are others that should disappear completely when reduced motion is requested. Pulse animations are a good example. To make sure these animations do not run in reduced motion mode, we can simply wrap them in the appropriate media query.<\/p>\n<pre><code class=\"language-css\">\n@media (prefers-reduced-motion: no-preference) {\n @keyfrmaes kf-pulse {\n from {\n scale: var(--kf-pulse-scale-from, 1);\n opacity: var(--kf-pulse-opacity-from, 1);\n }\n to {\n scale: var(--kf-pulse-scale-to, 1);\n opacity: var(--kf-pulse-opacity-to, 1);\n }\n }\n}\n<\/code><\/pre>\n<p>This ensures that users who have set <code>prefers-reduced-motion<\/code> to <code>reduce<\/code> will not see the animation and will get an experience that matches their preference.<\/p>\n<h3 id=\"instant-in\">Instant In<\/h3>\n<p>There are some keyframes we cannot simply remove, such as entrance animations. The value must change, must animate; otherwise, the element won’t have the correct values. But in reduced motion, this transition from the initial value should be instant.<\/p>\n<p>To achieve this, we\u2019ll define an extra set of keyframes where the value jumps immediately to the end state. These become our default keyframes. Then, we\u2019ll add the regular keyframes inside a media query for <code>prefers-reduced-motion<\/code> set to <code>no-preference<\/code>, just like in the previous example.<\/p>\n<pre><code class=\"language-css\">\/* pop in instantly for reduced motion *\/\n@keyframes kf-zoom {\n from, to {\n scale: var(--kf-zoom-to, 1);\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n \/* Original zoom keyframes *\/\n @keyframes kf-zoom {\n from {\n scale: var(--kf-zoom-from, 0.8);\n }\n to {\n scale: var(--kf-zoom-to, 1);\n }\n }\n}\n<\/code><\/pre>\n<p>This way, users who prefer reduced motion will see the element appear instantly in its final state, while everyone else gets the animated transition.<\/p>\n<h3 id=\"the-soft-approach\">The Soft Approach<\/h3>\n<p>There are cases where we do want to keep some movement, but much softer and calmer than the original animation. For example, we can replace a bounce entrance with a gentle fade-in.<\/p>\n<pre><code class=\"language-css\">\n@keyframes kf-bounce {\n \/* Soft fade-in for reduced motion *\/\n}\n\n@media (prefers-reduced-motion: no-preference) {\n @keyframes kf-bounce {\n \/* Original bounce keyframes *\/\n }\n}\n<\/code><\/pre>\n<p>Now, users with reduced motion enabled still get a sense of appearance, but without the intense movement of a bounce or elastic animation.<\/p>\n<p>With the building blocks in place, the next question is how to make them part of the actual workflow. Writing flexible keyframes is one thing, but making them reliable across a large project requires a few strategies that I had to learn the hard way.<\/p>\n<h2 id=\"implementation-strategies-best-practices\">Implementation Strategies & Best Practices<\/h2>\n<p>Once we have a solid library of keyframes tokens, the real challenge is how to bring them into everyday work.<\/p>\n<ul>\n<li>The temptation is to drop all keyframes in at once and declare the problem solved, but in practice I have found that <strong>the best results come from gradual adoption<\/strong>. Start with the most common animations, such as fade or slide. These are easy wins that show immediate value without requiring big rewrites.<\/li>\n<li><strong>Naming is another point that deserves attention.<\/strong> A consistent prefix or namespace makes it obvious which animations are tokens and which are local one-offs. It also prevents accidental collisions and helps new team members recognize the shared system at a glance.<\/li>\n<li><strong>Documentation is just as important as the code itself.<\/strong> Even a short comment above each keyframes token can save hours of guessing later. A developer should be able to open the tokens file, scan for the effect they need, and copy the usage pattern straight into their component.<\/li>\n<li><strong>Flexibility is what makes this approach worth the effort.<\/strong> By exposing sensible custom properties, we give teams room to adapt the animation without breaking the system. At the same time, try not to overcomplicate. Provide the knobs that matter and keep the rest opinionated.<\/li>\n<li>Finally, <strong>remember accessibility<\/strong>. Not every animation needs a reduced motion alternative, but many do. Baking in these adjustments early means we never have to retrofit them later, and it shows a level of care that our users will notice even if they never mention it.<\/li>\n<\/ul>\n<p>In my experience, treating keyframes tokens as part of our design tokens workflow is what makes them stick. Once they are in place, they stop feeling like special effects and become part of the design language, a natural extension of how the product moves and responds.<\/p>\n<h2 id=\"wrapping-up\">Wrapping Up<\/h2>\n<p>Animations can be one of the most joyful parts of building interfaces, but without structure, they can also become one of the biggest sources of frustration. By treating keyframes as tokens, you take something that is usually messy and hard to manage and turn it into a clear, predictable system.<\/p>\n<p>The real value is not just in saving a few lines of code. It is in the <strong>confidence<\/strong> that when you use a fade, slide, zoom, or spin, you know exactly how it will behave across the project. It is in the <strong>flexibility<\/strong> that comes from custom properties without the chaos of endless variations. And it is in the <strong>accessibility<\/strong> built into the foundation rather than added as an afterthought.<\/p>\n<p>I have seen these ideas work in different teams and different codebases, and the pattern is always the same.<\/p>\n<blockquote class=\"pull-quote\">\n<p>\n <a class=\"pull-quote__link\" aria-label=\"Share on Twitter\" href=\"https:\/\/twitter.com\/share?text=%0aOnce%20the%20tokens%20are%20in%20place,%20keyframes%20stop%20being%20a%20scattered%20collection%20of%20tricks%20and%20become%20part%20of%20the%20design%20language.%20They%20make%20the%20product%20feel%20more%20intentional,%20more%20consistent,%20and%20more%20alive.%0a&url=https:\/\/smashingmagazine.com%2f2025%2f11%2fkeyframes-tokens-standardizing-animation-across-projects%2f\"><\/p>\n<p>Once the tokens are in place, keyframes stop being a scattered collection of tricks and become part of the design language. They make the product feel more intentional, more consistent, and more alive.<\/p>\n<p> <\/a>\n <\/p>\n<div class=\"pull-quote__quotation\">\n<div class=\"pull-quote__bg\">\n <span class=\"pull-quote__symbol\">\u201c<\/span><\/div>\n<\/p><\/div>\n<\/blockquote>\n<p>If you take one thing from this article, let it be this: <strong>animations deserve the same care and structure we already give to colors, typography, and spacing<\/strong>. A small investment in keyframes tokens pays off every time your interface moves.<\/p>\n<div class=\"signature\">\n <img src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" class=\"lazyload\" data-src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\"><br \/>\n <span>(gg, yk)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Keyframes Tokens: Standardizing Animation Across Projects Keyframes Tokens: Standardizing Animation Across Projects Amit Sheen 2025-11-21T08:00:00+00:00 2025-11-27T20:33:09+00:00 Picture this: you join a new project, dive into the codebase, and within the first few hours, you discover something frustratingly familiar. Scattered throughout the stylesheets, you find multiple @keyframes definitions for the same basic animations. Three different fade-in…<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[11],"tags":[],"_links":{"self":[{"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/posts\/1113"}],"collection":[{"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/comments?post=1113"}],"version-history":[{"count":1,"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/posts\/1113\/revisions"}],"predecessor-version":[{"id":1114,"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/posts\/1113\/revisions\/1114"}],"wp:attachment":[{"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/media?parent=1113"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/categories?post=1113"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computercoursesonline.com\/index.php\/wp-json\/wp\/v2\/tags?post=1113"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}