<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CSS on Smashing Magazine — For Web Designers And Developers</title><link>https://www.smashingmagazine.com/category/css/index.xml</link><description>Recent content in CSS on Smashing Magazine — For Web Designers And Developers</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 14 Oct 2025 04:02:41 +0000</lastBuildDate><item><author>Andy Clarke</author><title>Smashing Animations Part 5: Building Adaptive SVGs With `&lt;symbol>`, `&lt;use>`, And CSS Media Queries</title><link>https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/</link><pubDate>Mon, 06 Oct 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/</guid><description>SVGs, they scale, yes, but how else can you make them adapt even better to several screen sizes? Web design pioneer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> explains how he builds what he calls “adaptive SVGs” using &lt;code>&amp;lt;symbol&amp;gt;&lt;/code>, &lt;code>&amp;lt;use&amp;gt;&lt;/code>, and CSS Media Queries.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/10/smashing-animations-part-5-building-adaptive-svgs/" />
              <title>Smashing Animations Part 5: Building Adaptive SVGs With `&lt;symbol&gt;`, `&lt;use&gt;`, And CSS Media Queries</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 5: Building Adaptive SVGs With `&lt;symbol&gt;`, `&lt;use&gt;`, And CSS Media Queries</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-10-06T13:00:00&#43;00:00" class="op-published">2025-10-06T13:00:00+00:00</time>
                  <time datetime="2025-10-06T13:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>I’ve written quite a lot recently about how I <a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">prepare and optimise</a> SVG code to use as static graphics or in <a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">animations</a>. I love working with SVG, but there’s always been something about them that bugs me.</p>

<p>To illustrate how I build adaptive SVGs, I’ve selected an episode of <em>The Quick Draw McGraw Show</em> called “<a href="https://yowpyowp.blogspot.com/2012/06/quick-draw-mcgraw-bow-wow-bandit.html">Bow Wow Bandit</a>,” first broadcast in 1959.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png"
			
			sizes="100vw"
			alt="Bow Wow Bandit illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Quick Draw McGraw Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/1-quick-draw-mcgraw-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In it, Quick Draw McGraw enlists his bloodhound Snuffles to rescue his sidekick Baba Looey. Like most Hanna-Barbera title cards of the period, the artwork was made by Lawrence (Art) Goble.</p>

<div class="refs">
  <ul><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</a></li><li><a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">Smashing Animations Part 4: Optimising SVGs</a></li></ul>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png"
			
			sizes="100vw"
			alt="Quick Draw McGraw character pulling back on a dog leash attached to his bloodhound, Snuffles."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Andy Clarke’s Bow Wow Bandit Toon Title recreation (16:9). (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/2-andy-clarke-bow-wow-bandit-toon-title-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Let’s say I’ve designed an SVG scene like that one that’s based on Bow Wow Bandit, which has a 16:9 aspect ratio with a <code>viewBox</code> size of 1920×1080. This SVG scales up and down (the clue’s in the name), so it looks sharp when it’s gigantic and when it’s minute.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png"
			
			sizes="100vw"
			alt="16:9 aspect ration vs. 3:4."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Left: 16:9 aspect ratio loses its impact. Right: 3:4 format suits the screen size better. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/3-svgs-aspect-ratio.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But on small screens, the 16:9 aspect ratio (<a href="https://stuffandnonsense.co.uk/toon-titles/quick-draw-3a.html">live demo</a>) might not be the best format, and the image loses its impact. Sometimes, a portrait orientation, like 3:4, would suit the screen size better.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="729"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png"
			
			sizes="100vw"
			alt="Andy Clarke’s Bow Wow Bandit Toon Title recreation (3:4)."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Andy Clarke’s Bow Wow Bandit Toon Title recreation (3:4). (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/4-bow-wow-bandit-toon-title-recreation-portrait.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But, herein lies the problem, as it’s not easy to reposition internal elements for different screen sizes using just <code>viewBox</code>. That’s because in SVG, internal element positions are locked to the coordinate system from the original <code>viewBox</code>, so you can’t easily change their layout between, say, desktop and mobile. This is a problem because animations and interactivity often rely on element positions, which break when the <code>viewBox</code> changes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png"
			
			sizes="100vw"
			alt="Left: 16:9 for larger screens. Right: 3:4 for smaller screens."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Left: 16:9 for larger screens. Right: 3:4 for smaller screens. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/5-svg-smaller-larger-screens.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>My challenge was to serve a 1080×1440 version of Bow Wow Bandit to smaller screens and a different one to larger ones. I wanted the position and size of internal elements &mdash; like Quick Draw McGraw and his dawg Snuffles &mdash; to change to best fit these two layouts. To solve this, I experimented with several alternatives.</p>

<p><strong>Note:</strong> Why are we not just using the <code>&lt;picture&gt;</code> with external SVGs? The <a href="https://www.smashingmagazine.com/2014/05/responsive-images-done-right-guide-picture-srcset/"><code>&lt;picture&gt;</code> element</a> is brilliant for responsive images, but it only works with raster formats (like JPEG or WebP) and external SVG files treated as images. That means that you can’t animate or style internal elements using CSS.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="showing-and-hiding-svg">Showing And Hiding SVG</h2>

<p>The most obvious choice was to include two different SVGs in my markup, one for small screens, the other for larger ones, then show or hide them using <a href="https://www.smashingmagazine.com/2018/02/media-queries-responsive-design-2018/">CSS and Media Queries</a>:</p>

<pre><code class="language-svg">&lt;svg id="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;svg id="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;!--... --&gt;
&lt;/svg&gt;


#svg-small { display: block; }
#svg-large { display: none; }

@media (min-width: 64rem) {
  #svg-small { display: none; }
  #svg-mobile { display: block; }
}
</code></pre>

<p>But using this method, both SVG versions are loaded, which, when the graphics are complex, means downloading lots and lots and lots of unnecessary code.</p>

<h2 id="replacing-svgs-using-javascript">Replacing SVGs Using JavaScript</h2>

<p>I thought about using JavaScript to swap in the larger SVG at a specified breakpoint:</p>

<pre><code class="language-javascript">if (window.matchMedia('(min-width: 64rem)').matches) {
  svgContainer.innerHTML = desktopSVG; 
} else {
  svgContainer.innerHTML = mobileSVG;
}
</code></pre>

<p>Leaving aside the fact that JavaScript would now be critical to how the design is displayed, both SVGs would usually be loaded anyway, which adds DOM complexity and unnecessary weight. Plus, maintenance becomes a problem as there are now two versions of the artwork to maintain, doubling the time it would take to update something as small as the shape of Quick Draw’s tail.</p>

<h2 id="the-solution-one-svg-symbol-library-and-multiple-uses">The Solution: One SVG Symbol Library And Multiple Uses</h2>

<p>Remember, my goal is to:</p>

<ul>
<li>Serve one version of Bow Wow Bandit to smaller screens,</li>
<li>Serve a different version to larger screens,</li>
<li>Define my artwork just once (DRY), and</li>
<li>Be able to resize and reposition elements.</li>
</ul>

<p>I don’t read about it enough, but the <code>&lt;symbol&gt;</code> element lets you define reusable SVG elements that can be hidden and reused to improve maintainability and reduce code bloat. They’re like components for SVG: <a href="https://css-tricks.com/svg-symbol-good-choice-icons/">create once and use wherever you need them</a>:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" style="display: none;"&gt;
  &lt;symbol id="quick-draw-body" viewBox="0 0 620 700"&gt;
    &lt;g class="quick-draw-body"&gt;[…]&lt;/g&gt;
  &lt;/symbol&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;use href="#quick-draw-body" /&gt;
</code></pre>

<p>A <code>&lt;symbol&gt;</code> is like storing a character in a library. I can reference it as many times as I need, to keep my code consistent and lightweight. Using <code>&lt;use&gt;</code> elements, I can insert the same symbol multiple times, at different positions or sizes, and even in different SVGs.</p>

<p>Each <code>&lt;symbol&gt;</code> must have its own <code>viewBox</code>, which defines its internal coordinate system. That means paying special attention to how SVG elements are exported from apps like Sketch.</p>

<div class="partners__lead-place"></div>

<h2 id="exporting-for-individual-viewboxes">Exporting For Individual Viewboxes</h2>

<p>I wrote before about <a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">how I export elements</a> in layers to make working with them easier. That process is a little different when creating symbols.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png"
			
			sizes="100vw"
			alt=""
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      My usual process of exporting elements from Sketch. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/6-exporting-elements-from-sketch.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Ordinarily, I would export all my elements using the same <code>viewBox</code>size. But when I’m creating a <code>symbol</code>, I need it to have its own specific <code>viewBox</code>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png"
			
			sizes="100vw"
			alt="Exporting elements from Sketch as individual SVG files."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Exporting elements from Sketch as individual SVG files. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/7-exporting-elements-sketch-individual-svgs-files.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>So I export each element as an individually sized SVG, which gives me the dimensions I need to convert its content into a <code>symbol</code>. Let’s take the SVG of Quick Draw McGraw’s hat, which has a <code>viewBox</code> size of 294×182:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 294 182"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>

<p>I swap the SVG tags for <code>&lt;symbol&gt;</code> and add its artwork to my SVG library:</p>

<pre><code class="language-svg">&lt;svg xmlns="http://www.w3.org/2000/svg" style="display: none;"&gt;
  &lt;symbol id="quick-draw-hat" viewBox="0 0 294 182"&gt;
    &lt;g class="quick-draw-hat"&gt;[…]&lt;/g&gt;
  &lt;/symbol&gt;
&lt;/svg&gt;
</code></pre>

<p>Then, I repeat the process for all the remaining elements in my artwork. Now, if I ever need to update any of my symbols, the changes will be automatically applied to every instance it’s used.</p>

<h2 id="using-a-symbol-in-multiple-svgs">Using A <code>&lt;symbol&gt;</code> In Multiple SVGs</h2>

<p>I wanted my elements to appear in both versions of Bow Wow Bandit, one arrangement for smaller screens and an alternative arrangement for larger ones. So, I create both SVGs:</p>

<pre><code class="language-svg">&lt;svg class="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;

&lt;svg class="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>

<p>…and insert links to my symbols in both:</p>

<pre><code class="language-svg">&lt;svg class="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;use href="#quick-draw-hat" /&gt;
&lt;/svg&gt;

&lt;svg class="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;use href="#quick-draw-hat" /&gt;
&lt;/svg&gt;
</code></pre>

<h2 id="positioning-symbols">Positioning Symbols</h2>

<p>Once I’ve placed symbols into my layout using <code>&lt;use&gt;</code>, my next step is to position them, which is especially important if I want alternative layouts for different screen sizes. Symbols behave like <code>&lt;g&gt;</code> groups, so I can scale and move them using attributes like <code>width</code>, <code>height</code>, and <code>transform</code>:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;svg class="svg-small" viewBox="0 0 1080 1440"&gt;
  &lt;use href="#quick-draw-hat" width="294" height="182" transform="translate(-30,610)"/&gt;
&lt;/svg&gt;

&lt;svg class="svg-large" viewBox="0 0 1920 1080"&gt;
  &lt;use href="#quick-draw-hat" width="294" height="182" transform="translate(350,270)"/&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>I can place each <code>&lt;use&gt;</code> element independently using <code>transform</code>. This is powerful because rather than repositioning elements inside my SVGs, I move the <code>&lt;use&gt;</code> references. My internal layout stays clean, and the file size remains small because I’m not duplicating artwork. A browser only loads it once, which reduces bandwidth and speeds up page rendering. And because I’m always referencing the same <code>symbol</code>, their appearance stays consistent, whatever the screen size.</p>

<h2 id="animating-use-elements">Animating <code>&lt;use&gt;</code> Elements</h2>

<p>Here’s where things got tricky. I wanted to animate parts of my characters &mdash; like Quick Draw’s hat tilting and his legs kicking. But when I added CSS animations targeting internal elements inside a <code>&lt;symbol&gt;</code>, nothing happened.</p>

<p><strong>Tip:</strong> You can animate the <code>&lt;use&gt;</code> element itself, but not elements inside the <code>&lt;symbol&gt;</code>. If you want individual parts to move, make them their own symbols and animate each <code>&lt;use&gt;</code>.</p>

<p>Turns out, you can’t style or animate a <code>&lt;symbol&gt;</code>, because <code>&lt;use&gt;</code> creates shadow DOM clones that aren’t easily targetable. So, I had to get sneaky. Inside each <code>&lt;symbol&gt;</code> in my library SVG, I added a <code>&lt;g&gt;</code> element around the part I wanted to animate:</p>

<pre><code class="language-svg">&lt;symbol id="quick-draw-hat" viewBox="0 0 294 182"&gt;
  &lt;g class="quick-draw-hat"&gt;
    &lt;!-- ... --&gt;
  &lt;/g&gt;
&lt;/symbol&gt;
</code></pre>

<p>…and animated it using an attribute substring selector, targeting the <code>href</code> attribute of the <code>use</code> element:</p>

<pre><code class="language-css">use[href="#quick-draw-hat"] {
  animation-delay: 0.5s;
  animation-direction: alternate;
  animation-duration: 1s;
  animation-iteration-count: infinite;
  animation-name: hat-rock;
  animation-timing-function: ease-in-out;
  transform-origin: center bottom;
}

@keyframes hat-rock {
from { transform: rotate(-2deg); }
to   { transform: rotate(2deg); } }
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="media-queries-for-display-control">Media Queries For Display Control</h2>

<p>Once I’ve created my two visible SVGs &mdash; one for small screens and one for larger ones &mdash; the final step is deciding which version to show at which screen size. I use CSS Media Queries to hide one SVG and show the other. I start by showing the small-screen SVG by default:</p>

<pre><code class="language-css">.svg-small { display: block; }
.svg-large { display: none; }
</code></pre>

<p>Then I use a <code>min-width</code> media query to switch to the large-screen SVG at <code>64rem</code> and above:</p>

<pre><code class="language-css">@media (min-width: 64rem) {
  .svg-small { display: none; }
  .svg-large { display: block; }
}
</code></pre>

<p>This ensures there’s only ever one SVG visible at a time, keeping my layout simple and the DOM free from unnecessary clutter. And because both visible SVGs reference the same hidden <code>&lt;symbol&gt;</code> library, the browser only downloads the artwork once, regardless of how many <code>&lt;use&gt;</code> elements appear across the two layouts.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png"
			
			sizes="100vw"
			alt="The final adaptive SVG."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      View the final adaptive SVG on my <a href='https://stuffandnonsense.co.uk/toon-titles/quick-draw-3.html'>Toon Titles website</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-5-building-adaptive-svgs/8-final-adaptive-svg.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>By combining <code>&lt;symbol&gt;</code>, <code>&lt;use&gt;</code>, CSS Media Queries, and specific transforms, I can build <strong>adaptive SVGs</strong> that reposition their elements without duplicating content, loading extra assets, or relying on JavaScript. I need to define each graphic only once in a hidden symbol library. Then I can reuse those graphics, as needed, inside several visible SVGs. With CSS doing the layout switching, the <strong>result is fast and flexible</strong>.</p>

<p>It’s a reminder that some of the most powerful techniques on the web don’t need big frameworks or complex tooling &mdash; just a bit of SVG know-how and a clever use of the basics.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Ambient Animations In Web Design: Principles And Implementation (Part 1)</title><link>https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/</link><pubDate>Mon, 22 Sep 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/</guid><description>Creating motion can be tricky. Too much and it’s distracting. Too little and a design feels flat. Ambient animations are the middle ground &amp;mdash; subtle, slow-moving details that add atmosphere without stealing the show. In this article, web design pioneer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> introduces the concept of ambient animations and explains how to implement them.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/09/ambient-animations-web-design-principles-implementation/" />
              <title>Ambient Animations In Web Design: Principles And Implementation (Part 1)</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Ambient Animations In Web Design: Principles And Implementation (Part 1)</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-09-22T13:00:00&#43;00:00" class="op-published">2025-09-22T13:00:00+00:00</time>
                  <time datetime="2025-09-22T13:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Unlike <em>timeline-based</em> animations, which tell stories across a sequence of events, or <em>interaction</em> animations that are triggered when someone touches something, <strong>ambient animations</strong> are the kind of passive movements you might not notice at first. But, they make a design look alive in subtle ways.</p>

<p>In an ambient animation, elements might subtly transition between colours, move slowly, or gradually shift position. Elements can appear and disappear, change size, or they could rotate slowly.</p>

<p>Ambient animations aren’t intrusive; they don’t demand attention, aren’t distracting, and don’t interfere with what someone’s trying to achieve when they use a product or website. They can be playful, too, making someone smile when they catch sight of them. That way, ambient animations <strong>add depth to a brand’s personality</strong>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="399"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png"
			
			sizes="100vw"
			alt="A three-page spread of a Quick Draw McGraw comic book including the animated cover and first two pages."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Hanna-Barbera’s Quick Draw McGraw © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/1-quick-draw-mcgraw-comic-book.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p class="c-pre-sidenote--left">To illustrate the concept of ambient animations, I’ve recreated the cover of a <a href="https://en.wikipedia.org/wiki/Quick_Draw_McGraw"><em>Quick Draw McGraw</em></a> <a href="https://dn720005.ca.archive.org/0/items/QuickDrawMcGrawCharlton/Quick%20Draw%20McGraw%20%233%20%28Charlton%201971%29.pdf">comic book</a> (PDF) as a CSS/SVG animation. The comic was published by Charlton Comics in 1971, and, being printed, these characters didn’t move, making them ideal candidates to transform into ambient animations.</p>
<p class="c-sidenote c-sidenote--right"><strong>FYI</strong>: Original cover artist <a href="https://www.lambiek.net/artists/d/dirgo_ray.htm">Ray Dirgo</a> was best known for his work drawing Hanna-Barbera characters for Charlton Comics during the 1970s. Ray passed away in 2000 at the age of 92. He outlived Charlton Comics, which went out of business in 1986, and DC Comics acquired its characters.</p>

<p><strong>Tip</strong>: You can view the complete ambient animation <a href="https://codepen.io/malarkey/pen/NPGrWVy">code on CodePen</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png"
			
			sizes="100vw"
			alt="Quick Draw McGraw ambient animations."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Quick Draw McGraw ambient animations. (<a href='https://codepen.io/malarkey/pen/NPGrWVy'>Live Demo</a>) (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/2-quick-draw-mcgraw-ambient-animations.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="choosing-elements-to-animate">Choosing Elements To Animate</h2>

<p>Not everything on a page or in a graphic needs to move, and part of designing an ambient animation is <strong>knowing when to stop</strong>. The trick is to pick elements that lend themselves naturally to subtle movement, rather than forcing motion into places where it doesn’t belong.</p>

<h3 id="natural-motion-cues">Natural Motion Cues</h3>

<p>When I’m deciding what to animate, I look for natural motion cues and think about when something would move naturally in the real world. I ask myself: <em>“Does this thing have weight?”</em>, <em>“Is it flexible?”</em>, and <em>“Would it move in real life?”</em> If the answer’s <em>“yes,”</em> it’ll probably feel right if it moves. There are several motion cues in Ray Dirgo’s cover artwork.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png"
			
			sizes="100vw"
			alt="Vibrantly illustrated pipe adorned with two feathers on the end against a silhouetted toon title card."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Pipe and feathers swing slightly. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/3-pipe-feathers-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For example, the peace pipe Quick Draw’s puffing on has two feathers hanging from it. They swing slightly left and right by three degrees as the pipe moves, just like real feathers would.</p>

<div class="break-out">
<pre><code class="language-css">&#35;quick-draw-pipe {
  animation: quick-draw-pipe-rotate 6s ease-in-out infinite alternate;
}

@keyframes quick-draw-pipe-rotate {
  0% { transform: rotate(3deg); }
  100% { transform: rotate(-3deg); }
}

&#35;quick-draw-feather-1 {
  animation: quick-draw-feather-1-rotate 3s ease-in-out infinite alternate;
}

&#35;quick-draw-feather-2 {
  animation: quick-draw-feather-2-rotate 3s ease-in-out infinite alternate;
}

@keyframes quick-draw-feather-1-rotate {
  0% { transform: rotate(3deg); }
  100% { transform: rotate(-3deg); }
}

@keyframes quick-draw-feather-2-rotate {
  0% { transform: rotate(-3deg); }
  100% { transform: rotate(3deg); }
}
</code></pre>
</div>

<h3 id="atmosphere-not-action">Atmosphere, Not Action</h3>

<p>I often choose elements or decorative details that add to the vibe but don’t fight for attention.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aAmbient%20animations%20aren%e2%80%99t%20about%20signalling%20to%20someone%20where%20they%20should%20look;%20they%e2%80%99re%20about%20creating%20a%20mood.%20%0a&url=https://smashingmagazine.com%2f2025%2f09%2fambient-animations-web-design-principles-implementation%2f">
      
Ambient animations aren’t about signalling to someone where they should look; they’re about creating a mood. 

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>Here, the chief slowly and subtly rises and falls as he puffs on his pipe.</p>

<pre><code class="language-css">&#35;chief {
  animation: chief-rise-fall 3s ease-in-out infinite alternate;
}

@keyframes chief-group-rise-fall {
  0% { transform: translateY(0); }
  100% { transform: translateY(-20px); }
}
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png"
			
			sizes="100vw"
			alt="An illustrated Indian chief seated and puffing on a pipe against a silhouetted toon title card."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The chief rises and falls as he puffs on his pipe. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/4-chief-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For added effect, the feather on his head also moves in time with his rise and fall:</p>

<div class="break-out">
<pre><code class="language-css">&#35;chief-feather-1 {
  animation: chief-feather-1-rotate 3s ease-in-out infinite alternate;
}

&#35;chief-feather-2 {
  animation: chief-feather-2-rotate 3s ease-in-out infinite alternate;
}

@keyframes chief-feather-1-rotate {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(-9deg); }
}

@keyframes chief-feather-2-rotate {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(9deg); }
}
</code></pre>
</div>

<h3 id="playfulness-and-fun">Playfulness And Fun</h3>

<p>One of the things I love most about ambient animations is how they bring fun into a design. They’re an opportunity to <strong>demonstrate personality</strong> through playful details that make people smile when they notice them.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png"
			
			sizes="100vw"
			alt="Closeup of the illustrated chief’s head and face."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The chief’s eyebrows rise and fall, and his eyes cross. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/5-closeup-illustrated-chief-head.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Take a closer look at the chief, and you might spot his eyebrows raising and his eyes crossing as he puffs hard on his pipe. Quick Draw’s eyebrows also bounce at what look like random intervals.</p>

<pre><code class="language-css">&#35;quick-draw-eyebrow {
  animation: quick-draw-eyebrow-raise 5s ease-in-out infinite;
}

@keyframes quick-draw-eyebrow-raise {
  0%, 20%, 60%, 100% { transform: translateY(0); }
  10%, 50%, 80% { transform: translateY(-10px); }
}
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="keep-hierarchy-in-mind">Keep Hierarchy In Mind</h2>

<p>Motion draws the eye, and even subtle movements have a visual weight. So, I reserve the most obvious animations for elements that I need to create the biggest impact.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png"
			
			sizes="100vw"
			alt="Illustrated Quick Draw McGraw holding the feather-adorned pipe with dizzy eyes veering right."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Quick Draw McGraw wobbles under the influence of his pipe. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/6-illustrated-duick-draw-mcgraw.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Smoking his pipe clearly has a big effect on Quick Draw McGraw, so to demonstrate this, I wrapped his elements &mdash; including his pipe and its feathers &mdash; within a new SVG group, and then I made that wobble.</p>

<pre><code class="language-css">&#35;quick-draw-group {
  animation: quick-draw-group-wobble 6s ease-in-out infinite;
}

@keyframes quick-draw-group-wobble {
  0% { transform: rotate(0deg); }
  15% { transform: rotate(2deg); }
  30% { transform: rotate(-2deg); }
  45% { transform: rotate(1deg); }
  60% { transform: rotate(-1deg); }
  75% { transform: rotate(0.5deg); }
  100% { transform: rotate(0deg); }
}
</code></pre>

<p>Then, to emphasise this motion, I mirrored those values to wobble his shadow:</p>

<pre><code class="language-css">&#35;quick-draw-shadow {
  animation: quick-draw-shadow-wobble 6s ease-in-out infinite;
}

@keyframes quick-draw-shadow-wobble {
  0% { transform: rotate(0deg); }
  15% { transform: rotate(-2deg); }
  30% { transform: rotate(2deg); }
  45% { transform: rotate(-1deg); }
  60% { transform: rotate(1deg); }
  75% { transform: rotate(-0.5deg); }
  100% { transform: rotate(0deg); }
}
</code></pre>

<h2 id="apply-restraint">Apply Restraint</h2>

<p>Just because something can be animated doesn’t mean it should be. When creating an ambient animation, I study the image and note the elements where subtle motion might add life. I keep in mind the questions: <em>“What’s the story I’m telling? Where does movement help, and when might it become distracting?”</em></p>

<p>Remember, restraint isn’t just about doing less; it’s about doing the right things less often.</p>

<h2 id="layering-svgs-for-export">Layering SVGs For Export</h2>

<p>In “<a href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/">Smashing Animations Part 4: Optimising SVGs</a>,” I wrote about the process I rely on to <em>“prepare, optimise, and structure SVGs for animation.”</em> When elements are crammed into a single SVG file, they can be a nightmare to navigate. Locating a specific path or group can feel like searching for a needle in a haystack.</p>

<blockquote>That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time &mdash; always in the order they’ll appear in the final file. This lets me build the master SVG gradually by pasting it in each cleaned-up section.</blockquote>

<p>I start by exporting background elements, optimising them, adding class and ID attributes, and pasting their code into my SVG file.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png"
			
			sizes="100vw"
			alt="The toon title card with the chief and Quick Draw characters cut out with their shapes remaining."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Exporting background elements. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/7-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then, I export elements that often stay static or move as groups, like the chief and Quick Draw McGraw.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png"
			
			sizes="100vw"
			alt="Showing Quick Draw pasted to the toon title card’s foreground, minus details including the pipe he is holding and his eyeballs."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Exporting larger groups. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/8-quick-draw-pasted-toon-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Before finally exporting, naming, and adding details, like Quick Draw’s pipe, eyes, and his stoned sparkles.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="484"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png"
			
			sizes="100vw"
			alt="Showing Quick Draw in the same toon title card but including the details that were left out before."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Adding details. (<a href='https://files.smashing.media/articles/ambient-animations-web-design-principles-implementation/9-quick-draw-toon-title-card-details.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues as they all slot into place automatically.</p>

<h2 id="implementing-ambient-animations">Implementing Ambient Animations</h2>

<p>You don’t need an animation framework or library to add ambient animations to a project. Most of the time, all you’ll need is a well-prepared SVG and some thoughtful CSS.</p>

<p>But, let’s start with the SVG. The key is to group elements logically and give them meaningful class or ID attributes, which act as animation hooks in the CSS. For this animation, I gave every moving part its own identifier like <code>#quick-draw-tail</code> or <code>#chief-smoke-2</code>. That way, I could target exactly what I needed without digging through the DOM like a raccoon in a trash can.</p>

<p>Once the SVG is set up, CSS does most of the work. I can use <code>@keyframes</code> for more expressive movement, or <code>animation-delay</code> to simulate randomness and stagger timings. The trick is to keep everything subtle and remember I’m not animating for attention, I’m animating for atmosphere.</p>

<p>Remember that most ambient animations loop continuously, so they should be <strong>lightweight</strong> and <strong>performance-friendly</strong>. And of course, <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">it’s good practice to respect users who’ve asked for less motion</a>. You can wrap your animations in an <code>@media prefers-reduced-motion</code> query so they only run when they’re welcome.</p>

<div class="break-out">
<pre><code class="language-javascript">@media (prefers-reduced-motion: no-preference) {
  &#35;quick-draw-shadow {
    animation: quick-draw-shadow-wobble 6s ease-in-out infinite;
  }
}
</code></pre>
</div>

<p>It’s a small touch that’s easy to implement, and it makes your designs more inclusive.</p>

<div class="partners__lead-place"></div>

<h2 id="ambient-animation-design-principles">Ambient Animation Design Principles</h2>

<p>If you want your animations to feel ambient, more like atmosphere than action, it helps to follow a few principles. These aren’t hard and fast rules, but rather things I’ve learned while animating smoke, sparkles, eyeballs, and eyebrows.</p>

<h3 id="keep-animations-slow-and-smooth">Keep Animations Slow And Smooth</h3>

<p>Ambient animations should feel relaxed, so use <strong>longer durations</strong> and choose <strong>easing curves that feel organic</strong>. I often use <code>ease-in-out</code>, but <a href="https://www.smashingmagazine.com/2022/10/advanced-animations-css/">cubic Bézier curves</a> can also be helpful when you want a more relaxed feel and the kind of movements you might find in nature.</p>

<h3 id="loop-seamlessly-and-avoid-abrupt-changes">Loop Seamlessly And Avoid Abrupt Changes</h3>

<p>Hard resets or sudden jumps can ruin the mood, so if an animation loops, ensure it cycles smoothly. You can do this by <strong>matching start and end keyframes</strong>, or by setting the <code>animation-direction</code> to <code>alternate</code> the value so the animation plays forward, then back.</p>

<h3 id="use-layering-to-build-complexity">Use Layering To Build Complexity</h3>

<p>A single animation might be boring. Five subtle animations, each on separate layers, can feel rich and alive. Think of it like building a sound mix &mdash; you want <strong>variation in rhythm, tone, and timing</strong>. In my animation, sparkles twinkle at varying intervals, smoke curls upward, feathers sway, and eyes boggle. Nothing dominates, and each motion plays its small part in the scene.</p>

<h3 id="avoid-distractions">Avoid Distractions</h3>

<p>The point of an ambient animation is that it doesn’t dominate. It’s a <strong>background element</strong> and not a call to action. If someone’s eyes are drawn to a raised eyebrow, it’s probably too much, so dial back the animation until it feels like something you’d only catch if you’re really looking.</p>

<h3 id="consider-accessibility-and-performance">Consider Accessibility And Performance</h3>

<p>Check <code>prefers-reduced-motion</code>, and don’t assume everyone’s device can handle complex animations. SVG and CSS are light, but things like blur filters and drop shadows, and complex CSS animations can still tax lower-powered devices. When an animation is purely decorative, consider adding <code>aria-hidden=&quot;true&quot;</code> to keep it from cluttering up the accessibility tree.</p>

<h2 id="quick-on-the-draw">Quick On The Draw</h2>

<p>Ambient animation is like seasoning on a great dish. It’s the pinch of salt you barely notice, but you’d miss when it’s gone. It doesn’t shout, it whispers. It doesn’t lead, it lingers. It’s floating smoke, swaying feathers, and sparkles you catch in the corner of your eye. And when it’s done well, ambient animation <strong>adds personality to a design without asking for applause</strong>.</p>

<p>Now, I realise that not everyone needs to animate cartoon characters. So, in part two, I’ll share how I created animations for several recent client projects. Until next time, if you’re crafting an illustration or working with SVG, ask yourself: <strong>What would move if this were real?</strong> Then animate just that. Make it slow and soft. Keep it ambient.</p>

<p>You can view the complete ambient animation <a href="https://codepen.io/malarkey/pen/NPGrWVy">code on CodePen</a>.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Victor Ayomipo</author><title>Integrating CSS Cascade Layers To An Existing Project</title><link>https://www.smashingmagazine.com/2025/09/integrating-css-cascade-layers-existing-project/</link><pubDate>Wed, 10 Sep 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/09/integrating-css-cascade-layers-existing-project/</guid><description>The idea behind this is to share a full, unfiltered look at integrating CSS Cascade Layers into an existing legacy codebase. In practice, it’s about refactoring existing CSS to use cascade layers without breaking anything.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/09/integrating-css-cascade-layers-existing-project/" />
              <title>Integrating CSS Cascade Layers To An Existing Project</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Integrating CSS Cascade Layers To An Existing Project</h1>
                  
                    
                    <address>Victor Ayomipo</address>
                  
                  <time datetime="2025-09-10T10:00:00&#43;00:00" class="op-published">2025-09-10T10:00:00+00:00</time>
                  <time datetime="2025-09-10T10:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>You can always get a fantastic overview of things in Stephenie Eckles’ article, “<a href="https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/">Getting Started With CSS Cascade Layers</a>”. But let’s talk about the experience of integrating cascade layers into real-world code, the good, the bad, and the spaghetti.</p>

<p>I could have created a sample project for a classic walkthrough, but nah, that’s not how things work in the real world. I want to get our hands dirty, like inheriting code with styles that work and no one knows why.</p>

<p>Finding projects without cascade layers was easy. The tricky part was finding one that was messy enough to have specificity and organisation issues, but broad enough to illustrate different parts of cascade layers integration.</p>

<p>Ladies and gentlemen, I present you with this <a href="https://github.com/Drix10/discord-bot-web">Discord bot website</a> by <a href="https://github.com/Drix10">Drishtant Ghosh</a>. I’m deeply grateful to Drishtant for allowing me to use his work as an example. This project is a typical landing page with a navigation bar, a hero section, a few buttons, and a mobile menu.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png"
			
			sizes="100vw"
			alt="Discord Bot landing page, including a circular logo centered above a heading, text blub, then a row of three buttons."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/integrating-css-cascade-layers-existing-project/1-discord-bot-landing-page.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You see how it looks perfect on the outside. Things get interesting, however, when we look at the CSS styles under the hood.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="understanding-the-project">Understanding The Project</h2>

<p>Before we start throwing <code>@layers</code> around, let’s get a firm understanding of what we’re working with. I <a href="https://codepen.io/vayospot/pen/bNdoYdP">cloned</a> the GitHub repo, and since our focus is working with CSS Cascade Layers, I’ll focus only on the main page, which consists of three files: <code>index.html</code>, <code>index.css</code>, and <code>index.js</code>.</p>

<p><strong>Note</strong>: <em>I didn’t include other pages of this project as it’d make this tutorial too verbose. However, you can refactor the other pages as an experiment.</em></p>

<p>The <code>index.css</code> file is over 450 lines of code, and skimming through it, I can see some red flags right off the bat:</p>

<ul>
<li>There’s a lot of code repetition with the same selectors pointing to the same HTML element.</li>
<li>There are quite a few <code>#id</code> selectors, which one might argue shouldn’t be used in CSS (and I am one of those people).</li>
<li><code>#botLogo</code> is defined twice and over 70 lines apart.</li>
<li>The <code>!important</code> keyword is used liberally throughout the code.</li>
</ul>

<p>And yet the site works. There is nothing “technically” wrong here, which is another reason CSS is a big, beautiful monster &mdash; errors are silent!</p>

<h2 id="planning-the-layer-structure">Planning The Layer Structure</h2>

<p>Now, some might be thinking, <em>“Can’t we simply move all of the styles into a single layer, like <code>@layer legacy</code> and call it a day?”</em></p>

<p>You could… but I don’t think you should.</p>

<p>Think about it: If more layers are added after the <code>legacy</code> layer, they <em>should</em> override the styles contained in the <code>legacy</code> layer because the specificity of layers is organized by priority, where the layers declared later carry higher priority.</p>

<pre><code class="language-css">/&#42; new is more specific &#42;/
@layer legacy, new;

/&#42; legacy is more specific &#42;/
@layer new, legacy;
</code></pre>

<p>That said, we must remember that the site’s existing styles make liberal use of the <code>!important</code> keyword. And when that happens, the order of cascade layers gets reversed. So, even though the layers are outlined like this:</p>

<pre><code class="language-css">@layer legacy, new;
</code></pre>

<p>…any styles with an <code>!important</code> declaration suddenly shake things up. In this case, the priority order becomes:</p>

<ol>
<li><code>!important</code> styles in the <code>legacy</code> layer (most powerful),</li>
<li><code>!important</code> styles in the <code>new</code> layer,</li>
<li>Normal styles in the <code>new</code> layer,</li>
<li>Normal styles in the <code>legacy</code> layer (least powerful).</li>
</ol>

<p>I just wanted to clear that part up. Let’s continue.</p>

<p>We know that cascade layers handle specificity by creating an explicit order where each layer has a clear responsibility, and later layers always win.</p>

<p>So, I decided to split things up into five distinct layers:</p>

<ul>
<li><strong><code>reset</code></strong>: Browser default resets like <code>box-sizing</code>, margins, and paddings.</li>
<li><strong><code>base</code></strong>: Default styles of HTML elements, like <code>body</code>, <code>h1</code>, <code>p</code>, <code>a</code>, etc., including default typography and colours.</li>
<li><strong><code>layout</code></strong>: Major page structure stuff for controlling how elements are positioned.</li>
<li><strong><code>components</code></strong>: Reusable UI segments, like buttons, cards, and menus.</li>
<li><strong><code>utilities</code></strong>: Single helper modifiers that do just one thing and do it well.</li>
</ul>

<p>This is merely how I like to break things out and organize styles. Zell Liew, for example, <a href="https://css-tricks.com/composition-in-css/">has a different set of four buckets</a> that could be defined as layers.</p>

<p>There’s also the concept of dividing things up even further into <strong>sublayers</strong>:</p>

<pre><code class="language-css">@layer components {
  /&#42; sub-layers &#42;/
  @layer buttons, cards, menus;
}

/&#42; or this: &#42;/
@layer components.buttons, components.cards, components.menus;
</code></pre>

<p>That might come in handy, but I also don’t want to overly abstract things. That might be a better strategy for a project that’s scoped to a well-defined design system.</p>

<p>Another thing we could leverage is <strong>unlayered styles</strong> and the fact that any styles not contained in a cascade layer get the highest priority:</p>

<pre><code class="language-css">@layer legacy { a { color: red !important; } }
@layer reset { a { color: orange !important; } }
@layer base { a { color: yellow !important; } }

/&#42; unlayered &#42;/
a { color: green !important; } /&#42; highest priority &#42;/
</code></pre>

<p>But I like the idea of keeping all styles organized in explicit layers because it keeps things <strong>modular</strong> and <strong>maintainable</strong>, at least in this context.</p>

<p>Let’s move on to adding cascade layers to this project.</p>

<div class="partners__lead-place"></div>

<h2 id="integrating-cascade-layers">Integrating Cascade Layers</h2>

<p>We need to define the layer order at the top of the file:</p>

<pre><code class="language-css">@layer reset, base, layout, components, utilities;
</code></pre>

<p>This makes it easy to tell which layer takes precedence over which (they get more priority from left to right), and now we can think in terms of layer responsibility instead of selector weight. Moving forward, I’ll proceed through the stylesheet from top to bottom.</p>

<p>First, I noticed that the <a href="https://fonts.google.com/specimen/Poppins?query=poppins">Poppins font</a> was imported in both the HTML and CSS files, so I removed the CSS import and left the one in <code>index.html</code>, as that’s generally recommended for quickly loading fonts.</p>

<p>Next is the universal selector (<code>*</code>) styles, which include <a href="https://css-tricks.com/box-sizing/">classic reset styles</a> that are perfect for <code>@layer reset</code>:</p>

<pre><code class="language-css">@layer reset {
  &#42; {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
}
</code></pre>

<p>With that out of the way, the <code>body</code> selector is next. I’m putting this into <code>@layer base</code> because it contains core styles for the project, like backgrounds and fonts:</p>

<div class="break-out">
<pre><code class="language-css">@layer base {
  body {
    background-image: url("bg.svg"); /&#42; Renamed to bg.svg for clarity &#42;/
    font-family: "Poppins", sans-serif;
    /&#42; ... other styles &#42;/
  }
}
</code></pre>
</div>

<p>The way I’m tackling this is that styles in the <code>base</code> layer should generally affect the whole document. So far, no page breaks or anything.</p>

<h3 id="swapping-ids-for-classes">Swapping IDs For Classes</h3>

<p>Following the <code>body</code> element selector is the page loader, which is defined as an ID selector, <code>#loader</code>.</p>

<blockquote>I’m a firm believer in using class selectors over ID selectors as much as possible. It keeps specificity low by default, which prevents specificity battles and <a href="https://css-tricks.com/the-difference-between-id-and-class/">makes the code a lot more maintainable</a>.</blockquote>

<p>So, I went into the <code>index.html</code> file and refactored elements with <code>id=&quot;loader&quot;</code> to <code>class=&quot;loader&quot;</code>. In the process, I saw another element with <code>id=&quot;page&quot;</code> and changed that at the same time.</p>

<p>While still in the <code>index.html</code> file, I noticed a few <code>div</code> elements missing closing tags. It is astounding how permissive browsers are with that. Anyways, I cleaned those up and moved the <code>&lt;script&gt;</code> tag out of the <code>.heading</code> element to be a direct child of <code>body</code>. Let’s not make it any tougher to load our scripts.</p>

<p>Now that we’ve levelled the specificity playing field by moving IDs to classes, we can drop them into the <code>components</code> layer since a loader is indeed a reusable component:</p>

<pre><code class="language-css">@layer components {
  .loader {
    width: 100%;
    height: 100vh;
    /&#42; ... &#42;/
  }
  .loader .loading {
    /&#42; ... &#42;/
  }
  .loader .loading span {
    /&#42; ... &#42;/
  }
  .loader .loading span:before {
    /&#42; ... &#42;/
  }
}
</code></pre>

<h3 id="animations">Animations</h3>

<p>Next are keyframes, and this was a bit tricky, but I eventually chose to isolate animations in their own new fifth layer and updated the layer order to include it:</p>

<div class="break-out">
<pre><code class="language-css">@layer reset, base, layout, components, utilities, animations;
</code></pre>
</div>

<p>But why place <code>animations</code> as the last layer? Because animations are generally the last to run and shouldn’t be affected by style conflicts.</p>

<p>I searched the project’s styles for <code>@keyframes</code> and dumped them into the new layer:</p>

<pre><code class="language-css">@layer animations {
  @keyframes loading {
    /&#42; ... &#42;/
  }
  @keyframes loading2 {
    /&#42; ... &#42;/
  }
  @keyframes pageShow {
    /&#42; ... &#42;/
  }
}
</code></pre>

<p>This gives a clear distinction of static styles from dynamic ones while also enforcing reusability.</p>

<h3 id="layouts">Layouts</h3>

<p>The <code>#page</code> selector also has the same issue as <code>#id</code>, and since we fixed it in the HTML earlier, we can modify it to <code>.page</code> and drop it in the <code>layout</code> layer, as its main purpose is to control the initial visibility of the content:</p>

<pre><code class="language-css">@layer layout {
  .page {
    display: none;
  }
}
</code></pre>

<h3 id="custom-scrollbars">Custom Scrollbars</h3>

<p>Where do we put these? Scrollbars are global elements that persist across the site. This might be a gray area, but I’d say it fits perfectly in <code>@layer base</code> since it’s a global, default feature.</p>

<pre><code class="language-css">@layer base {
  /&#42; ... &#42;/
  ::-webkit-scrollbar {
    width: 8px;
  }
  ::-webkit-scrollbar-track {
    background: &#35;0e0e0f;
  }
  ::-webkit-scrollbar-thumb {
    background: &#35;5865f2;
    border-radius: 100px;
  }
  ::-webkit-scrollbar-thumb:hover {
    background: &#35;202225;
  }
}
</code></pre>

<p>I also removed the <code>!important</code> keywords as I came across them.</p>

<h3 id="navigation">Navigation</h3>

<p>The <code>nav</code> element is pretty straightforward, as it is the main structure container that defines the position and dimensions of the navigation bar. It should definitely go in the <code>layout</code> layer:</p>

<pre><code class="language-css">@layer layout {
  /&#42; ... &#42;/
  nav {
    display: flex;
    height: 55px;
    width: 100%;
    padding: 0 50px; /&#42; Consistent horizontal padding &#42;/
    /&#42; ... &#42;/
  }
}
</code></pre>

<h3 id="logo">Logo</h3>

<p>We have three style blocks that are tied to the logo: <code>nav .logo</code>, <code>.logo img</code>, and <code>#botLogo</code>. These names are redundant and could benefit from inheritance component reusability.</p>

<p>Here’s how I’m approaching it:</p>

<ol>
<li>The <code>nav .logo</code> is overly specific since the logo can be reused in other places. I dropped the <code>nav</code> so that the selector is just <code>.logo</code>. There was also an <code>!important</code> keyword in there, so I removed it.</li>
<li>I updated <code>.logo</code> to be a Flexbox container to help position <code>.logo img</code>, which was previously set with less flexible absolute positioning.</li>
<li>The <code>#botLogo</code> ID is declared twice, so I merged the two rulesets into one and lowered its specificity by making it a <code>.botLogo</code> class. And, of course, I updated the HTML to replace the ID with the class.</li>
<li>The <code>.logo img</code> selector becomes <code>.botLogo</code>, making it the base class for styling all instances of the logo.</li>
</ol>

<p>Now, we’re left with this:</p>

<pre><code class="language-css">/&#42; initially .logo img &#42;/
.botLogo {
  border-radius: 50%;
  height: 40px;
  border: 2px solid &#35;5865f2;
}

/&#42; initially &#35;botLogo &#42;/
.botLogo {
  border-radius: 50%;
  width: 180px;
  /&#42; ... &#42;/
}
</code></pre>

<p>The difference is that one is used in the navigation and the other in the hero section heading. We can transform the second <code>.botLogo</code> by slightly increasing the specificity with a <code>.heading .botLogo</code> selector. We may as well clean up any duplicated styles as we go.</p>

<p>Let’s place the entire code in the <code>components</code> layer as we’ve successfully turned the logo into a reusable component:</p>

<div class="break-out">
<pre><code class="language-css">@layer components {
  /&#42; ... &#42;/
  .logo {
    font-size: 30px;
    font-weight: bold;
    color: &#35;fff;
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .botLogo {
    aspect-ratio: 1; /&#42; maintains square dimensions with width &#42;/
    border-radius: 50%;
    width: 40px;
    border: 2px solid &#35;5865f2;
  }
  .heading .botLogo {
    width: 180px;
    height: 180px;
    background-color: &#35;5865f2;
    box-shadow: 0px 0px 8px 2px rgba(88, 101, 242, 0.5);
    /&#42; ... &#42;/
  }
}
</code></pre>
</div>

<p>This was a bit of work! But now the logo is properly set up as a component that fits perfectly in the new layer architecture.</p>

<div class="partners__lead-place"></div>

<h3 id="navigation-list">Navigation List</h3>

<p>This is a typical navigation pattern. Take an unordered list (<code>&lt;ul&gt;</code>) and turn it into a flexible container that displays all of the list items horizontally on the same row (with wrapping allowed). It’s a type of navigation that can be reused, which belongs in the <code>components</code> layer. But there’s a little refactoring to do before we add it.</p>

<p>There’s already a <code>.mainMenu</code> class, so let’s lean into that. We’ll swap out any <code>nav ul</code> selectors with that class. Again, it keeps specificity low while making it clearer what that element does.</p>

<pre><code class="language-css">@layer components {
  /&#42; ... &#42;/
  .mainMenu {
    display: flex;
    flex-wrap: wrap;
    list-style: none;
  }
  .mainMenu li {
    margin: 0 4px;
  }
  .mainMenu li a {
    color: &#35;fff;
    text-decoration: none;
    font-size: 16px;
    /&#42; ... &#42;/
  }
  .mainMenu li a:where(.active, .hover) {
    color: &#35;fff;
    background: &#35;1d1e21;
  }
  .mainMenu li a.active:hover {
    background-color: &#35;5865f2;
  }
}
</code></pre>

<p>There are also two buttons in the code that are used to toggle the navigation between “open” and “closed” states when the navigation is collapsed on smaller screens. It’s tied specifically to the <code>.mainMenu</code> component, so we’ll keep everything together in the <code>components</code> layer. We can combine and simplify the selectors in the process for cleaner, more readable styles:</p>

<pre><code class="language-css">@layer components {
  /&#42; ... &#42;/
  nav:is(.openMenu, .closeMenu) {
    font-size: 25px;
    display: none;
    cursor: pointer;
    color: &#35;fff;
  }
}
</code></pre>

<p>I also noticed that several other selectors in the CSS were not used anywhere in the HTML. So, I removed those styles to keep things trim. There are <a href="https://css-tricks.com/how-do-you-remove-unused-css-from-a-site/">automated ways to go about this</a>, too.</p>

<h3 id="media-queries">Media Queries</h3>

<p>Should media queries have a dedicated layer (<code>@layer responsive</code>), or should they be in the same layer as their target elements? I really struggled with that question while refactoring the styles for this project. I did some research and testing, and my verdict is the latter, that <strong>media queries ought to be in the same layer as the elements they affect</strong>.</p>

<p>My reasoning is that keeping them together:</p>

<ul>
<li>Maintains responsive styles with their base element styles,</li>
<li>Makes overrides predictable, and</li>
<li>Flows well with component-based architecture common in modern web development.</li>
</ul>

<p>However, it also means <strong>responsive logic</strong> is scattered across layers. But it beats the one with a gap between the layer where elements are styled and the layer where their responsive behaviors are managed. That’s a deal-breaker for me because it’s way too easy to update styles in one layer and forget to update their corresponding responsive style in the responsive layer.</p>

<p>The other big point is that media queries in the same layer have <strong>the same priority</strong> as their elements. This is consistent with my overall goal of keeping the CSS Cascade simple and predictable, free of style conflicts.</p>

<p>Plus, the <a href="https://css-tricks.com/tag/nesting/">CSS nesting syntax</a> makes the relationship between media queries and elements super clear. Here’s an abbreviated example of how things look when we nest media queries in the <code>components</code> layer:</p>

<pre><code class="language-css">@layer components {
  .mainMenu {
    display: flex;
    flex-wrap: wrap;
    list-style: none;
  }
  @media (max-width: 900px) {
    .mainMenu {
      width: 100%;
      text-align: center;
      height: 100vh;
      display: none;
    }
  }
}
</code></pre>

<p>This also allows me to nest a component’s child element styles (e.g., <code>nav .openMenu</code> and <code>nav .closeMenu</code>).</p>

<pre><code class="language-css">@layer components {
  nav {
    &.openMenu {
      display: none;
      
      @media (max-width: 900px) {
        &.openMenu {
          display: block;
        }
      }
    }
  }
}
</code></pre>

<h3 id="typography-buttons">Typography &amp; Buttons</h3>

<p>The <code>.title</code> and <code>.subtitle</code> can be seen as typography components, so they and their responsive associates go into &mdash; you guessed it &mdash; the <code>components</code> layer:</p>

<pre><code class="language-css">@layer components {
  .title {
    font-size: 40px;
    font-weight: 700;
    /&#42; etc. &#42;/
  }
  .subtitle {
    color: rgba(255, 255, 255, 0.75);
    font-size: 15px;
    /&#42; etc.. &#42;/
  }
  @media (max-width: 420px) {
    .title {
      font-size: 30px;
    }
    .subtitle {
      font-size: 12px;
    }
  }
}
</code></pre>

<p>What about buttons? Like many website’s this one has a class, <code>.btn</code>, for that component, so we can chuck those in there as well:</p>

<pre><code class="language-css">@layer components {
  .btn {
    color: &#35;fff;
    background-color: #1d1e21;
    font-size: 18px;
    /&#42; etc. &#42;/
  }
  .btn-primary {
    background-color: &#35;5865f2;
  }
  .btn-secondary {
    transition: all 0.3s ease-in-out;
  }
  .btn-primary:hover {
    background-color: &#35;5865f2;
    box-shadow: 0px 0px 8px 2px rgba(88, 101, 242, 0.5);
    /&#42; etc. &#42;/
  }
  .btn-secondary:hover {
    background-color: &#35;1d1e21;
    background-color: rgba(88, 101, 242, 0.7);
  }
  @media (max-width: 420px) {
    .btn {
      font-size: 14px;
      margin: 2px;
      padding: 8px 13px;
    }
  }
  @media (max-width: 335px) {
    .btn {
      display: flex;
      flex-direction: column;
    }
  }
}
</code></pre>

<h3 id="the-final-layer">The Final Layer</h3>

<p>We haven’t touched the <code>utilities</code> layer yet! I’ve reserved this layer for helper classes that are designed for specific purposes, like hiding content &mdash; or, in this case, there’s a <code>.noselect</code> class that fits right in. It has a single reusable purpose: to disable selection on an element.</p>

<p>So, that’s going to be the only style rule in our <code>utilities</code> layer:</p>

<pre><code class="language-css">@layer utilities {
  .noselect {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -webkit-user-drag: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
  }
}
</code></pre>
 

<p>And that’s it! We’ve completely refactored the CSS of a real-world project to use CSS Cascade Layers. You can compare <a href="https://codepen.io/vayospot/pen/bNdoYdP">where we started</a> with the <a href="https://codepen.io/vayospot/pen/XJbeVdB">final code</a>.</p>

<h2 id="it-wasn-t-all-easy">It Wasn’t All Easy</h2>

<p>That’s not to say that working with Cascade Layers was challenging, but there were some sticky points in the process that forced me to pause and carefully think through what I was doing.</p>

<p>I kept some notes as I worked:</p>

<ul>
<li><strong>It’s tough to determine where to start with an existing project.</strong><br />
However, by defining the layers first and setting their priority levels, I had a framework for deciding how and where to move specific styles, even though I was not totally familiar with the existing CSS. That helped me avoid situations where I might second-guess myself or define extra, unnecessary layers.</li>
<li><strong>Browser support is still a thing!</strong><br />
I mean, Cascade Layers enjoy 94% support coverage as I’m writing this, but you might be one of those sites that needs to accommodate legacy browsers that are unable to support layered styles.</li>
<li><strong>It wasn’t clear where media queries fit into the process.</strong><br />
Media queries put me on the spot to find where they work best: nested in the same layers as their selectors, or in a completely separate layer? I went with the former, as you know.</li>
<li><strong>The <code>!important</code> keyword is a juggling act.</strong><br />
They invert the entire layering priority system, and this project was littered with instances. Once you start chipping away at those, the existing CSS architecture erodes and requires a balance between refactoring the code and fixing what’s already there to know exactly how styles cascade.</li>
</ul>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aOverall,%20refactoring%20a%20codebase%20for%20CSS%20Cascade%20Layers%20is%20a%20bit%20daunting%20at%20first%20glance.%20The%20important%20thing,%20though,%20is%20to%20acknowledge%20that%20it%20isn%e2%80%99t%20really%20the%20layers%20that%20complicate%20things,%20but%20the%20existing%20codebase.%0a&url=https://smashingmagazine.com%2f2025%2f09%2fintegrating-css-cascade-layers-existing-project%2f">
      
Overall, refactoring a codebase for CSS Cascade Layers is a bit daunting at first glance. The important thing, though, is to acknowledge that it isn’t really the layers that complicate things, but the existing codebase.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>It’s tough to completely overhaul someone’s existing approach for a new one, even if the new approach is elegant.</p>

<h2 id="where-cascade-layers-helped-and-didn-t">Where Cascade Layers Helped (And Didn’t)</h2>

<p>Establishing layers improved the code, no doubt. I’m sure there are some <strong>performance benchmarks</strong> in there since we were able to remove unused and conflicting styles, but the real win is in <strong>a more maintainable set of styles</strong>. It’s easier to find what you need, know what specific style rules are doing, and where to insert new styles moving forward.</p>

<p>At the same time, I wouldn’t say that Cascade Layers are a silver bullet solution. Remember, CSS is intrinsically tied to the HTML structure it queries. If the HTML you’re working with is unstructured and suffers from <code>div</code>-itus, then you can safely bet that the effort to untangle that mess is higher and involves rewriting markup at the same time.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aRefactoring%20CSS%20for%20cascade%20layers%20is%20most%20certainly%20worth%20the%20maintenance%20enhancements%20alone.%0a&url=https://smashingmagazine.com%2f2025%2f09%2fintegrating-css-cascade-layers-existing-project%2f">
      
Refactoring CSS for cascade layers is most certainly worth the maintenance enhancements alone.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>It may be “easier” to start from scratch and define layers as you work from the ground up because there’s less inherited overhead and technical debt to sort through. But if you have to start from an existing codebase, you might need to de-tangle the complexity of your styles first to determine exactly how much refactoring you’re looking at.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Gabriel Shoyombo</author><title>CSS Intelligence: Speculating On The Future Of A Smarter Language</title><link>https://www.smashingmagazine.com/2025/07/css-intelligence-speculating-future-smarter-language/</link><pubDate>Wed, 02 Jul 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/07/css-intelligence-speculating-future-smarter-language/</guid><description>CSS has evolved from a purely presentational language into one with growing logical powers — thanks to features like container queries, relational pseudo-classes, and the &lt;code>if()&lt;/code> function. Is it still just for styling, or is it becoming something more? Gabriel Shoyombo explores how smart CSS has become over the years, where it is heading, the challenges it addresses, whether it is becoming too complex, and how developers are reacting to this shift.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/07/css-intelligence-speculating-future-smarter-language/" />
              <title>CSS Intelligence: Speculating On The Future Of A Smarter Language</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>CSS Intelligence: Speculating On The Future Of A Smarter Language</h1>
                  
                    
                    <address>Gabriel Shoyombo</address>
                  
                  <time datetime="2025-07-02T13:00:00&#43;00:00" class="op-published">2025-07-02T13:00:00+00:00</time>
                  <time datetime="2025-07-02T13:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Once upon a time, CSS was purely presentational. It imperatively handled the fonts, colors, backgrounds, spacing, and layouts, among other styles, for markup languages. It was a <strong>language for looks</strong>, doing what it was asked to, never thinking or making decisions. At least, that was what it was made for when <a href="https://www.w3.org/People/howcome/">Håkon Wium Lie proposed CSS in 1994</a>, and the World Wide Web Consortium (W3C) adopted it two years later.</p>

<p>Fast-forward to today, a lot has changed with the addition of new features, and more are on the way that shift the style language to a more imperative paradigm. CSS now actively powers complex responsive and interactive user interfaces. With recent advancements like <a href="https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/">container queries</a>, <a href="https://www.smashingmagazine.com/2021/06/has-native-css-parent-selector/">relational pseudo-classes</a>, and <a href="https://www.w3.org/TR/css-values-5/#if-notation">the <code>if()</code> function</a>, the language once within the <strong>domains of presentations</strong> has stepped foot into the <strong>territory of logic</strong>, reducing its reliance on the language that had handled its logical aspect to date, JavaScript.</p>

<p>This shift presents interesting questions about CSS and its future for developers. CSS has deliberately remained within the domains of styling alone for a while now, but is it time for that to change? Also, is CSS still a <strong>presentational language</strong> as it started, or is it becoming something more and bigger? This article explores how smart CSS has become over the years, where it is heading, the problems it is solving, whether it is getting too complex, and how developers are reacting to this shift.</p>

<h2 id="historical-context-css-s-intentional-simplicity">Historical Context: CSS’s Intentional Simplicity</h2>

<p>A glimpse into CSS history shows a language born to separate content from presentation, making web pages easier to manage and maintain. The first official version of CSS, <a href="https://www.w3.org/TR/CSS1/">CSS1</a>, was released in 1996, and it introduced basic styling capabilities like font properties, colors, box model (padding, margin, and border), sizes (width and height), a few simple displays (none, block, and inline), and basic selectors.</p>

<p>Two years later, <a href="https://www.w3.org/TR/CSS2/">CSS2 was launched</a> and expanded what CSS could style in HTML with features like positioning, <code>z-index</code>, enhanced selectors, table layouts, and media types for different devices. However, there were inconsistencies within the style language, an issue CSS2.1 resolved in 2011, becoming the standard for modern CSS. It simplified web authoring and site maintenance.</p>

<p>CSS was largely <strong>static</strong> and <strong>declarative</strong> during the years between CSS1 and CSS2.1. Developers experienced a mix of frustrations and breakthroughs for their projects. Due to the absence of intuitive layouts like Flexbox and CSS Grid, developers relied on hacky alternatives with table layouts, positioning, or floats to get around complex designs, even though <a href="https://www.w3.org/TR/CSS1/#floating-elements">floats were originally designed for text to wrap around an obstacle</a> on a webpage, usually a media object. As a result, developers faced issues with collapsing containers and unexpected wrapping behaviour. Notwithstanding, basic styling was intuitive. A newbie could easily pick up web development today and add basic styling the next day. CSS was separated from content and logic, and as a result, it was <strong>highly performant</strong> and <strong>lightweight</strong>.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="css3-the-first-step-toward-context-awareness">CSS3: The First Step Toward Context Awareness</h2>

<p>Things changed <a href="https://www.smashingmagazine.com/2012/07/learning-css3-useful-reference-guide/">when CSS3 rolled out</a>. Developers had expected a single monolithic update like the previous versions, but their expectations and the reality of the latest release were unmatched. The CSS3 red carpet revealed a <strong>modular system</strong> with powerful layout tools like Flexbox, CSS Grid, and media queries, defining for the first time how developers establish responsive designs. <a href="https://www.w3.org/Style/CSS/current-work">With over 20 modules</a>, CSS3 marked the inception of a <strong>“smarter CSS”</strong>.</p>

<p>Flexbox’s introduction around 2012 provided a flexible, one-dimensional layout system, while CSS Grid, launched in 2017, took layout a step further by offering a two-dimensional layout framework, making complex designs with minimal code possible. These advancements, as discussed by <a href="https://origin-blog.mediatemple.net/design-creative/five-huge-css-milestones/">Chris Coyier</a>, reduced reliance on hacks like floats.</p>

<p>It did not stop there. There’s <a href="https://www.w3.org/TR/mediaqueries-3/">media queries</a>, a prominent release of CSS3, that is one of the major contributors to this <em>smart CSS</em>. With media queries, CSS can react to different devices’ screens, adjusting its styles to fit the screen dimensions, aspect ratio, and orientation, a feat that earlier versions could not easily achieve. In the fifth level, it added <a href="https://www.w3.org/TR/mediaqueries-5/#mf-user-preferences">user preference media features</a> such as <code>prefers-color-scheme</code> and <code>prefers-reduced-motion</code>, making CSS more <strong>user-centric</strong> by adapting styles to user settings, <strong>enhancing accessibility</strong>.</p>

<p>CSS3 marked the beginning of a <strong>context-aware CSS</strong>.</p>

<blockquote>Context-awareness means the ability to understand and react to the situation around you or in your environment accordingly. It means systems and devices can sense critical information, like your location, time of day, and activity, and adjust accordingly.</blockquote>

<p>In web development, the term “context-awareness” has always been used with components, but what drives a context-aware component? If you mentioned anything other than the component’s styles, you would be wrong! For a component to be considered context-aware, <a href="https://www.lukeleber.com/blog/2024-07-25-context-aware-components">it needs to feel its environment’s presence</a> and know what happens in it. For instance, for your website to update its styles to accommodate a dark mode interface, it needs to be aware of the user’s preferences. Also, to change its layout, a website needs to know the device a user is accessing it on &mdash; and thanks to user preference media queries, that is possible.</p>

<p>Despite these features, CSS remained largely reactive. It responded to external factors like screen size (via media queries) or input states (like <code>:hover</code>, <code>:focus</code>, or <code>:checked</code>), but it never made decisions based on the changes in its environment. Developers typically turn to JavaScript for that level of interaction.</p>

<p>However, not anymore.</p>

<p>For example, with container queries and, more recently, <a href="https://www.smashingmagazine.com/2024/06/what-are-css-container-style-queries-good-for/">container <em>style</em> queries</a>, CSS now responds not only to layout constraints but to <strong>design intent</strong>. It can adjust based on a component’s environment and even its parent’s theme or state. And that’s not all. The recently specced <code>if()</code> function promises <strong>inline conditional logic</strong>, <a href="https://css-tricks.com/if-css-gets-inline-conditionals/">allowing styles to change based on conditions</a>, all of which can be achieved without scripting.</p>

<p>These developments suggest CSS is moving beyond presentation to handle behaviour, challenging its traditional role.</p>

<h2 id="new-css-features-driving-intelligence">New CSS Features Driving Intelligence</h2>

<p>Several features are currently pushing CSS towards a dynamic and adaptive edge, thereby making it smarter, but these two are worth mentioning: container style queries and the <code>if()</code> function.</p>

<h3 id="what-are-container-style-queries-and-why-do-they-matter">What Are Container Style Queries, And Why Do They Matter?</h3>

<p>To better understand what container style queries are, it makes sense to make a quick stop at a close cousin: container size queries introduced in the <a href="https://www.w3.org/TR/css-contain-3/">CSS Containment Module Level 3</a>.</p>

<p><a href="https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/">Container size queries</a> allow developers to style elements based on the dimensions of their parent container. This is a huge win for component-based designs as it eliminates the need to shoehorn responsive styles into global media queries.</p>

<pre><code class="language-css">/&#42; Size-based container query &#42;/
@container (min-width: 500px) {
  .card {
    flex-direction: row;
  }
}
</code></pre>

<p><a href="https://css-tricks.com/css-container-queries/#aa-container-style-queries">Container style queries</a> take it a step further by allowing you to style elements based on custom properties (aka CSS variables) set on the container.</p>

<pre><code class="language-css">/&#42; Style-based container query &#42;/
@container style(--theme: dark) {
  .button {
    background: black;
    color: white;
  }
}
</code></pre>

<p>These features are a big deal in CSS because they unlock <strong>context-aware components</strong>. A button can change appearance based on a <code>--theme</code> property set by a parent without using JavaScript or hardcoded classes.</p>

<h3 id="the-if-function-a-glimpse-into-the-future">The <code>if()</code> Function: A Glimpse Into The Future</h3>

<p>The CSS <code>if()</code> function might just be the most radical shift yet. When implemented (Chrome is the only one to support it, <a href="https://developer.chrome.com/blog/new-in-chrome-137?hl=en#if">as of version 137</a>), it would allow developers to write inline conditional logic directly in property declarations. Think of the <em>ternary operator</em> in CSS.</p>

<pre><code class="language-css">padding: if(style(--theme: dark): 2rem; else: 3rem);
</code></pre>

<p>This hypothetical line or pseudo code, <em>not syntax</em>, sets the text color to white <em>if</em> the <code>--theme</code> variable equals <code>dark</code>, or black otherwise. Right now, the <code>if()</code> function is not supported in any browser, but it is on the radar of the CSS Working Group, and influential developers like <a href="https://lea.verou.me/blog/2024/css-conditionals/">Lea Verou</a> are already exploring its possibilities.</p>

<div class="partners__lead-place"></div>

<h2 id="the-new-css-is-the-boundary-between-css-and-javascript-blurring">The New CSS: Is The Boundary Between CSS And JavaScript Blurring?</h2>

<p>Traditionally, the separation of concerns concerning styling was thus: <a href="https://medium.com/@giosterr44/mastering-the-basics-why-html-css-javascript-are-still-essential-c0343ab485b4">CSS for how things look and JavaScript for how things behave</a>. However, features like container style queries and the specced <code>if()</code> function are starting to blur the line. CSS is beginning to <em>behave</em>, not in the sense of API calls or event listeners, but in the ability to conditionally apply styles based on logic or context.</p>

<p>As web development evolved, CSS started encroaching on JavaScript territory. CSS3 brought in animations and transitions, a powerful combination for interactive web development, which was impossible without JavaScript in the earlier days. Today, research proves that CSS has taken on several interactive tasks previously handled by JavaScript. For example, the <code>:hover</code> pseudo-class and <code>transition</code> property allow for visual feedback and smooth animations, as discussed in “<a href="https://www.smashingmagazine.com/2011/02/bringing-interactivity-to-your-website-with-web-standards/">Bringing Interactivity To Your Website With Web Standards</a>”.</p>

<p>That’s not all. Toggling accordions and modals existed within the domains of JavaScript before, but today, this is possible with new <a href="https://pagepro.co/blog/html-css-vs-javascript/">powerful CSS combos like the <code>&lt;details&gt;</code> and <code>&lt;summary&gt;</code> HTML tags for accordions or modals with the <code>:target</code> pseudo-class</a>. CSS can also handle tooltips using <code>aria-label</code> with <code>content: attr(aria-label)</code>, and star ratings with radio inputs and labels, as detailed in the same <a href="https://pagepro.co/blog/html-css-vs-javascript/">article</a>.</p>

<p>Another article, “<a href="https://blog.logrocket.com/5-things-you-can-do-with-css-instead-of-javascript/">5 things you can do with CSS instead of JavaScript</a>”, lists features like <code>scroll-behavior: smooth</code> for smooth scrolling and <code>@media (prefers-color-scheme: dark)</code> for dark mode, tasks that once required JavaScript. In the same article, you can also see that it’s possible to create a carousel without JavaScript by using the CSS scroll snapping functionality (and we’re not even talking about features designed specifically for creating carousels solely in CSS, recently <a href="https://developer.chrome.com/blog/carousels-with-css?hl=en">prototyped in Chrome</a>).</p>

<p>These extensions of CSS into the JavaScript domain have now left the latter with handling only complex, crucial interactions in a web application, such as user inputs, making API calls, and managing state. While the CSS pseudo-classes like <code>:valid</code> and <code>:invalid</code> can help as error or success indicators in input elements, you still need JavaScript for dynamic content updates, form validation, and real-time data fetching.</p>

<p>CSS now solves problems that many developers never knew existed. With JavaScript out of the way in many style scenarios, developers now have simplified codebases. The dependencies are fewer, the overheads are lower, and website performance is better, especially on mobile devices. In fact, this shift leans CSS towards a <strong>more accessible web</strong>, as CSS-driven designs are often easier for browsers and assistive technologies to process.</p>

<p>While the new features come with a lot of benefits, they also introduce complexities that did not exist before:</p>

<ul>
<li>What happens when logic is spread across both CSS and JavaScript?</li>
<li>How do we <strong>debug conditional styles</strong> without a clear view of what triggered them?</li>
<li>CSS only had to deal with basic styling like colors, fonts, layouts, and spacing, which were easier for new developers to onboard. How hard does the <strong>learning curve</strong> become as these new features require understanding concepts once exclusive to JavaScript?</li>
</ul>

<p>Developers are split. While some welcome the idea of a natural evolution of a smarter, more component-aware web, <a href="https://css-tricks.com/is-there-too-much-css-now/">others worry CSS is becoming too complex</a> &mdash; a language originally designed for formatting documents now juggling logic trees and style computation.</p>

<h2 id="divided-perspective-is-logic-in-css-helpful-or-harmful">Divided Perspective: Is Logic In CSS Helpful Or Harmful?</h2>

<p>While the evidence in the previous section leans towards boundary-blurring, there’s significant <strong>controversy among developers</strong>. Many modern developers argue that logic in CSS is long overdue. As web development grows more componentized, the limitations of declarative styling have become more apparent, causing proponents to see logic as a necessary evolution for a once purely styling language.</p>

<p>For instance, in frontend libraries like React, components often require conditional styles based on props or states. Developers have had to make do with JavaScript or CSS-in-JS solutions for such cases, but the truth remains that these solutions are not right. They introduce complexity and couple styles and logic. CSS and JavaScript are meant to have standalone concerns in web development, <a href="https://css-tricks.com/the-differing-perspectives-on-css-in-js/">but libraries like CSS-in-JS have ignored the rules and combined both</a>.</p>

<p>We have seen how preprocessors like SASS and LESS proved the usefulness of conditionals, loops, and variables in styling. Developers who do not accept the CSS in JavaScript approach have settled for these preprocessors. Nevertheless, like <a href="https://x.com/argyleink/status/1317304102460608512?t=rgyYyNApPOZt8iqh8NTEUQ&amp;s=19">Adam Argyle</a>, they voice their need for native CSS solutions. With native conditionals, developers could reduce JavaScript overhead and avoid runtime class toggling to achieve conditional presentation.</p>

<blockquote>“It never felt right to me to manipulate style settings in JavaScript when CSS is the right tool for the job. With CSS custom properties, we can send to CSS what needs to come from JavaScript.”<br /><br />&mdash; <a href="https://x.com/codepo8/status/1358082931122724864">Chris Heilmann</a></blockquote>

<p>Also, Bob Ziroll <a href="https://x.com/bobziroll/status/1819078139055595669">dislikes using JavaScript for what CSS is meant to handle</a> and finds it unnecessary. This reflects a preference for using CSS for styling tasks, even when JavaScript is involved. These developers embrace CSS’s new capabilities, seeing it as a way to reduce JavaScript dependency for performance reasons.</p>

<p>Others argue against it. Introducing logic into CSS is a slippery slope, and CSS could lose its core strengths &mdash; simplicity, readability, and accessibility &mdash; by becoming too much like a programming language. The fear is that developers run the risk of <a href="https://www.smashingmagazine.com/2024/02/web-development-getting-too-complex/">complicating the web more than it is supposed to be</a>.</p>

<blockquote>“I’m old-fashioned. I like my CSS separated from my HTML; my HTML separated from my JS; my JS separated from my CSS.”<br /><br />&mdash; <a href="https://x.com/SaraSoueidan/status/1273181281103351812">Sara Soueidan</a></blockquote>

<p>This view emphasises the traditional separation of concerns, arguing that mixing roles can complicate maintenance. Additionally, Brad Frost has also <a href="https://x.com/brad_frost/status/993189025132490755">expressed skepticism</a> when talking specifically about CSS-in-JS, stating that it, <em>“doesn’t scale to non-JS-framework environments, adds more noise to an already-noisy JS file, and the demos/examples I have seen haven’t embodied CSS best practices.”</em> This highlights concerns about scalability and best practices, suggesting that <strong>the blurred boundary might not always be beneficial</strong>.</p>

<p>Community discussions, such as on <a href="https://stackoverflow.com/questions/24012569/is-it-always-better-to-use-css-when-possible-instead-of-js">Stack Overflow</a>, also reflect this divide. A question like <em>“Is it always better to use CSS when possible instead of JS?”</em> receives answers favouring CSS for performance and simplicity, but others argue JavaScript is necessary for complex scenarios, illustrating the ongoing debate. Don’t be fooled. It might seem convenient to agree that CSS performs better than JavaScript in styling, <a href="https://css-tricks.com/myth-busting-css-animations-vs-javascript/">but that’s not always the case</a>.</p>

<h2 id="a-smarter-css-without-losing-its-soul">A Smarter CSS Without Losing Its Soul</h2>

<p>CSS has always stood apart from full-blown programming languages, like JavaScript, by being declarative, accessible, and purpose-driven.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aIf%20CSS%20is%20to%20grow%20more%20intelligent,%20the%20challenge%20lies%20not%20in%20making%20it%20more%20powerful%20for%20its%20own%20sake%20but%20in%20evolving%20it%20without%20compromising%20its%20major%20concern.%0a&url=https://smashingmagazine.com%2f2025%2f07%2fcss-intelligence-speculating-future-smarter-language%2f">
      
If CSS is to grow more intelligent, the challenge lies not in making it more powerful for its own sake but in evolving it without compromising its major concern.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>So, what might a logically enriched but <em>still declarative</em> CSS look like? Let’s find out.</p>

<h3 id="conditional-rules-if-when-else-with-carefully-introduced-logic">Conditional Rules (<code>if</code>, <code>@when</code>…<code>@else</code>) With Carefully Introduced Logic</h3>

<p>A major frontier in CSS evolution is the introduction of native conditionals via the <a href="https://chromestatus.com/feature/6313805904347136"><code>if()</code></a> function and the <code>@when</code>…<code>@else</code> at-rules, which are part of the <a href="https://drafts.csswg.org/css-conditional-5/">CSS Conditional Rules Module Level 5</a> specification. While still in the early draft stages, this would allow developers to apply styles based on evaluated conditions without turning to JavaScript or a preprocessor. Unlike JavaScript’s imperative nature, these conditionals aim to keep logic ingrained in CSS’s existing flow, aligned with the cascade and specificity.</p>

<h3 id="more-powerful-intentional-selectors">More Powerful, Intentional Selectors</h3>

<p>Selectors have always been one of the major strengths of CSS, and expanding them in a targeted way would make it easier to express relationships and conditions declaratively without needing classes or scripts. Currently, <a href="https://css-tricks.com/the-css-has-selector/"><code>:has()</code></a> lets developers style a parent based on a child, and <code>:nth-child(An+B [of S]?)</code> (<a href="https://www.w3.org/TR/selectors-4/">in Selectors Level 4</a>) allows for more complex matching patterns. Together, they allow greater precision without altering CSS’s nature.</p>

<h3 id="scoped-styling-without-javascript">Scoped Styling Without JavaScript</h3>

<p>One of the challenges developers face in component-based frameworks like React or Vue is style scoping. Style scoping ensures styles apply only to specific elements or components and do not leak out. In the past, to achieve this, you needed to implement BEM naming conventions, CSS-in-JS, or build tools like CSS Modules. Native scoped styling in CSS, via the new experimental <a href="https://css-tricks.com/almanac/rules/s/scope/"><code>@scope</code></a> rule, allows developers to encapsulate styles in a specific context without extra tooling. This feature makes CSS more modular without tying it to JavaScript logic or complex class systems.</p>

<p>A fundamental design question now is whether we could empower CSS without making it like JavaScript. The truth is, to empower CSS with conditional logic, powerful selectors, and scoped rules, we don’t need it to mirror JavaScript’s syntax or complexity. The goal is declarative expressiveness, giving CSS more awareness and control while retaining its clear, readable nature, and we should focus on that. When done right, smarter CSS can amplify the language’s strengths rather than dilute them.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aThe%20real%20danger%20is%20not%20logic%20itself%20but%20unchecked%20complexity%20that%20obscures%20the%20simplicity%20with%20which%20CSS%20was%20built.%0a&url=https://smashingmagazine.com%2f2025%2f07%2fcss-intelligence-speculating-future-smarter-language%2f">
      
The real danger is not logic itself but unchecked complexity that obscures the simplicity with which CSS was built.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<div class="partners__lead-place"></div>

<h2 id="cautions-and-constraints-why-smart-isn-t-always-better">Cautions And Constraints: Why Smart Isn’t Always Better</h2>

<p>The push for a smarter CSS comes with significant trade-offs alongside control and flexibility. Over the years, <a href="https://www.quora.com/In-general-does-adding-features-to-a-programming-language-make-it-better">history has shown that adding a new feature to a language or framework, or library, most likely introduces complexity</a>, not just for newbies, but also for expert developers. The danger is not in CSS gaining power but in how that power is implemented, taught, and used.</p>

<p>One of CSS’s greatest strengths has always been its <strong>approachability</strong>. Designers and beginners could learn the basics quickly: selectors, properties, and values. With more logic, scoping, and advanced selectors being introduced, that learning curve steepens. The risk is a widening gap between “basic CSS” and “real-world CSS”, echoing what happened with JavaScript and its ecosystem.</p>

<p>As CSS becomes more powerful, developers increasingly lean on tooling to manage and abstract that power, like building systems (e.g., webpack, Vite), linters and formatters, and component libraries with strict styling conventions. This creates dependencies that are hard to escape. <strong>Tooling becomes a prerequisite, not an option</strong>, further complicating onboarding and increasing setup time for projects that used to work with a single stylesheet.</p>

<p>Also, more logic means more potential for <strong>unexpected outcomes</strong>. New issues might arise that are harder to spot and fix. Resources like DevTools will then need to evolve to visualise scope boundaries, conditional applications, and complex selector chains. Until then, debugging may remain a challenge. <a href="https://robkendal.co.uk/blog/why-is-css-in-js-a-bad-or-good-idea/">All of these are challenges experienced with CSS-in-JS</a>; how much more Native CSS?</p>

<p>We’ve seen this before. CSS history is filled with overcomplicated workarounds, like tables for the layout before Flexbox, relying on floats with clear fix hacks, and overly rigid grid systems before native CSS Grid. In each case, the hacky solution eventually became the problem. CSS got better not by mimicking other languages but by <em>standardising thoughtful, declarative solutions</em>. With the right power, <a href="https://rachelandrew.co.uk/archives/2020/04/07/making-things-better/">we can make CSS better</a> at the end of the day.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We just took a walk down the history lane of CSS, explored its presence, and peeked into what its future could be. We can all agree that CSS has come a long way from a simple, declarative language to a <strong>dynamic</strong>, <strong>context-aware</strong>, and, yes, <strong>smarter language</strong>. The evolution, of course, comes with tension: a smarter styling language with fewer dependencies on scripts and a complex one with a steeper learning curve.</p>

<p>This is what I conclude:</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aThe%20future%20of%20CSS%20shouldn%e2%80%99t%20be%20a%20race%20to%20add%20logic%20for%20its%20own%20sake.%20Instead,%20it%20should%20be%20a%20thoughtful%20expansion,%20power%20balanced%20by%20clarity%20and%20innovation%20grounded%20in%20accessibility.%0a&url=https://smashingmagazine.com%2f2025%2f07%2fcss-intelligence-speculating-future-smarter-language%2f">
      
The future of CSS shouldn’t be a race to add logic for its own sake. Instead, it should be a thoughtful expansion, power balanced by clarity and innovation grounded in accessibility.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>That means asking tough questions before shipping new features. It means ensuring that new capabilities help solve actual problems without introducing new barriers.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Myriam Frisano</author><title>Decoding The SVG &lt;code>path&lt;/code> Element: Curve And Arc Commands</title><link>https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-curve-arc-commands/</link><pubDate>Mon, 23 Jun 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-curve-arc-commands/</guid><description>On her quest to teach you how to code vectors by hand, Myriam Frisano’s second installment of a &lt;code>path&lt;/code> deep dive explores the most complex aspects of SVG’s most powerful element. She’ll help you understand the underlying rules and function of how curves and arcs are constructed. By the end of it, your toolkit is ready to tackle all types of tasks required to draw with code — even if some of the lines twist and turn.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-curve-arc-commands/" />
              <title>Decoding The SVG &lt;code&gt;path&lt;/code&gt; Element: Curve And Arc Commands</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Decoding The SVG &lt;code&gt;path&lt;/code&gt; Element: Curve And Arc Commands</h1>
                  
                    
                    <address>Myriam Frisano</address>
                  
                  <time datetime="2025-06-23T10:00:00&#43;00:00" class="op-published">2025-06-23T10:00:00+00:00</time>
                  <time datetime="2025-06-23T10:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>In the <a href="https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-line-commands/">first part of decoding the SVG <code>path</code> pair</a>, we mostly dealt with converting things from semantic tags (<code>line</code>, <code>polyline</code>, <code>polygon</code>) into the <code>path</code> command syntax, but the <code>path</code> element didn’t really offer us any new shape options. This will change in this article as we’re learning how to draw <strong>curves</strong> and <strong>arcs</strong>, which just refer to parts of an ellipse.</p>

<h2 id="tl-dr-on-previous-articles">TL;DR On Previous Articles</h2>

<p>If this is your first meeting with this series, I recommend you familiarize yourself with the <a href="https://www.smashingmagazine.com/2024/09/svg-coding-examples-recipes-writing-vectors-by-hand/">basics of hand-coding SVG</a>, as well as <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/marker">how the <code>&lt;marker&gt;</code> works</a> and have a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/animate">basic understanding of animate</a>, as this guide doesn’t explain them. I also recommend knowing about the <code>M/m</code> command within the <code>&lt;path&gt;</code> <code>d</code> attribute (I wrote the aforementioned <a href="https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-line-commands/">article on path line commands</a> to help).</p>

<p><strong>Note</strong>: <em>This article will solely focus on the syntax of curve and arc commands and not offer an introduction to <code>path</code> as an element.</em></p>

<p>Before we get started, I want to do a quick recap of how I code SVG, which is by using JavaScript. I don’t like dealing with numbers and math, and reading SVG code that has numbers filled into every attribute makes me lose all understanding of it. By giving coordinates names and having all my math easy to parse and all written out, I have a much better time with this type of code, and I think you will, too.</p>

<p>As the goal of this article is about understanding <code>path</code> syntax and not about doing placement or how to leverage loops and other more basic things, I will not run you through the entire setup of each example. I’ll share some snippets of the code, but please note that it may be slightly adjusted from the CodePen or simplified to make the article easier to read. However, if there are specific questions about code not part of the text that’s in the CodePen demos &mdash; the comment section is open, as always.</p>

<p>To keep this all framework-agnostic, the code is written in vanilla JavaScript, though, in practice, TypeScript comes highly recommended when dealing with complex images.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="drawing-bézier-curves">Drawing Bézier Curves</h2>

<p>Being able to draw lines, polygons, polylines, and compounded versions of them is all fun and nice, but <code>path</code> can also do more than just offer more cryptic implementations of basic semantic SVG tags.</p>

<p>One of those additional types is Bézier curves.</p>

<p>There are multiple different curve commands. And this is where the idea of points and control points comes in.</p>

<blockquote><strong>Bézier math plotting is out of scope for this article.</strong><br />But, there is a visually gorgeous video by Freya Holmér called <a href="https://youtu.be/aVwxzDHniEw?si=WB_3i88VVJlZS6jf">The Beauty of Bézier Curves</a> which gets into the construction of cubic and quadratic bézier curves that features beautiful animation and the math becomes a lot easier to digest.</blockquote>

<p>Luckily, SVG allows us to draw quadratic curves with one control point and cubic curves with two control points without having to do any additional math.</p>

<p>So, what is a control point? A control point is the position of the handle that controls the curve. It is not a point that is drawn.</p>

<p>I found the best way to understand these path commands is to render them like a GUI, like Affinity and Illustrator would. Then, draw the “handles” and draw a few random curves with different properties, and see how they affect the curve. Seeing that animation also really helps to see the mechanics of these commands.</p>

<p>This is what I’ll be using markers and animation for in the following visuals. You will notice that the markers I use are rectangles and circles, and since they are connected to lines, I can make use of <code>marker</code> and then save myself a lot of animation time because these additional elements are rigged to the system. (And animating a single <code>d</code> command instead of <code>x</code> and <code>y</code> attributes separately makes the SVG code also much shorter.)</p>

<h3 id="quadratic-bézier-curves-q-t-commands">Quadratic Bézier Curves: <code>Q</code> &amp; <code>T</code> Commands</h3>

<p>The <code>Q</code> command is used to draw quadratic béziers. It takes two arguments: the control point and the end point.</p>

<p>So, for a simple curve, we would start with <code>M</code> to move to the start point, then <code>Q</code> to draw the curve.</p>

<div class="break-out">
<pre><code class="language-javascript">const path = `M${start.x} ${start.y} Q${control.x} ${control.y} ${end.x} ${end.y}`;
</code></pre>
</div>

<p>Since we have the Control Point, the Start Point, and the End Point, it’s actually quite simple to render the singular handle path like a graphics program would.</p>

<p>Funny enough, you probably have never interacted with a quadratic Bézier curve like with a cubic one in most common GUIs! Most of the common programs will convert this curve to a cubic curve with two handles and control points as soon as you want to play with it.</p>

<p>For the drawing, I created a couple of markers, and I’m drawing the handle in red to make it stand out a bit better.</p>

<p>I also stroked the main <code>path</code> with a gradient and gave it a crosshatch pattern fill. (We looked at <code>pattern</code> in <a href="https://www.smashingmagazine.com/2025/06/decoding-svg-path-element-line-commands/">my first article</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient"><code>linearGradient</code></a> is fairly similar. They’re both <code>def</code> elements you can refer to via <code>id</code>.) I like seeing the fill, but if you find it distracting, you can modify the variable for it.</p>

<p>I encourage you to look at the example with and without the rendering of the handle to see some of the nuance that happens around the points as the control points get closer to them.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="LEVXLoJ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Path Quadratic Bézier Curve Visual [forked]](https://codepen.io/smashingmag/pen/LEVXLoJ) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LEVXLoJ">SVG Path Quadratic Bézier Curve Visual [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<blockquote><strong>Quadratic Béziers are the “less-bendy” ones.</strong><br />These curves always remain somewhat related to “u” or “n” shapes and can’t be manipulated to be contorted. They can be squished, though.</blockquote>

<p>Connected Bézier curves are called “Splines”. And there is an additional command when chaining multiple quadratic curves, which is the <code>T</code> command.</p>

<p>The <code>T</code> command is used to draw a curve that is connected to the previous curve, so it always has to follow a <code>Q</code> command (or another <code>T</code> command). It only takes one argument, which is the endpoint of the curve.</p>

<div class="break-out">
<pre><code class="language-javascript">const path = `M${p1.x} ${p1.y} Q${cP.x} ${cP.y} ${p2.x} ${p2.y} T${p3.x} ${p3.y}`
</code></pre>
</div>

<p>The <code>T</code> command will actually use information about our control Point <code>cP</code> within the <code>Q</code> command.</p>

<p>To see how I created the following example. Notice that the inferred handles are drawn in green, while our specified controls are still rendered in red.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="vEOQJBM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Path Quadratic Curve T Command [forked]](https://codepen.io/smashingmag/pen/vEOQJBM) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/vEOQJBM">SVG Path Quadratic Curve T Command [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<p>OK, so the top curve takes two <code>Q</code> commands, which means, in total, there are three control points. Using a separate control point to create the scallop makes sense, but the third control point is just a reflection of the second control point through the preceding point.</p>

<p>This is what the <code>T</code> command does. It infers control points by reflecting them through the end point of the preceding <code>Q</code> (or <code>T</code>) command. You can see how the system all links up in the animation below, where all I’ve manipulated is the position of the main points and the first control points. The inferred control points follow along.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="WbvYENx"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Path Quadratic Bézier Spline T Command Visual [forked]](https://codepen.io/smashingmag/pen/WbvYENx) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/WbvYENx">SVG Path Quadratic Bézier Spline T Command Visual [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<p>The <code>q</code> and <code>t</code> commands also exist, so they will use relative coordinates.</p>

<p>Before I go on, if you do want to interact with a cubic curve, <a href="https://yqnn.github.io/svg-path-editor/">SVG Path Editor</a> allows you to edit all path commands very nicely.</p>

<h3 id="cubic-bézier-curves-c-and-s">Cubic Bézier Curves: <code>C</code> And <code>S</code></h3>

<p>Cubic Bézier curves work basically like quadratic ones, but instead of having one control point, they have two. This is probably the curve you are most familiar with.</p>

<p>The order is that you start with the first control point, then the second, and then the end point.</p>

<div class="break-out">
<pre><code class="language-javascript">const path = `M${p1.x} ${p1.y} C${cP1.x} ${cP1.y} ${cP2.x} ${cP2.y} ${p2.x} ${p2.y}`;
</code></pre>
</div>

<p>Let’s look at a visual to see it in action.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="EajOvaL"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Path Cubic Bézier Curve Animation [forked]](https://codepen.io/smashingmag/pen/EajOvaL) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/EajOvaL">SVG Path Cubic Bézier Curve Animation [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<blockquote><strong>Cubic Bézier curves are contortionists.</strong><br />Unlike the quadratic curve, this one can curl up and form loops and take on completely different shapes than any other SVG element. It can split the filled area into two parts, while the quadratic curve can not.</blockquote>

<p>Just like with the <code>T</code> command, a reflecting command is available for cubic curves <code>S</code>.</p>

<p>When using it, we get the first control point through the reflection, while we can define the new end control point and then the end point. Like before, this requires a spline, so at least one preceding <code>C</code> (or <code>S</code>) command.</p>

<pre><code class="language-javascript">const path = `    
  M ${p0.x} ${p0.y}
  C ${c0.x} ${c0.y} ${c1.x} ${c1.y} ${p1.x} ${p1.y}
  S ${c2.x} ${c2.y} ${p2.x} ${p2.y}
`;
</code></pre>

<p>I created a living visual for that as well.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="RNPqZPz"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Path Cubic Bézier Spline S Command Visual [forked]](https://codepen.io/smashingmag/pen/RNPqZPz) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/RNPqZPz">SVG Path Cubic Bézier Spline S Command Visual [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<blockquote><strong>When to use <code>T</code> and <code>S</code>:</strong><br />The big advantage of using these chaining reflecting commands is if you want to draw waves or just absolutely ensure that your spline connection is smooth.</blockquote>

<p>If you can’t use a reflection but want to have a nice, smooth connection, make sure your control points form a straight line. If you have a kink in the handles, your spline will get one, too.</p>

<div class="partners__lead-place"></div>

<h2 id="arcs-a-command">Arcs: <code>A</code> Command</h2>

<p>Finally, the last type of <code>path</code> command is to create arcs. Arcs are sections of circles or ellipses.</p>

<p>It’s my least favorite command because there are so many elements to it. But it is the secret to drawing a proper donut chart, so I have a bit of time spent with it under my belt.</p>

<p>Let’s look at it.</p>

<p>Like with any other <code>path</code> command, lowercase implies relative coordinates. So, just as there is an <code>A</code> command, there’s also an <code>a</code>.</p>

<p>So, an arc path looks like this:</p>

<div class="break-out">
<pre><code class="language-javascript">const path = `M${start.x} ${start.y} A${radius.x} ${radius.y} ${xAxisRotation} ${largeArcFlag} ${sweepFlag} ${end.x} ${end.y}`;
</code></pre>
</div>

<p>And what the heck are <code>xAxisRotation</code>, <code>largeArcFlag</code>, and <code>sweepFlag</code> supposed to be? In short:</p>

<ul>
<li><code>xAxisRotation</code> is the rotation of the underlying ellipse’s axes in degrees.</li>
<li><code>largeArcFlag</code> is a boolean value that determines if the arc is greater than 180°.</li>
<li><code>sweepFlag</code> is also a boolean and determines the arc direction, so does it go clockwise or counter-clockwise?</li>
</ul>

<p>To better understand these concepts, I created this visual.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="GgJwvZR"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [SVG Path Arc Command Visuals [forked]](https://codepen.io/smashingmag/pen/GgJwvZR) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/GgJwvZR">SVG Path Arc Command Visuals [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<h3 id="radius-size">Radius Size</h3>

<p>You’ll notice in that CodePen that there are ellipses drawn for each command. In the top row, they are overlapping, while in the bottom row, they are stacked up. Both rows actually use the same <code>radius.x</code>  and <code>radius.y</code> values in their arc definitions, while the distance between the start and end points increases for the second row.</p>

<p>The reason why the stacking happens is that the radius size is only taken into consideration if the start and end points fit within the specified ellipse. That behavior surprised me, and thus, I dug into the specs and found the following information on how the arc works:</p>

<blockquote>“Arbitrary numerical values are permitted for all elliptical arc parameters (other than the boolean flags), but user agents must make the following adjustments for invalid values when rendering curves or calculating their geometry:<br /><br />If the endpoint (<strong>x</strong>, <strong>y</strong>) of the segment is identical to the current point (e.g., the endpoint of the previous segment), then this is equivalent to omitting the elliptical arc segment entirely.<br /><br />If either <strong>rx</strong> or <strong>ry</strong> is 0, then this arc is treated as a straight line segment (a “lineto”) joining the endpoints.<br /><br />If either <strong>rx</strong> or <strong>ry</strong> have negative signs, these are dropped; the absolute value is used instead.<br /><br />If <strong>rx</strong>, <strong>ry</strong> and <strong>x-axis-rotation</strong> are such that there is no solution (basically, the ellipse is not big enough to reach from the current point to the new endpoint) then the ellipse is scaled up uniformly until there is exactly one solution (until the ellipse is just big enough).<br /><br />See the appendix section <a href="https://svgwg.org/svg2-draft/implnote.html#ArcCorrectionOutOfRangeRadii">Correction of out-of-range radii</a> for the mathematical formula for this scaling operation.”<br /><br />&mdash; <a href="https://svgwg.org/svg2-draft/paths.html#ArcOutOfRangeParameters">9.5.1 Out-of-range elliptical arc parameters</a></blockquote>

<p>So, really, that stacking is just nice and graceful error-handling and not how it was intended. Because the top row is how arcs should be used.</p>

<blockquote>When plugging in logical values, the underlying ellipses and the two points give us four drawing options for how we could connect the two points along an elliptical path. That’s what the boolean values are for.</blockquote>

<h3 id="xaxisrotation"><code>xAxisRotation</code></h3>

<p>Before we get to the booleans, the crosshatch pattern shows the <code>xAxisrotation</code>. The ellipse is rotated around its center, with the degree value being in relation to the x-direction of the SVG.</p>

<p>So, if you work with a circular ellipse, the rotation won’t have any effect on the arc (except if you use it in a pattern like I did there).</p>

<h3 id="sweep-flag">Sweep Flag</h3>

<p>Notice the little arrow marker to show the arc drawing direction. If the value is 0, the arc is drawn clockwise. If the value is 1, the arc is drawn counterclockwise.</p>

<h3 id="large-arc-flag">Large Arc Flag</h3>

<p>The large Arc Flag tells the path if you want the smaller or the larger arc from the ellipse. If we have a scaled case, we get exactly 180° of our ellipse.</p>

<blockquote>Arcs usually require a lot more annoying circular number-wrangling than I am happy doing (As soon as radians come to play, I tend to spiral into rabbit holes where I have to relearn too much math I happily forget.)<br /><br />They are more reliant on values being related to each other for the outcome to be as expected and there’s just so much information going in.<br /><br />But &mdash; and that’s a bit but &mdash; arcs are wonderfully powerful!</blockquote>

<div class="partners__lead-place"></div>

<h2 id="conclusion">Conclusion</h2>

<p>Alright, that was a lot! However, I do hope that you are starting to see how <code>path</code> commands can be helpful. I find them extremely useful to illustrate data.</p>

<p>Once you know how easy it is to set up stuff like grids, boxes, and curves, it doesn’t take many more steps to create visualizations that are a bit more unique than what the standard data visualization libraries offer.</p>

<blockquote>With everything you’ve learned in this series of articles, you’re basically fully equipped to render all different types of charts &mdash; or other types of visualizations.</blockquote>

<p>Like, how about visualizing the underlying cubic-bezier of something like <code>transition-timing-function: ease;</code> in CSS? That’s the thing I made to figure out how I could turn those transition-timing-functions into something an <code>&lt;animate&gt;</code> tag understands.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="gbpQxgp"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [CSS Cubic Beziers as SVG Animations &amp; CSS Transition Comparisons [forked]](https://codepen.io/smashingmag/pen/gbpQxgp) by <a href="https://codepen.io/halfapx">Myriam</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/gbpQxgp">CSS Cubic Beziers as SVG Animations &amp; CSS Transition Comparisons [forked]</a> by <a href="https://codepen.io/halfapx">Myriam</a>.</figcaption>
</figure>

<p>SVG is fun and quirky, and the <code>path</code> element may be the holder of the most overwhelming string of symbols you’ve ever laid eyes on during code inspection. However, if you take the time to understand the underlying logic, it all transforms into one beautifully simple and extremely powerful syntax.</p>

<p>I hope with this pair of <code>path</code> decoding articles, I managed to expose the underlying mechanics of how path plots work. If you want even more resources that don’t require you to dive through specs, try the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths">MDN tutorial about paths</a>. It’s short and compact, and was the main resource for me to learn all of this.</p>

<p>However, since I wrote my deep dive on the topic, I stumbled into the beautiful <a href="https://svg-tutorial.com">svg-tutorial.com</a>, which does a wonderful job visualizing SVG coding as a whole but mostly features my favorite arc visual of them all in the <a href="https://svg-tutorial.com/editor/arc">Arc Editor</a>. And if you have a path that you’d like properly decoded without having to store all of the information in these two articles, there’s <a href="https://svg-path-visualizer.netlify.app/">SVG Path Visualizer</a>, which breaks down path information super nicely.</p>

<p>And now: Go forth and have fun playing in the matrix.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Victor Ayomipo</author><title>CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control</title><link>https://www.smashingmagazine.com/2025/06/css-cascade-layers-bem-utility-classes-specificity-control/</link><pubDate>Thu, 19 Jun 2025 15:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/06/css-cascade-layers-bem-utility-classes-specificity-control/</guid><description>CSS can be unpredictable — and specificity is often the culprit. Victor Ayomipo breaks down how and why your styles might not behave as expected, and why understanding specificity is better than relying on &lt;code>!important&lt;/code> flags.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/06/css-cascade-layers-bem-utility-classes-specificity-control/" />
              <title>CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control</h1>
                  
                    
                    <address>Victor Ayomipo</address>
                  
                  <time datetime="2025-06-19T15:00:00&#43;00:00" class="op-published">2025-06-19T15:00:00+00:00</time>
                  <time datetime="2025-06-19T15:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>CSS is wild, really wild. And tricky. But let’s talk specifically about <strong>specificity</strong>.</p>

<p>When writing CSS, it’s close to impossible that you haven’t faced the frustration of styles not applying as expected &mdash; that’s specificity. You applied a style, it worked, and later, you try to override it with a different style and… nothing, it just ignores you. Again, specificity.</p>

<p>Sure, there’s the option of resorting to <code>!important</code> flags, but like all developers before us, it’s always <a href="https://cssguidelin.es/#important">risky and discouraged</a>. It’s way better to fully understand specificity than go down that route because otherwise you wind up fighting your own important styles.</p>

<h2 id="specificity-101">Specificity 101</h2>

<p>Lots of developers understand the concept of specificity in different ways.</p>

<blockquote>The core idea of specificity is that the CSS Cascade algorithm used by browsers determines which style declaration is applied when two or more rules match the same element.</blockquote>

<p>Think about it. As a project expands, so do the specificity challenges. Let’s say Developer A adds <code>.cart-button</code>, then maybe the button style looks good to be used on the sidebar, but with a little tweak. Then, later, Developer B adds <code>.cart-button .sidebar</code>, and from there, any future changes applied to <code>.cart-button</code> might get overridden by <code>.cart-button .sidebar</code>, and just like that, the specificity war begins.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png"
			
			sizes="100vw"
			alt="Specifity tension represented by a pile of different elements"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I’ve written CSS long enough to witness different strategies that developers have used to manage the specificity battles that come with CSS.</p>

<pre><code class="language-css">/&#42; Traditional approach &#42;/
#header .nav li a.active { color: blue; }

/&#42; BEM approach &#42;/
.header__nav-item--active { color: blue; }

/&#42; Utility classes approach &#42;/
.text-blue { color: blue; }

/&#42; Cascade Layers approach &#42;/
@layer components {
  .nav-link.active { color: blue; }
}
</code></pre>

<p>All these methods reflect different strategies on how to control or at least maintain CSS specificity:</p>

<ul>
<li><strong>BEM</strong>: tries to simplify specificity by being explicit.</li>
<li><strong>Utility-first CSS</strong>: tries to bypass specificity by keeping it all atomic.</li>
<li><strong>CSS Cascade Layers</strong>: manage specificity by organizing styles in layered groups.</li>
</ul>

<p>We’re going to put all three side by side and look at how they handle specificity.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png"
			
			sizes="100vw"
			alt="A chart which ilustrates different strategies on how to control or at least maintain CSS specificity"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="my-relationship-with-specificity">My Relationship With Specificity</h2>

<p>I actually used to think that I got the whole picture of CSS specificity. Like the usual inline greater than ID greater than class greater than tag. But, reading <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Cascade">the MDN docs on how the CSS Cascade truly works</a> was an eye-opener.</p>

<p>There’s a code I worked on in an old codebase provided by a client, which looked something like this:</p>

<pre><code class="language-css">/&#42; Legacy code &#42;/
&#35;main-content .product-grid button.add-to-cart {
  background-color: #3a86ff;
  color: white;
  padding: 10px 15px;
  border-radius: 4px;
}

/&#42; 100 lines of other code here &#42;/

/&#42; My new CSS &#42;/
.btn-primary {
  background-color: #4361ee; /&#42; New brand color &#42;/
  color: white;
  padding: 12px 20px;
  border-radius: 4px;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
</code></pre>

<p>Looking at this code, no way that the <code>.btn-primary</code> class stands a chance against whatever specificity chain of selectors was previously written. As far as specification goes, CSS gives the first selector a specificity score of <code>1, 2, 1</code>: one point for the ID, two points for the two classes, and one point for the element selector. Meanwhile, the second selector is scored as <code>0, 1, 0</code> since it only consists of a single class selector.</p>

<p>Sure, I had some options:</p>

<ul>
<li>I could <strong>use <code>!important</code> on the properties</strong> in <code>.btn-primary</code> to override the ones declared in the stronger selector, but the moment that happens, be prepared to use it everywhere. So, I’d rather avoid it.</li>
<li>I could try <strong>going more specific</strong>, but personally, that’s just being cruel to the next developer (who might even be me).</li>
<li>I could <strong>change the styles of the existing code</strong>, but that’s adding to the specificity problem:</li>
</ul>

<pre><code class="language-css">&#35;main-content .product-grid .btn-primary {
  /&#42; edit styles directly &#42;/
}
</code></pre>

<p>Eventually, I ended up writing the whole CSS from scratch.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png"
			
			sizes="100vw"
			alt="Legacy button vs modern button"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>When <em>nesting</em> was introduced, I tried it to control specificity that way:</p>

<div class="break-out">
<pre><code class="language-css">.profile-widget {
  // ... other styles
  .header {
    // ... header styles
    .user-avatar {
      border: 2px solid blue;
      &.is-admin {
        border-color: gold; // This becomes .profile-widget .header .user-avatar.is-admin
      }
    }
  }
}
</code></pre>
</div>

<p>And just like that, I have unintentionally created high-specificity rules. That’s how easily and naturally we can drift toward specificity complexities.</p>

<p>So, to save myself a lot of these issues, I have one principle I always abide by: <a href="https://css-tricks.com/strategies-keeping-css-specificity-low/"><strong>keep specificity as low as possible</strong></a>. And if the selector complexity is becoming a complex chain, I rethink the whole thing.</p>

<div class="partners__lead-place"></div>

<h2 id="bem-the-og-system">BEM: The OG System</h2>

<p>The Block-Element-Modifier (BEM, for short) has been around the block (pun intended) for a long time. It is a methodological system for writing CSS that forces you to make every style hierarchy explicit.</p>

<pre><code class="language-css">/&#42; Block &#42;/
.panel {}

/&#42; Element that depends on the Block &#42;/
.panel&#95;&#95;header {}
.panel&#95;&#95;content {}
.panel&#95;&#95;footer {}

/&#42; Modifier that changes the style of the Block &#42;/
.panel--highlighted {}
.panel&#95;&#95;button--secondary {}
</code></pre>

<p>When I first experienced BEM, I thought it was amazing, despite contrary opinions that it looked ugly. I had no problems with the double hyphens or underscores because they made my CSS predictable and simplified.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png"
			
			sizes="100vw"
			alt="Illustration for BEM methodological system"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="how-bem-handles-specificity">How BEM Handles Specificity</h3>

<p>Take a look at these examples. Without BEM:</p>

<pre><code class="language-css">/&#42; Specificity: 0, 3, 0 &#42;/
.site-header .main-nav .nav-link {
  color: &#35;472EFE;
  text-decoration: none;
}

/&#42; Specificity: 0, 2, 0 &#42;/
.nav-link.special {
  color: &#35;FF5733;
}
</code></pre>

<p>With BEM:</p>

<pre><code class="language-css">/&#42; Specificity: 0, 1, 0 &#42;/
.main-nav&#95;&#95;link {
  color: &#35;472EFE;
  text-decoration: none;
}

/&#42; Specificity: 0, 1, 0 &#42;/
.main-nav&#95;&#95;link--special {
  color: &#35;FF5733;
}
</code></pre>

<p>You see how BEM makes the code look predictable as all selectors are created equal, thus making the code easier to maintain and extend. And if I want to add a button to <code>.main-nav</code>, I just add <code>.main-nav__btn</code>, and if I need a disabled button (modifier), <code>.main-nav__btn--disabled</code>. Specificity is low, as I don’t have to increase it or fight the cascade; I just write a new class.</p>

<p>BEM’s naming principle made sure components lived in isolation, which, for a part of CSS, the specificity part, it worked, i.e, <code>.card__title</code> class will never accidentally clash with a <code>.menu__title</code> class.</p>

<h3 id="where-bem-falls-short">Where BEM Falls Short</h3>

<p>I like the idea of BEM, but it is not perfect, and a lot of people noticed it:</p>

<ul>
<li>The class names can get <em>really</em> long.</li>
</ul>

<div class="break-out">
<pre><code class="language-html">&lt;div class="product-carousel&#95;&#95;slide--featured product-carousel&#95;&#95;slide--on-sale"&gt;
  &lt;!-- yikes --&gt;
&lt;/div&gt;
</code></pre>
</div>

<ul>
<li><strong>Reusability might not be prioritized</strong>, which somewhat contradicts the native CSS ideology. Should a button inside a card be <code>.card__button</code> or reuse a global <code>.button</code> class? With the former, styles are being duplicated, and with the latter, the BEM strict model is being broken.</li>
<li>One of the core pains in software development starts becoming a reality &mdash; <strong>naming things</strong>. <a href="https://css-tricks.com/naming-things-is-only-getting-harder/">I’m sure you know the frustration of that already.</a></li>
</ul>

<p>BEM is good, but sometimes you may need to be flexible with it. A <strong>hybrid system</strong> (maybe using BEM for core components but simpler classes elsewhere) can still keep specificity as low as needed.</p>

<pre><code class="language-css">/&#42; Base button without BEM &#42;/
.button {
  /&#42; Button styles &#42;/
}

/&#42; Component-specific button with BEM &#42;/
.card&#95;&#95;footer .button {
  /&#42; Minor overrides &#42;/
}
</code></pre>

<h2 id="utility-classes-specificity-by-avoidance">Utility Classes: Specificity By Avoidance</h2>

<p>This is also called <a href="https://css-tricks.com/lets-define-exactly-atomic-css/">Atomic CSS</a>. And in its entirety, it <em>avoids specificity.</em></p>

<div class="break-out">
<pre><code class="language-html">&lt;button class="bg-red-300 hover:bg-red-500 text-white py-2 px-4 rounded"&gt;
  A button
&lt;/button&gt;
</code></pre>
</div>

<blockquote>The idea behind utility-first classes is that every utility class has the same specificity, which is one class selector. Each class is a tiny CSS property with a single purpose.</blockquote>

<p><code>p-2</code>? Padding, nothing more. <code>text-red</code>? Color red for text. <code>text-center</code>? Text alignment. It’s like how LEGOs work, but for styling. You stack classes on top of each other until you get your desired appearance.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png"
			
			sizes="100vw"
			alt="An illustration with a title: Avoiding specifity - one utility at a time"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="how-utility-classes-handle-specificity">How Utility Classes Handle Specificity</h3>

<p>Utility classes do not solve specificity, but rather, they take the BEM ideology of low specificity to the extreme. Almost all utility classes have the same lowest possible specificity level of (<code>0</code>, <code>1</code>, <code>0</code>). And because of this, overrides become easy; if more padding is needed, bump <code>.p-2</code> to <code>.p-4</code>.</p>

<p>Another example:</p>

<pre><code class="language-html">&lt;button class="bg-orange-300 hover:bg-orange-700"&gt;
  This can be hovered
&lt;/button&gt;
</code></pre>

<p>If another class, <code>hover:bg-red-500</code>, is added, the order matters for CSS to determine which to use. So, even though the utility classes avoid specificity, the other parts of the CSS Cascade come in, which is the order of appearance, with the last matching selector declared being the winner.</p>

<h3 id="utility-class-trade-offs">Utility Class Trade-Offs</h3>

<p>The most common issue with utility classes is that <strong>they make the code look ugly</strong>. And frankly, I agree. But being able to picture what a component looks like without seeing it rendered is just priceless.</p>

<p>There’s also the argument of reusability, that you repeat yourself every single time. But once one finds a repetition happening, just turn that part into a reusable component. It also has its genuine <strong>limitations</strong> when it comes to specificity:</p>

<ul>
<li>If your brand color changes, which is a global change, and you’re deep in the codebase, you can’t just change one and have others follow like native CSS.</li>
<li>The parent-child relationship that happens naturally in native CSS is out the window due to how atomic utility classes behave.</li>
<li>Some argue the HTML part should be left as markup and the CSS part for styling. Because now, there’s more markup to scan, and if you decide to clean up:</li>
</ul>

<div class="break-out">
<pre><code class="language-html">&lt;!-- Too long --&gt;
&lt;div class="p-4 bg-yellow-100 border border-yellow-300 text-yellow-800 rounded"&gt;

&lt;!-- Better? --&gt;
&lt;div class="alert-warning"&gt;
</code></pre>
</div>

<p>Just like that, we’ve ended up writing CSS. Circle of life.</p>

<p>In my experience with utility classes, they work best for:</p>

<ul>
<li><strong>Speed</strong><br />
Writing the markup, styling it, and seeing the result swiftly.</li>
<li><strong>Predictability</strong><br />
A utility class does exactly what it says it does.</li>
</ul>

<h2 id="cascade-layers-specificity-by-design">Cascade Layers: Specificity By Design</h2>

<p>Now, this is where it gets interesting. BEM offers structure, utility classes gain speed, and CSS Cascade Layers give us something paramount: <strong>control</strong>.</p>

<p>Anyways, Cascade Layers (<code>@layers</code>) groups styles and declares what order the groups should be, regardless of the specificity scores of those rules.</p>

<p>Looking at a set of independent rulesets:</p>

<pre><code class="language-css">button {
  background-color: orange; /&#42; Specificity: 0, 0, 1 &#42;/
}

.button {
  background-color: blue; //&#42; Specificity: 0, 1, 0&#42;/
}

#button {
  background-color: red; /&#42; Specificity: 1, 0, 0 &#42;/
}

/&#42; No matter what, the button is red &#42;/
</code></pre>

<p>But with <code>@layer</code>, let’s say, I want to prioritize the <code>.button</code> class selector. I can shape how the specificity order should go:</p>

<pre><code class="language-css">@layer utilities, defaults, components;

@layer defaults {
  button {
    background-color: orange; /&#42; Specificity: 0, 0, 1 &#42;/
  }
}

@layer components {
  .button {
    background-color: blue; //&#42; Specificity: 0, 1, 0&#42;/
  }
}

@layer utilities {
  #button {
    background-color: red; /&#42; Specificity: 1, 0, 0 &#42;/
  }
}
</code></pre>

<p>Due to how <code>@layer</code> works, <code>.button</code> would win because the <code>components</code> layer is the highest priority, even though <code>#button</code> has higher specificity. Thus, before CSS could even check the usual specificity rules, the layer order would first be respected.</p>

<p>You just have to respect the folks over at W3C, because now one can purposely override an ID selector with a simple class, without even using <code>!important</code>. Fascinating.</p>

<h3 id="cascade-layers-nuances">Cascade Layers Nuances</h3>

<p>Here are some things that are worth calling out when we’re talking about CSS Cascade Layers:</p>

<ul>
<li>Specificity is still part of the game.</li>
<li><code>!important</code> acts differently than expected in <code>@layer</code> (they work in reverse!).</li>
<li><code>@layers</code> aren’t selector-specific but rather style-property-specific.</li>
</ul>

<div class="break-out">
<pre><code class="language-css">@layer base {
  .button {
    background-color: blue;
    color: white;
  }
}

@layer theme {
  .button {
    background-color: red;
    /&#42; No color property here, so white from base layer still applies &#42;/
  }
}
</code></pre>
</div>

<ul>
<li><code>@layer</code> can easily be abused. I’m sure there’s a developer out there with over 20+ layer declarations that’s grown into a monstrosity.</li>
</ul>

<h3 id="comparing-all-three">Comparing All Three</h3>

<p>Now, for the TL;DR folks out there, here’s a side-by-side comparison of the three: BEM, utility classes, and CSS Cascade Layers.</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>Feature</th>
            <th>BEM</th>
      <th>Utility Classes</th>
      <th>Cascade Layers</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Core Idea</td>
            <td>Namespace components</td>
      <td>Single purpose classes</td>
      <td>Control cascade order</td>
        </tr>
        <tr>
            <td>Specificity Control</td>
            <td>Low and flat</td>
            <td>Avoids entirely</td>
            <td>Absolute control due to Layer supremacy</td>
        </tr>
        <tr>
            <td>Code Readability</td>
            <td>Clear structure due to naming</td>
            <td>Unclear if unfamiliar with the class names</td>
            <td>Clear if layer structure is followed</td>
        </tr>
        <tr>
            <td>HTML Verbosity</td>
            <td>Moderate class names (can get long)</td>
            <td>Many small classes that adds up quickly</td>
            <td>No direct impact, stays only in CSS</td>
        </tr>
        <tr>
            <td>CSS Organization</td>
            <td>By component</td>
            <td>By property</td>
            <td>By priority order</td>
        </tr>
        <tr>
            <td>Learning Curve</td>
            <td>Requires understanding conventions</td>
            <td>Requires knowing the utility names</td>
            <td>Easy to pick up, but requires a deep understanding of CSS</td>
        </tr>
        <tr>
            <td>Tools Dependency</td>
            <td>Pure CSS</td>
            <td>Often depends of third-party e.g Tailwind</td>
            <td>Native CSS</td>
        </tr>
        <tr>
            <td>Refactoring Ease</td>
            <td>High</td>
            <td>Medium</td>
            <td>Low</td>
        </tr>
        <tr>
            <td>Best Use Case</td>
            <td>Design Systems</td>
            <td>Fast builds</td>
            <td>Legacy code or third-party codes that need overrides</td>
        </tr>
        <tr>
            <td>Browser Support</td>
            <td>All</td>
            <td>All</td>
            <td>All (except IE)</td>
        </tr>
    </tbody>
</table>

<p>Among the three, each has its sweet spot:</p>

<ul>
<li><strong>BEM</strong> is best when:

<ul>
<li>There’s a clear design system that needs to be consistent,</li>
<li>There’s a team with different philosophies about CSS (BEM can be the middle ground), and</li>
<li>Styles are less likely to leak between components.</li>
</ul></li>
<li><strong>Utility classes</strong> work best when:

<ul>
<li>You need to build fast, like prototypes or MVPs, and</li>
<li>Using a component-based JavaScript framework like React.</li>
</ul></li>
<li><strong>Cascade Layers</strong> are most effective when:

<ul>
<li>Working on legacy codebases where you need full specificity control,</li>
<li>You need to integrate third-party libraries or styles from different sources, and</li>
<li>Working on a large, complex application or projects with long-term maintenance.</li>
</ul></li>
</ul>

<p>If I had to choose or rank them, I’d go for utility classes with Cascade Layers over using BEM. But that’s just me!</p>

<div class="partners__lead-place"></div>

<h2 id="where-they-intersect-how-they-can-work-together">Where They Intersect (How They Can Work Together)</h2>

<p>Among the three, Cascade Layers should be seen as an orchestrator, as it can work with the other two strategies. <code>@layer</code> is a fundamental tenet of the CSS Cascade’s architecture, unlike BEM and utility classes, which are methodologies for controlling the Cascade’s behavior.</p>

<pre><code class="language-css">/&#42; Cascade Layers + BEM &#42;/
@layer components {
  .card&#95;&#95;title {
    font-size: 1.5rem;
    font-weight: bold;
  }
}

/&#42; Cascade Layers + Utility Classes &#42;/
@layer utilities {
  .text-xl {
    font-size: 1.25rem;
  }
  .font-bold {
    font-weight: 700;
  }
}
</code></pre>

<p>On the other hand, using BEM with utility classes would just end up clashing:</p>

<div class="break-out">
<pre><code class="language-javascript">&lt;!-- This feels wrong --&gt;
&lt;div class="card__container p-4 flex items-center"&gt;
  &lt;p class="card__title text-xl font-bold"&gt;Something seems wrong&lt;/p&gt;
&lt;/div&gt;
</code></pre>
</div>

<p>I’m putting all my cards on the table: I’m a utility-first developer. And most utility class frameworks use <code>@layer</code> behind the scenes (e.g., <a href="https://tailwindcss.com/blog/tailwindcss-v4#designed-for-the-modern-web">Tailwind</a>). So, those two are already together in the bag.</p>

<p>But, do I dislike BEM? Not at all! I’ve used it a lot and still would, if necessary. I just find naming things to be an exhausting exercise.</p>

<p>That said, we’re all different, and you might have opposing thoughts about what you think feels best. It truly doesn’t matter, and that’s the beauty of this web development space. <em>Multiple routes can lead to the same destination</em>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So, when it comes to comparing BEM, utility classes, and CSS Cascade Layers, is there a true “winning” approach for controlling specificity in the Cascade?</p>

<p>First of all, CSS Cascade Layers are arguably the most powerful CSS feature that we’ve gotten in years. They shouldn’t be confused with BEM or utility classes, which are strategies rather than part of the CSS feature set.</p>

<p>That’s why I like the idea of combining either BEM with Cascade Layers or utility classes with Cascade Layers. Either way, the idea is to <strong>keep specificity low and leverage Cascade Layers to set priorities on those styles</strong>.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 4: Optimising SVGs</title><link>https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/</link><pubDate>Wed, 04 Jun 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/</guid><description>What’s the best way to make your SVGs faster, simpler, and more manageable? In this article, pioneering author and web designer &lt;a href="https://stuffandnonsense.co.uk/">Andy Clarke&lt;/a> explains the process he relies on &lt;em>to&lt;/em> prepare, optimise, and structure SVGs for animation and beyond.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/06/smashing-animations-part-4-optimising-svgs/" />
              <title>Smashing Animations Part 4: Optimising SVGs</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 4: Optimising SVGs</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-06-04T08:00:00&#43;00:00" class="op-published">2025-06-04T08:00:00+00:00</time>
                  <time datetime="2025-06-04T08:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>SVG animations take me back to the Hanna-Barbera cartoons I watched as a kid. Shows like <em>Wacky Races</em>, <em>The Perils of Penelope Pitstop</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear"><em>Yogi Bear</em></a>. They inspired me to lovingly recreate some classic <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a> using CSS, SVG, and SMIL animations.</p>

<p>But getting animations to load quickly and work smoothly needs more than nostalgia. It takes clean design, lean code, and a process that makes complex SVGs easier to animate. Here’s how I do it.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://stuffandnonsense.co.uk/toon-titles">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png"
			
			sizes="100vw"
			alt="An example of Toon Titles from the website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      There’s now a website where you can see all my <a href='https://stuffandnonsense.co.uk/toon-titles'>Toon Titles</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="refs">
  <ul><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</a></li></ul>
</div>

<p>Whether for personal projects or commercial work, preparing SVGs well ensures they’re accessible. Optimising them ensures they load quickly, especially on mobile, and thinking carefully about how they’re structured makes maintaining them easier. I’ve developed a <strong>process that balances visuals with accessibility and performance</strong> and makes complex SVGs easier to work with.</p>

<p>So, to explain my process, I’ve chosen an episode of <em>The Yogi Bear Show</em> called “Bewitched Bear,” first broadcast in January 1960. In this story, Yogi steals a witch’s broom to help him grab “pic-a-nic” baskets.</p>

<p>“Hey, hey, hey!”</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png"
			
			sizes="100vw"
			alt="An illustration from the “Bewitched Bear” episode of The Yogi Bear Show where bear is on a witch’s broom"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="start-clean-and-design-with-optimisation-in-mind">Start Clean And Design With Optimisation In Mind</h2>

<p>Keeping things simple is key to making SVGs that are optimised and ready to animate. Tools like Adobe Illustrator convert bitmap images to vectors, but the output often contains too many extraneous groups, layers, and masks. Instead, I start cleaning in Sketch, work from a reference image, and use the Pen tool to create paths.</p>

<blockquote><strong>Tip</strong>: <a href="https://affinity.serif.com/en-gb/designer/">Affinity Designer</a> (UK) and <a href="https://www.sketch.com">Sketch</a> (Netherlands) are alternatives to Adobe Illustrator and Figma. Both are independent and based in Europe. Sketch has been my default design app since Adobe killed Fireworks.</blockquote>

<h2 id="beginning-with-outlines">Beginning With Outlines</h2>

<p>For these Toon Titles illustrations, I first use the Pen tool to draw black outlines with as few anchor points as possible. The more points a shape has, the bigger a file becomes, so simplifying paths and reducing the number of points makes an SVG much smaller, often with no discernible visual difference.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png"
			
			sizes="100vw"
			alt="Two outlines with different anchor points"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <strong>Left</strong>: 160 anchor points. <strong>Right</strong>: 80 points. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Bearing in mind that parts of this Yogi illustration will ultimately be animated, I keep outlines for this Bewitched Bear’s body, head, collar, and tie separate so that I can move them independently. The head might nod, the tie could flap, and, like in those classic cartoons, Yogi’s collar will hide the joins between them.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png"
			
			sizes="100vw"
			alt="Separate outlines for body, head, collar and tie, and broom."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Separate outlines for body, head, collar and tie, and broom. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="drawing-simple-background-shapes">Drawing Simple Background Shapes</h2>

<p>With the outlines in place, I use the Pen tool again to draw new shapes, which fill the areas with colour. These colours sit behind the outlines, so they don’t need to match them exactly. The fewer anchor points, the smaller the file size.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png"
			
			sizes="100vw"
			alt="Original vector artwork and a simplified version with Adobe Illustrator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <strong>Left</strong>: Original vector artwork, 8 Kb. <strong>Right</strong>: Simplified using Adobe Illustrator, 2 Kb. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Sadly, neither Affinity Designer nor Sketch has tools that can simplify paths, but if you have it, using Adobe Illustrator can shave a few extra kilobytes off these background shapes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png"
			
			sizes="100vw"
			alt="An illustration how to simplify paths with Adobe Illustrator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Adobe Illustrator: Object → Path → Simplify. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="optimising-the-code">Optimising The Code</h2>

<p>It’s not just metadata that makes SVG bulkier. The way you export from your design app also affects file size.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png"
			
			sizes="100vw"
			alt="Vector artwork ready for optimisation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Vector artwork ready for optimisation. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Exporting just those simple background shapes from Adobe Illustrator includes unnecessary groups, masks, and bloated path data by default. Sketch’s code is barely any better, and there’s plenty of room for improvement, even in its SVGO Compressor code. I rely on Jake Archibald’s <a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a>, which uses SVGO v3 and consistently delivers the best optimised SVGs.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="439"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png"
			
			sizes="100vw"
			alt="Jake Archibald’s SVGOMG online optimisation tool."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Jake Archibald’s SVGOMG online optimisation tool. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div class="partners__lead-place"></div>

<h2 id="layering-svg-elements">Layering SVG Elements</h2>

<p>My process for preparing SVGs for animation goes well beyond drawing vectors and optimising paths &mdash; it also includes how I <strong>structure the code</strong> itself. When every visual element is crammed into a single SVG file, even optimised code can be a nightmare to navigate. Locating a specific path or group often feels like searching for a needle in a haystack.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png"
			
			sizes="100vw"
			alt="Toon Titles recreation of the Yogi Bear title card"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Toon Titles recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time &mdash; always in the order they’ll appear in the final file. This lets me build the master SVG gradually by pasting it in each cleaned-up section. For example, I start with backgrounds like this gradient and title graphic.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png"
			
			sizes="100vw"
			alt="Gradient background and title graphic."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Gradient background and title graphic. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Instead of facing a wall of SVG code, I can now easily identify the background gradient’s path and its associated <code>linearGradient</code>, and see the group containing the title graphic. I take this opportunity to add a comment to the code, which will make editing and adding animations to it easier in the future:</p>

<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;defs&gt;
    &lt;!-- ... --&gt;
  &lt;/defs&gt;
  &lt;path fill="url(#grad)" d="…"/&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
  &lt;g&gt;
    &lt;path … /&gt;
    &lt;!-- ... --&gt; 
  &lt;/g&gt;
&lt;/svg&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png"
			
			sizes="100vw"
			alt="Trail with Gaussian Blur."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Trail with Gaussian Blur. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Next, I add the blurred trail from Yogi’s airborne broom. This includes defining a Gaussian Blur filter and placing its path between the background and title layers:</p>

<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;defs&gt;
    &lt;linearGradient id="grad" …&gt;…&lt;/linearGradient&gt;
    &lt;filter id="trail" …&gt;…&lt;/filter&gt;
  &lt;/defs&gt;
  &lt;!-- GRADIENT --&gt;
  &lt;!-- TRAIL --&gt;
  &lt;path filter="url(#trail)" …/&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
&lt;/svg&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png"
			
			sizes="100vw"
			alt="Yogi Bear’s magical stars."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear’s magical stars. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then come the magical stars, added in the same sequential fashion:</p>

<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;!-- GRADIENT --&gt;
  &lt;!-- TRAIL --&gt;
  &lt;!-- STARS --&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
&lt;/svg&gt;
</code></pre>

<p>To keep everything organised and animation-ready, I create an empty group that will hold all the parts of Yogi:</p>

<pre><code class="language-svg">&lt;g id="yogi"&gt;...&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png"
			
			sizes="100vw"
			alt="Added Yogi Bear’s component parts"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Sequentially adding Yogi Bear’s component parts. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then I build Yogi from the ground up &mdash; starting with background props, like his broom:</p>

<pre><code class="language-svg">&lt;g id="broom"&gt;...&lt;/g&gt;
</code></pre>

<p>Followed by grouped elements for his body, head, collar, and tie:</p>

<pre><code class="language-svg">&lt;g id="yogi"&gt;
  &lt;g id="broom"&gt;…&lt;/g&gt;
  &lt;g id="body"&gt;…&lt;/g&gt;
  &lt;g id="head"&gt;…&lt;/g&gt;
  &lt;g id="collar"&gt;…&lt;/g&gt;
  &lt;g id="tie"&gt;…&lt;/g&gt;
&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png"
			
			sizes="100vw"
			alt="Toon Titles recreation of the Yogi Bear title card"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Toon Titles recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues later on &mdash; they’ll all slot into place automatically. I keep my code <strong>clean</strong>, <strong>readable</strong>, and <strong>ordered logically</strong> by layering elements this way. It also makes animating smoother, as each component is easier to identify.</p>

<h2 id="reusing-elements-with-use">Reusing Elements With <code>&lt;use&gt;</code></h2>

<p>When duplicate shapes get reused repeatedly, SVG files can get bulky fast. My recreation of the “Bewitched Bear” title card contains 80 stars in three sizes. Combining all those shapes into one optimised path would bring the file size down to 3KB. But I want to animate individual stars, which would almost double that to 5KB:</p>

<pre><code class="language-svg">&lt;g id="stars"&gt;
 &lt;path class="star-small" fill="#eae3da" d="..."/&gt;
 &lt;path class="star-medium" fill="#eae3da" d="..."/&gt;
 &lt;path class="star-large" fill="#eae3da" d="..."/&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>

<p>Moving the stars’ <code>fill</code> attribute values to their parent group reduces the overall weight a little:</p>

<pre><code class="language-svg">&lt;g id="stars" fill="#eae3da"&gt;
 &lt;path class="star-small" d="…"/&gt;
 &lt;path class="star-medium" d="…"/&gt;
 &lt;path class="star-large" d="…"/&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png"
			
			sizes="100vw"
			alt="Yogi Bear’s sparkling stars."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear’s sparkling stars. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But a more efficient and manageable option is to define each star size as a reusable template:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
  &lt;path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
  &lt;path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
&lt;/defs&gt;
</code></pre>
</div>

<p>With this setup, changing a star’s design only means updating its template once, and every instance updates automatically. Then, I reference each one using <code>&lt;use&gt;</code> and position them with <code>x</code> and <code>y</code> attributes:</p>

<pre><code class="language-svg">&lt;g id="stars"&gt;
  &lt;!-- Large stars --&gt;
  &lt;use href="#star-large" x="1575" y="495"/&gt;
  &lt;!-- ... --&gt;
  &lt;!-- Medium stars --&gt;
  &lt;use href="#star-medium" x="1453" y="696"/&gt;
  &lt;!-- ... --&gt;
  &lt;!-- Small stars --&gt;
  &lt;use href="#star-small" x="1287" y="741"/&gt;
  &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>

<p>This approach makes the SVG easier to manage, lighter to load, and faster to iterate on, especially when working with dozens of repeating elements. Best of all, it keeps the markup clean <strong>without compromising on flexibility or performance</strong>.</p>

<div class="partners__lead-place"></div>

<h2 id="adding-animations">Adding Animations</h2>

<p>The stars trailing behind Yogi’s stolen broom bring so much personality to the animation. I wanted them to sparkle in a seemingly random pattern against the dark blue background, so I started by defining a keyframe animation that cycles through different <code>opacity</code> levels:</p>

<pre><code class="language-css">@keyframes sparkle {
  0%, 100% { opacity: .1; }
  50% { opacity: 1; }
}
</code></pre>

<p>Next, I applied this looping animation to every <code>use</code> element inside my stars group:</p>

<pre><code class="language-css">&#35;stars use {
  animation: sparkle 10s ease-in-out infinite;
}
</code></pre>

<p>The secret to creating a convincing twinkle lies in <strong>variation</strong>. I staggered animation delays and durations across the stars using <code>nth-child</code> selectors, starting with the quickest and most frequent sparkle effects:</p>

<pre><code class="language-css">/&#42; Fast, frequent &#42;/
&#35;stars use:nth-child(n + 1):nth-child(-n + 10) {
  animation-delay: .1s;
  animation-duration: 2s;
}
</code></pre>

<p>From there, I layered in additional timings to mix things up. Some stars sparkle slowly and dramatically, others more randomly, with a variety of rhythms and pauses:</p>

<pre><code class="language-css">/&#42; Medium &#42;/
&#35;stars use:nth-child(n + 11):nth-child(-n + 20) { ... }

/&#42; Slow, dramatic &#42;/
&#35;stars use:nth-child(n + 21):nth-child(-n + 30) { ... }

/&#42; Random &#42;/
&#35;stars use:nth-child(3n + 2) { ... }

/&#42; Alternating &#42;/
&#35;stars use:nth-child(4n + 1) { ... }

/&#42; Scattered &#42;/
&#35;stars use:nth-child(n + 31) { ... }
</code></pre>

<p>By thoughtfully structuring the SVG and reusing elements, I can build complex-looking animations without bloated code, making even a simple effect like changing <code>opacity</code> sparkle.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png"
			
			sizes="100vw"
			alt="Yogi Bear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Subtle movements bring Yogi Bear to life. (<a href='https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then, for added realism, I make Yogi’s head wobble:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes headWobble {
  0% { transform: rotate(-0.8deg) translateY(-0.5px); }
  100% { transform: rotate(0.9deg) translateY(0.3px); }
}

&#35;head {
  animation: headWobble 0.8s cubic-bezier(0.5, 0.15, 0.5, 0.85) infinite alternate;
}
</code></pre>
</div>

<p>His tie waves:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes tieWave {
  0%, 100% { transform: rotateZ(-4deg) rotateY(15deg) scaleX(0.96); }
  33% { transform: rotateZ(5deg) rotateY(-10deg) scaleX(1.05); }
  66% { transform: rotateZ(-2deg) rotateY(5deg) scaleX(0.98); }
}

&#35;tie {
  transform-style: preserve-3d;
  animation: tieWave 10s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite;
}
</code></pre>
</div>

<p>His broom swings:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes broomSwing {
  0%, 20% { transform: rotate(-5deg); }
  30% { transform: rotate(-4deg); }
  50%, 70% { transform: rotate(5deg); }
  80% { transform: rotate(4deg); }
  100% { transform: rotate(-5deg); }
}

&#35;broom {
  animation: broomSwing 4s cubic-bezier(0.5, 0.05, 0.5, 0.95) infinite;
}
</code></pre>
</div>

<p>And, finally, Yogi himself gently rotates as he flies on his magical broom:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes yogiWobble {
  0% { transform: rotate(-2.8deg) translateY(-0.8px) scale(0.998); }
  30% { transform: rotate(1.5deg) translateY(0.3px); }
  100% { transform: rotate(3.2deg) translateY(1.2px) scale(1.002); }
}

&#35;yogi {
  animation: yogiWobble 3.5s cubic-bezier(.37, .14, .3, .86) infinite alternate;
}
</code></pre>
</div>

<p>All these subtle movements bring Yogi to life. By developing structured SVGs, I can create animations that feel full of character without writing a single line of JavaScript.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="bNdwJBN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Bewitched Bear CSS/SVG animation [forked]](https://codepen.io/smashingmag/pen/bNdwJBN) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNdwJBN">Bewitched Bear CSS/SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>Whether you’re recreating a classic title card or animating icons for an interface, the principles are the same:</p>

<ol>
<li>Start clean,</li>
<li>Optimise early, and</li>
<li>Structure everything with animation in mind.</li>
</ol>

<p>SVGs offer incredible creative freedom, but only if kept <strong>lean</strong> and <strong>manageable</strong>. When you plan your process like a production cell &mdash; layer by layer, element by element &mdash; you’ll spend less time untangling code and more time bringing your work to life.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</title><link>https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/</link><pubDate>Wed, 21 May 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/</guid><description>While there are plenty of ways that CSS animations can bring designs to life, adding simple SMIL (Synchronized Multimedia Integration Language) animations in SVG can help them do much more. Andy Clarke explains where SMIL animations in SVG take over where CSS leaves off.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/" />
              <title>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-05-21T08:00:00&#43;00:00" class="op-published">2025-05-21T08:00:00+00:00</time>
                  <time datetime="2025-05-21T08:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>The SMIL specification was introduced by the W3C in 1998 for synchronizing multimedia. This was long before CSS animations or JavaScript-based animation libraries were available. It was built into SVG 1.1, which is why we can still use it there today.</p>

<p>Now, you might’ve heard that <a href="https://css-tricks.com/smil-is-dead-long-live-smil-a-guide-to-alternatives-to-smil-features">SMIL is dead</a>. However, it’s alive and well since Google reversed a decision to deprecate the technology almost a decade ago. It remains a terrific choice for designers and developers who want simple, semantic ways to add animations to their designs.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Tip</strong>: <em>There’s now a website where you can see all my <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a>.</em></p>

<div class="refs">
  <ul><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li><li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li></ul>
</div>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="introducing-mike-worth">Introducing Mike Worth</h2>

<p>I’ve recently been working on a new website for Emmy-award-winning game composer Mike Worth. He hired me to create a bold, retro-style design that showcases his work. I used animations throughout to delight and surprise his audience as they move through his website.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="550"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png"
			
			sizes="100vw"
			alt="Illustrations from Mike Worth’s website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Design by <a href='https://stuffandnonsense.co.uk/'>Andy Clarke, Stuff & Nonsense</a>. Mike Worth’s website will launch in June 2025, but you can see <a href='https://codepen.io/collection/YwMKPb'>examples from this article on CodePen</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Mike loves ’90s animation &mdash; especially <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)">Disney’s</a> <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)"><em>Duck Tales</em></a>. Unsurprisingly, my taste in cartoons stretches back a little further to <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear"><em>The Yogi Bear Show</em></a>. So, to explain how this era of animation relates to SVG, I’ll be adding SMIL animations in SVG to title cards from some classic Yogi Bear cartoons.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="300"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustrations"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Fundamentally, animation changes how an element looks and where it appears over time using a few basic techniques. That might be simply shifting an element up or down, left or right, to create the appearance of motion, like Yogi Bear moving across the screen.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png"
			
			sizes="100vw"
			alt="Yogi Bear title card design recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Rotating objects around a fixed point can create everything, from simple spinning effects to natural-looking movements of totally normal things, like a bear under a parachute falling from the sky.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png"
			
			sizes="100vw"
			alt="Yogi Bear title card recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Scaling makes an element grow, shrink, or stretch, which can add drama, create perspective, or simulate depth.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png"
			
			sizes="100vw"
			alt="Yogi Bear title card recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Changing colour and transitioning opacity can add atmosphere, create a mood, and enhance visual storytelling. Just these basic principles can create animations that attract attention and improve someone’s experience using a design.</p>

<p>These results are all achievable using CSS animations, but some SVG properties can’t be animated using CSS. Luckily, we can do more &mdash; and have much more fun &mdash; using SMIL animations in SVG. We can combine complex animations, move objects along paths, and control when they start, stop, and everything in between.</p>

<p>Animations can be embedded within any SVG element, including <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorials/SVG_from_scratch/Basic_shapes">primitive shapes</a> like circles, ellipses, and rectangles. They can also be encapsulated into groups, paths, and polygons:</p>

<pre><code class="language-svg">&lt;circle ...&gt;
  &lt;animate&gt;...&lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>Animations can also be defined outside an element, elsewhere in an SVG, and connected to it using an <code>xlink</code> attribute:</p>

<pre><code class="language-svg">&lt;g id="yogi"&gt;...&lt;/g&gt;
  ...
&lt;animate xlink:href="#yogi"&gt;…&lt;/animate&gt;
</code></pre>

<h2 id="building-an-animation">Building An Animation</h2>

<p><code>&lt;animate&gt;</code> is just one of several animation elements in SVG. Together with an <code>attributeName</code> value, it enables animations based on one or more of an element’s attributes.</p>

<p>Most animation explanations start by moving a primitive shape, like this exciting circle:</p>

<pre><code class="language-svg">&lt;circle
  r="50"
  cx="50" 
  cy="50" 
  fill="#062326" 
  opacity="1"
/&gt;
</code></pre>

<p>Using this <code>attributeName</code> property, I can define which of this circle’s attributes I want to animate, which, in this example, is its <code>cx</code> (x-axis center point) position:</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate attributename="cx"&gt;&lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>On its own, this does precisely nothing until I define three more values. The <code>from</code> keyword specifies the circle’s initial position, <code>to</code>, its final position, and the <code>dur</code>-ation between those two positions:</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  from="50" 
  to="500"
  dur="1s"&gt;
  &lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>If I want more precise control, I can replace <code>from</code> and <code>to</code> with a set of <code>values</code> separated by semicolons:</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  values="50; 250; 500; 250;"
  dur="1s"&gt;
  &lt;/animate&gt;
&lt;/circle&gt;
</code></pre>

<p>Finally, I can define how many times the animation repeats (<code>repeatcount</code>) and even after what period that repeating should stop (<code>repeatdur</code>):</p>

<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  values="50; 250; 500; 250;"
  dur="1s"
  repeatcount="indefinite"
  repeatdur="180s"&gt;
&lt;/circle&gt;
</code></pre>

<p>Most SVG elements have attributes that can be animated. This title card from 1959’s <a href="https://yogibear.fandom.com/wiki/Brainy_Bear">“Brainy Bear” episode</a> shows Yogi in a crazy scientist‘s brain experiment. Yogi’s head is under the dome, and energy radiates around him.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration where Yogi’s head is under the dome, and energy radiates around him"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To create the buzz around Yogi, my SVG includes three <code>path</code> elements, each with <code>opacity</code>, <code>stroke</code>, and <code>stroke-width</code> attributes, which can all be animated:</p>

<pre><code class="language-svg">&lt;path opacity="1" stroke="#fff" stroke-width="5" ... /&gt;
</code></pre>

<p>I animated each path’s <code>opacity</code>, changing its value from <code>1</code> to <code>.5</code> and back again:</p>

<pre><code class="language-svg">&lt;path opacity="1" ... &gt;
  &lt;animate 
    attributename="opacity"
    values="1; .25; 1;"
    dur="1s"
    repeatcount="indefinite"&gt;
  &lt;/animate&gt;
&lt;/path&gt;
</code></pre>

<p>Then, to radiate energy from Yogi, I specified when each animation should <code>begin</code>, using a different value for each <code>path</code>:</p>

<pre><code class="language-svg">&lt;path ... &gt;
  &lt;animate begin="0" … &gt;
&lt;/path&gt;

&lt;path ... &gt;
  &lt;animate begin=".5s" … &gt;
&lt;/path&gt;

&lt;path ... &gt;
  &lt;animate begin="1s" … &gt;
&lt;/path&gt;
</code></pre>

<p>I’ll explain more about the <code>begin</code> property and how to start animations after this short commercial break.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="qEEzYgG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Brainy Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/qEEzYgG) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/qEEzYgG">Brainy Bear SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>To make animations appear more natural, I can apply more than one <code>animate</code> element and give each one a different <code>attributename</code> value. Those paths also contain a <code>stroke-width</code> attribute, which I can also animate by changing the stroke widths between <code>5</code> and <code>7</code>:</p>

<pre><code class="language-svg">&lt;path ... &gt;
  &lt;animate attributename="opacity" ... &gt;&lt;/animate&gt;
  &lt;animate attributename="stroke-width" ... &gt;&lt;/animate&gt;
&lt;/path&gt;
</code></pre>

<p>Finally, I can animate the dome over Yogi’s head, changing its <code>fill</code> colour between two values over five seconds to create the impression that the crazy scientist’s machine is heating up:</p>

<pre><code class="language-svg">&lt;path fill="#50D9E0" ... &gt;
  &lt;animate
    attributename="fill"
    values="#50D9E0; #E18C50;"
    dur="5s"
    begin="2s"
  &gt;
&lt;/path&gt;
</code></pre>

<p>Implement that code, and you’ll soon notice that the dome returns to its original state after the animation is complete. To retain its colour at the end of the animation, I can add the &mdash; confusingly named &mdash; <code>fill</code> property and a value of <code>freeze.</code> This stops the animation in its final state and prevents it from returning to the original colour:</p>

<pre><code class="language-svg">&lt;path fill="#50D9E0" ... &gt;
  &lt;animate fill="freeze"&gt;
&lt;/path&gt;
</code></pre>

<p>Animating attributes brings these title card designs to life, whether by adjusting the position of a primitive shape, its opacity, and stroke width or by creating complex sequences with staggered timing. But there’s still more I can do, starting with the next animation element, <code>animateTransform</code>.</p>

<div class="partners__lead-place"></div>

<h2 id="animatetransform"><code>animateTransform</code></h2>

<p>If <code>&lt;animate&gt;</code> controls attributes, then <code>animateTransform</code> animates transformations, including rotations, scaling, skewing, and translations. It works by changing the values of a transform property, like this <code>translate</code>:</p>

<pre><code class="language-svg">&lt;path transform="translate(0,0)"/&gt;
</code></pre>

<p>Then, the animation works the same way as <code>&lt;animate&gt;</code>, adding an <code>attributename</code> and specifying the type of transform, in this example, <code>rotate:</code></p>

<pre><code class="language-svg">&lt;animatetransform 
  attributename="transform"
  type="rotate"&gt;
&lt;/animatetransform&gt;
</code></pre>

<p>I can use either <code>from</code> and <code>to</code> or the <code>values</code> attribute to define how an element is transformed.</p>

<ul>
<li><strong>Scale</strong> uses <code>x</code> and <code>y</code> values  (<code>.5</code>, <code>1</code>).</li>
<li><strong>Rotate</strong> uses degrees (<code>0</code>–<code>360</code>) plus optional <code>x</code> and <code>y</code> (<code>360</code>, <code>0</code>, <code>0</code>).</li>
<li><strong>Translate</strong> also uses <code>x</code> and <code>y</code> values  (<code>50</code>, <code>100</code>).</li>
<li><strong>Skew</strong> uses <code>x</code> and <code>y</code> values, too (<code>50</code>, <code>100</code>).</li>
</ul>

<p>What’s interesting about those values is that they can be added to an element’s existing values instead of replacing them. For example, when an attribute contains a <code>translate</code> value of <code>100, 0</code>:</p>

<pre><code class="language-svg">&lt;path transform="translate(100, 0)"/&gt;
</code></pre>

<p>And then I animate that translation horizontally by <code>100</code>:</p>

<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0, 0"
  to="100, 0"
  additive="sum"&gt;
&lt;/animatetransform&gt;
</code></pre>

<p>Using the <code>additive</code> property with a value of <code>sum</code>, the animation values are relative to the original, starting the animation at <code>100</code> and ending at <code>200</code> by adding <code>100</code> to <code>100</code>.</p>

<p>Similarly, if I give the <code>accumulate</code> property a value of <code>sum</code>, each instance of animation will build on the last. So, in an animation where an element is translated by <code>100</code> and repeats five times, each movement will be cumulative, moving the element by <code>500</code>:</p>

<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0, 0"
  to="100, 0"
  additive="sum"
  accumulate="sum" 
/&gt;
</code></pre>

<p>This title card from 1958’s Yogi Bear’s <a href="https://yogibear.fandom.com/wiki/Yogi_Bear%27s_Big_Break">“Big Break” episode</a> shows Yogi floating from the sky under a parachute.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration shows Yogi floating from the sky under a parachute"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I needed two types of transform animations to generate the effect of Yogi drifting gently downwards: <code>translate</code>, and <code>rotate</code>. I first added an <code>animatetransform</code> element to the group, which contains Yogi and his chute. I defined his initial vertical position &mdash; <code>1200</code> off the top of the <code>viewBox</code> &mdash; then translated his descent to <code>1000</code> over a 15-second duration:</p>

<pre><code class="language-svg">&lt;g transform="translate(1200, -1200)"&gt;
  ...
  &lt;animateTransform
    attributeName="transform"
    type="translate"
    values="500,-1200; 500,1000"
    dur="15s"
    repeatCount="1" 
  /&gt;
&lt;/g&gt;
</code></pre>

<p>Yogi appears to fall from the sky, but the movement looks unrealistic. So, I added a second <code>animatetransform</code> element, this time with an indefinitely repeating +/- 5-degree rotation to swing Yogi from side to side during his descent:</p>

<pre><code class="language-svg">&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-5; 5; -5"
  dur="14s"
  repeatCount="indefinite"
  additive="sum" 
/&gt;
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="PwwraNm"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Big Break SVG animation [forked]](https://codepen.io/smashingmag/pen/PwwraNm) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/PwwraNm">Big Break SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="starting-and-stopping">Starting And Stopping</h2>

<p>So far, every animation begins as soon as the page has loaded. But there are ways to not only delay the start of animation but define precisely where it begins, using the begin <code>property</code>:</p>

<p>In this title card from 1959’s <a href="https://yogibear.fandom.com/wiki/Robin_Hood_Yogi">“Robin Hood Yogi”</a>, Yogi shoots an arrow into an apple on Boo-Boo’s head.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration where Yogi shoots an arrow into an apple on Boo-Boo’s head"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>By default, the arrow is set loose when the page loads. Blink, and you might miss it. To build some anticipation, I can <code>begin</code> the animation two seconds later:</p>

<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0 0"
  to="750 0"
  dur=".25s"
  begin="2s"
  fill="freeze"
/&gt;
</code></pre>

<p>Or, I can let the viewer take the shot when they click the arrow:</p>

<pre><code class="language-svg">&lt;animatetransform
  ...
  begin="click"
/&gt;
</code></pre>

<p>And I can combine the click event and a delay, all with no JavaScript, just a smattering of SMIL:</p>

<pre><code class="language-svg">&lt;animatetransform
  ...
  begin="click + .5s"
/&gt;
</code></pre>

<p>Try this yourself by clicking the arrow:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="OPPeERj"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Robin Hood Yogi CSS animation [forked]](https://codepen.io/smashingmag/pen/OPPeERj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/OPPeERj">Robin Hood Yogi CSS animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="synchronising-animations">Synchronising Animations</h2>

<p>In his 1958 <a href="https://yogibear.fandom.com/wiki/Pie-Pirates">“Pie-Pirates” episode</a>, Yogi Bear tries to steal a pie and has to outwit a bulldog. The title card &mdash; designed by Lawrence Goble &mdash; shows the chase but, alas, (spoiler alert) no stolen pie.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration showing a bulldog chasing Yogi Bear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To bring this title card to life, I needed two groups of paths: one for Yogi and the other for the dog. I translated them both off the left edge of the <code>viewBox</code>:</p>

<pre><code class="language-svg">&lt;g class="dog" transform="translate(-1000, 0)"&gt;
  ...
&lt;/g&gt;

&lt;g class="yogi" transform="translate(-1000, 0)"&gt;
  ...
&lt;/g&gt;
</code></pre>

<p>Then, I applied an <code>animatetransform</code> element to both groups, which moves them back into view:</p>

<pre><code class="language-svg">&lt;!-- yogi --&gt;
&lt;animateTransform
  attributeName="transform"
  type="translate"
  from="-1000,0"
  to="0,0"
  dur="2s"
  fill="freeze"
/&gt;

&lt;!-- dog --&gt;
&lt;animateTransform
  attributeName="transform"
  type="translate"
  from="-1000,0"
  to="0,0"
  dur=".5s"
  fill="freeze"
/&gt;
</code></pre>

<p>This sets up the action, but the effect feels flat, so I added another pair of animations that bounce both characters:</p>

<pre><code class="language-svg">&lt;!-- yogi --&gt;
&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-1,0,450; 1,0,450; -1,0,450"
  dur=".25s"
  repeatCount="indefinite"
/&gt;

&lt;!-- dog --&gt;
&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-1,0,450; 1,0,450; -1,0,450"
  dur="0.5s"
  repeatCount="indefinite"
/&gt;
</code></pre>

<p>Animations can begin when a page loads, after a specified time, or when clicked. And by naming them, they can also synchronise with other animations.</p>

<p>I wanted Yogi to enter the frame first to build anticipation, with a short pause before other animations begin, synchronising to the moment he’s arrived. First, I added an ID to Yogi’s <code>translate</code> animation:</p>

<pre><code class="language-svg">&lt;animateTransform
  id="yogi"
  type="translate"
  ...
/&gt;
</code></pre>

<blockquote><strong>Watch out</strong>: For a reason, I can’t, for the life of me, explain why Firefox won’t begin animations with an ID when the ID contains a hyphen. This isn’t smarter than the average browser, but replacing hyphens with underscores fixes the problem.</blockquote>

<p>Then, I applied a <code>begin</code> to his <code>rotate</code> animation, which starts playing a half-second after the <code>#yogi</code> animation ends:</p>

<pre><code class="language-svg">&lt;animateTransform
  type="rotate"
  begin="yogi.end + .5s"
  ...
/&gt;
</code></pre>

<p>I can build sophisticated sets of synchronised animations using the <code>begin</code> property and whether a named animation begins or ends. The bulldog chasing Yogi enters the frame two seconds after Yogi begins his entrance:</p>

<pre><code class="language-svg">&lt;animateTransform
  id="dog"
  type="translate"
  begin="yogi.begin + 2s"
  fill="freeze"
  ...
/&gt;
</code></pre>

<p>One second after the dog has caught up with Yogi, a <code>rotate</code> transformation makes him bounce, too:</p>

<pre><code class="language-svg">&lt;animateTransform
  type="rotate"
  ...
  begin="dog.begin + 1s"
  repeatCount="indefinite" 
/&gt;
</code></pre>

<p>The background rectangles whizzing past are also synchronised, this time to one second before the bulldog ends his run:</p>

<pre><code class="language-svg">&lt;rect ...&gt;
  &lt;animateTransform
    begin="dog.end + -1s"
  /&gt;
&lt;/rect&gt;
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="LEEKryp"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Pie-Pirates SVG animation [forked]](https://codepen.io/smashingmag/pen/LEEKryp) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LEEKryp">Pie-Pirates SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>The timing of this background movement is synchronised with the dog arriving, which, in turn, is relative to Yogi’s arrival, building a sequence of animations that all feel connected.</p>

<div class="partners__lead-place"></div>

<h2 id="animating-along-motion-paths">Animating Along Motion Paths</h2>

<p>Until now, all the animations in these title cards have been up, down, left, right, or one combination or another. But there’s one more aspect of SMIL in SVG, which can add an extra dimension to animations: animating along motion paths using the <code>animatemotion</code> element.</p>

<p><code>animatemotion</code> accepts all the same properties and values as <code>animate</code> and <code>animateTransform</code>, but adds a few more for finer control over direction and timing. <code>animatemotion</code> uses the <code>path</code> property to enable elements to move along a motion path. It also uses the <code>d</code> value for coordinate data in the same way as any conventional path.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="540"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration of the Runaway Bear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In <a href="https://yogibear.fandom.com/wiki/The_Runaway_Bear">“The Runaway Bear”</a> from 1959, Yogi must avoid a hunter turning his head into a trophy. I wanted Yogi to leap in and out of the screen by making him follow a path. I also wanted to vary the speed of his dash: speeding up as he enters and exits, and slowing down as he passes the title text.</p>

<p>I first added a <code>path</code> property, using its coordinate data to give Yogi a route to follow, and specified a two-second duration for my animation:</p>

<pre><code class="language-svg">&lt;g&gt;
  &lt;animateMotion
    dur="2s"
    path="..."
  &gt;
  &lt;/animateMotion&gt;
&lt;/g&gt;
</code></pre>

<p>Alternatively, I could add a <code>path</code> element, leave it visible, or prevent it from being rendered by placing it inside a <code>defs</code> element:</p>

<pre><code class="language-svg">&lt;defs&gt;
  &lt;path id="yogi" d="..." /&gt;
&lt;/defs&gt;
</code></pre>

<p>I can then reference that by using a <code>mpath</code> element inside my <code>animateMotion</code>:</p>

<pre><code class="language-svg">&lt;animateMotion
  ...
  &lt;mpath href="#yogi" /&gt;
&lt;/animateMotion&gt;
</code></pre>

<p>I experimented with several paths before settling on the one that delivered the movement shape I was looking for:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png"
			
			sizes="100vw"
			alt="Several variants of the Yogi Bear title card recreated by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1959.) Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>One was too bouncy, one was too flat, but the third motion path was just right. Almost, as I also wanted to vary the speed of Yogi’s dash: speeding him up as he enters and exits and slowing him down as he passes the title text.</p>

<p>The <code>keyPoints</code> property enabled me to specify points along the motion path and then adjust the duration Yogi spends between them. To keep things simple, I defined five points between <code>0</code> and <code>1</code>:</p>

<pre><code class="language-svg">&lt;animateMotion
  ...
  keyPoints="0; .35; .5; .65; 1;"
&gt;
&lt;/animateMotion&gt;
</code></pre>

<p>Then I added the same number of <code>keyTimes</code> values, separated by semicolons, to control the pacing of this animation:</p>

<pre><code class="language-svg">&lt;animateMotion
  ...
  keyTimes="0; .1; .5; .95; 1;"
&gt;
&lt;/animateMotion&gt;
</code></pre>

<p>Now, Yogi rushes through the first three <code>keyPoints</code>, slows down as he passes the title text, then speeds up again as he exits the <code>viewBox</code>.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="oggryox"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Runaway Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/oggryox) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggryox">Runaway Bear SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="smil-s-not-dead-baby-smil-s-not-dead">SMIL’s Not Dead, Baby. SMIL’s Not Dead</h2>

<p>With their ability to control transformations, animate complex motion paths, and synchronise multiple animations, SMIL animations in SVG are still powerful tools. They can bring design to life without needing a framework or relying on JavaScript. It’s compact, which makes it great for small SVG effects.</p>

<p>SMIL includes the <code>begin</code> attribute, which makes chaining animations far more intuitive than with CSS. Plus, SMIL lives inside the SVG file, making it perfect for animations that travel with an asset. So, while SMIL is not modern by today’s standards and may be a little bit niche, it can still be magical.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aDon%e2%80%99t%20let%20the%20misconception%20that%20SMIL%20is%20%e2%80%9cdead%e2%80%9d%20stop%20you%20from%20using%20this%20fantastic%20tool.%0a&url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-3-smil-not-dead%2f">
      
Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>Google reversed its decision to deprecate SMIL almost a decade ago, so it remains a terrific choice for designers and developers who want <strong>simple</strong>, <strong>semantic ways</strong> to add animations to their designs.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</title><link>https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/</link><pubDate>Wed, 14 May 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/</guid><description>What if you could take your CSS animations beyond simple fades and slides &amp;mdash; adding an extra dimension and a bit of old-school animation magic? In this article, pioneering author and web designer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> will show you how masking can unlock new creative possibilities for CSS animations, making them feel more fluid, layered, and cinematic.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/" />
              <title>Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-05-14T13:00:00&#43;00:00" class="op-published">2025-05-14T13:00:00+00:00</time>
                  <time datetime="2025-05-14T13:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Despite keyframes and scroll-driven events, CSS animations have remained relatively rudimentary. As I wrote in <a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Part 1</a>, they remind me of the 1960s <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> animated series I grew up watching on TV. Shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear">Yogi Bear</a>.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="640"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg"
			
			sizes="100vw"
			alt="The Yogi Bear Show poster"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Mike loves ’90s animation &mdash; especially <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)">Disney’s <em>Duck Tales</em></a>. So, that is the aesthetic applied throughout the design.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="550"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png"
			
			sizes="100vw"
			alt="Design by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Design by <a href='https://stuffandnonsense.co.uk/'>Andy Clarke, Stuff & Nonsense</a>. Mike Worth’s website will launch in June 2025, but you can see <a href='https://codepen.io/collection/YwMKPb'>examples from this article on CodePen</a>. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I used animations throughout and have recently added an extra dimension to them using <strong>masking</strong>. So, to explain how this era of animation relates to masking in CSS, I’ve chosen an episode of <em>The Yogi Bear Show</em>, “Disguise and Gals,” first broadcast in May 1961. In this story, two bank robbers, disguised as little old ladies, hide their loot in a “pic-a-nic” basket in Yogi and Boo-Boo’s cave!</p>

<p>What could <em>possibly</em> go wrong?</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="640"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg"
			
			sizes="100vw"
			alt="The Yogi Bear Show, “Disguise and Gals” episode"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="what-s-a-mask">What’s A Mask?</h2>

<p>One simple masking example comes at the end of “Disguise and Gals” and countless other cartoons. Here, an animated vignette gradually hides more of Yogi’s face. The content behind the mask isn’t erased; it’s hidden.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="198"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg"
			
			sizes="100vw"
			alt="A masking example of Yogi’s face"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In CSS, <strong>masking controls visibility using a bitmap, vector, or gradient mask image</strong>. When a mask’s filled pixels cover an element, its content will be visible. When they are transparent, it will be hidden, which makes sense. Filled pixels can be any colour, but I always make mine hot pink so that it’s clear to me which areas will be visible.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="264"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg"
			
			sizes="100vw"
			alt="The Yogi Bear’s masking example by Andy Clarke"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>A <code>clip-path</code> functions similarly to a <code>mask</code> but uses paths to create hard-edged clipping areas. If you want to be picky, masks and clipping paths are technically different, but the goal for using them is usually the same. So, for this article, I’ll refer to them as two entrances to the same cave and call using either “masking.”</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="148"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png"
			
			sizes="100vw"
			alt="A robber rushes the picnic basket into Yogi’s cave"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In this sequence from “Disguise and Gals,” one of the robbers rushes the picnic basket containing their loot into Yogi’s cave. Masking defines the visible area, creating the illusion that the robber is entering the cave.</p>

<blockquote>How do I choose when to use <code>clip path</code> and when to choose <code>mask</code>?</blockquote>

<p>I’ll explain my reasons in each example.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="219"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png"
			
			sizes="100vw"
			alt="Masking defines the visible area of the cave"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>When Mike Worth and I discussed working together, we knew we would neither have the budget nor the time to create a short animated cartoon for his website. However, we were keen to explore how animations could bring to life what would’ve otherwise been static images.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="549"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png"
			
			sizes="100vw"
			alt="Illustration by Andy Clarke for Mike Worth’s website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="masking-using-a-clipping-path">Masking Using A Clipping Path</h2>

<p>On Mike’s biography page, his character also enters a cave. The SVG illustration I created contains two groups, one for the background and the other for the orangutan in the foreground:</p>

<pre><code class="language-html">&lt;figure&gt;
  &lt;svg viewBox="0 0 1400 960" id="cave"&gt;
    &lt;g class="background"&gt;…&lt;/g&gt;
    &lt;g class="foreground"&gt;…&lt;/g&gt;
  &lt;/svg&gt;
&lt;/figure&gt;
</code></pre>

<p>I defined a keyframe animation that moves the character from <code>2000px</code> on the right to its natural position in the center of the frame by altering its <code>translate</code> value:</p>

<pre><code class="language-css">@keyframes foreground {
  0% { 
    opacity: .75; 
    translate: 2000px 0;
  }
  60% { 
    opacity: 1;
    translate: 0 0;
  }
  80% {
    opacity: 1; 
    translate: 50px 0;
  }
  100% {
    opacity: 1;
    translate: 0 0;
  }
}
</code></pre>

<p>Then, I applied that animation to the foreground group:</p>

<pre><code class="language-css">.foreground {
  opacity: 0;
  animation: foreground 5s 2s ease-in-out infinite;
}
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="VYYgYRQ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s about page (without clip-path) [forked]](https://codepen.io/smashingmag/pen/VYYgYRQ) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/VYYgYRQ">Mike Worth’s about page (without clip-path) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>While the <code>50px</code> bounce does add a touch of realism to his movement, I wasn’t happy with how the character’s entrance started at the edge of the viewport.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="549"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png"
			
			sizes="100vw"
			alt="Character standing at the edge of the illustartion made with masking using a clipping path"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I wanted him to become visible at the edge of the illustration instead. As the edges of the cave walls are hard, I chose a <code>clip-path</code>.</p>

<p>There are several ways to define a <code>clip-path</code> in CSS. I could use a primitive shape like a rectangle, where each of the first four values specifies its corner positions. The <code>round</code> keyword and the value that follows define any rounded corners:</p>

<pre><code class="language-css">clip-path: rect(0px 150px 150px 0px round 5px);
</code></pre>

<p>Or <code>xywh</code> (x, y, width, height) values, which I find easier to read:</p>

<pre><code class="language-css">clip-path: xywh(0 0 150px 150px round 5px);
</code></pre>

<p>I could use a <code>circle</code>:</p>

<pre><code class="language-css">clip-path: circle(60px at center);
</code></pre>

<p>Or an <code>ellipse</code>:</p>

<pre><code class="language-css">clip-path: ellipse(50% 40% at 50% 50%);
</code></pre>

<p>I could use a <code>polygon</code> shape:</p>

<pre><code class="language-css">clip-path: polygon(...);
</code></pre>

<p>Or even the points from a path I created in a graphics app like Sketch:</p>

<pre><code class="language-css">clip-path: path("M ...");
</code></pre>

<p>Finally &mdash; and my choice for this example &mdash; I might use a mask that I defined using paths from an SVG file:</p>

<pre><code class="language-css">clip-path: url(#mask-cave);
</code></pre>

<p>To make the character visible from the edge of the illustration, I added a second SVG. To prevent a browser from displaying it, set both its dimensions to zero:</p>

<pre><code class="language-html">&lt;figure&gt;
  &lt;svg viewBox="0 0 1400 960" id="cave"&gt;...&lt;/svg&gt;
  &lt;svg height="0" width="0" id="mask"&gt;...&lt;/svg&gt;
&lt;/figure&gt;
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="549"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png"
			
			sizes="100vw"
			alt="A single SVG clipPath"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>This contains a single SVG <code>clipPath</code>. By placing this inside the <code>defs</code> element, this path isn’t rendered, but it will be available to create my CSS <code>clip-path</code>:</p>

<pre><code class="language-svg">&lt;svg height="0" width="0" id="mask"&gt;
  &lt;defs&gt;
    &lt;clipPath id="mask-cave"&gt;...&lt;/clipPath&gt;
  &lt;/defs&gt;
&lt;/svg&gt;
</code></pre>

<p>I applied the <code>clipPath</code> URL to my illustration, and now Mike’s mascot only becomes visible when he enters the cave:</p>

<pre><code class="language-css">#cave {
  clip-path: url(#mask-cave);
}
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="oggmgKp"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s about page (with clip-path) (without clip-path) [forked]](https://codepen.io/smashingmag/pen/oggmgKp) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggmgKp">Mike Worth’s about page (with clip-path) (without clip-path) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<blockquote><strong>Tip</strong>: Implement that code, and you’ll notice there’s a problem when resizing a browser window. While my cave illustration is flexible, the <code>clipPath</code> remains a fixed width.<br /><br />To make <code>clipPath</code> responsive, add <code>clipPathUnits="objectBoundingBox"</code> to the opening tag, then apply two <code>scale</code> values to the <code>transform</code> property. To calculate these values, divide <code>1</code> first by the SVG’s <code>width</code> and then by its <code>height</code>. My SVG’s width is <code>1400px</code>, which produces the first <code>scale</code> value, <code>0.0007142857143</code>.<br/>
<div class="break-out">
<pre><code class="language-svg">&lt;clipPath id="mask-cave"
  clipPathUnits="objectBoundingBox"
  transform="scale(0.0007142857143, 0.001041666667)"&gt;
  ...
&lt;/clipPath&gt;
</code></pre>
</div></blockquote>

<div class="partners__lead-place"></div>

<h2 id="clipping-irregular-shapes">Clipping Irregular Shapes</h2>

<p>I often need to change or alter illustrations &mdash; perhaps by overlaying colours or applying blending modes &mdash; but I want to retain their overall shape.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="181"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png"
			
			sizes="100vw"
			alt="Altered illustrations with overlaying colours"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>While a <code>clipPath</code> will give me the result I’m looking for, the complexity and size of these paths can sometimes negatively affect performance. That’s when I choose a CSS <code>mask</code> as its properties have been baseline and highly usable since 2023.</p>

<p>The <code>mask</code> property is a shorthand and can include values for <code>mask-clip</code>, <code>mask-mode</code>, <code>mask-origin</code>, <code>mask-position</code>, <code>mask-repeat</code>, <code>mask-size</code>, and <code>mask-type</code>. I find it’s best to learn these properties individually to grasp the concept of masks more easily.</p>

<p>Masks control visibility using bitmap, vector, or gradient mask images. Again, when a mask’s filled pixels cover an element, its content will be visible. When they‘re transparent, the content will be hidden. And when parts of a mask are semi-transparent, some of the content will show through. I can use a bitmap format that includes an alpha channel, such as PNG or WebP:</p>

<pre><code class="language-css">mask-image: url(mask.webp);
</code></pre>

<p>I could apply a mask using a vector graphic:</p>

<pre><code class="language-css">mask-image: url(mask.svg);
</code></pre>

<p>Or generate an image using a conical, linear, or radial gradient:</p>

<pre><code class="language-css">mask-image: linear-gradient(#000, transparent); 
</code></pre>

<p>…or:</p>

<div class="break-out">
<pre><code class="language-css">mask-image: radial-gradient(circle, #ff104c 0%, transparent 100%);
</code></pre>
</div>

<p>I might apply more than one mask to an element and mix several image types using what should be a familiar syntax:</p>

<pre><code class="language-css">mask-image:
  image(url(mask.webp)),
  linear-gradient(#000, transparent);
</code></pre>

<p><code>mask</code> shares the same syntax as CSS backgrounds, which makes remembering its properties much easier. To apply a <code>background-image</code>, add its URL value:</p>

<pre><code class="language-css">background-image: url("background.webp");
</code></pre>

<p>To apply a mask, swap the <code>background-image</code> property for <code>mask-image</code>:</p>

<pre><code class="language-css">mask-image: url("mask.webp");
</code></pre>

<p>The <code>mask</code> property also shares the same browser styles as CSS backgrounds, so by default, a mask will repeat horizontally and vertically unless I specify otherwise:</p>

<div class="break-out">
<pre><code class="language-css">/&#42; Options: repeat, repeat-x, repeat-y, round, space, no-repeat &#42;/
mask-repeat: no-repeat;
</code></pre>
</div>

<p>It will be placed at the top-left corner unless I alter its position:</p>

<pre><code class="language-css">/&#42; Options: Keywords, units, percentages &#42;/
mask-position: center;
</code></pre>

<p>Plus, I can specify <code>mask-size</code> in the same way as <code>background-size</code>:</p>

<div class="break-out">
<pre><code class="language-css">/&#42; Options: Keywords (auto, contain, cover), units, percentages &#42;/
mask-size: cover;
</code></pre>
</div>

<p>Finally, I can define where a mask starts:</p>

<pre><code class="language-css">mask-origin: content-box;
mask-origin: padding-box;
mask-origin: border-box;
</code></pre>

<h2 id="using-a-mask-image">Using A Mask Image</h2>

<p>Mike’s FAQs page includes an animated illustration of his hero standing at a crossroads. My goal was to separate the shape from its content, allowing me to change the illustration throughout the hero’s journey. So, I created a scalable <code>mask-image</code> which defines the visible area and applied it to the figure element:</p>

<pre><code class="language-css">figure {
  mask-image: url(mask.svg);
}
</code></pre>

<p>To ensure the mask matched the illustration’s dimensions, I also set the <code>mask-size</code> to always <code>cover</code> its content:</p>

<pre><code class="language-css">figure {
  mask-size: cover;
}
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="181"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png"
			
			sizes="100vw"
			alt="Mike Worth’s FAQs (mask-image)"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="VYYgLed"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s FAQs (mask-image) [forked]](https://codepen.io/smashingmag/pen/VYYgLed) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/VYYgLed">Mike Worth’s FAQs (mask-image) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>Although “X“ never, ever marks the spot, Mike Worth’s review page illustration sees his orangutan mascot studying his treasure map. I wanted to focus someone’s attention on the center part of the image by using an elliptical shape.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="549"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png"
			
			sizes="100vw"
			alt="A gorilla dressed as an explorer studies a treasure map at a desk with an elliptical shape effect"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png'>Large preview</a>)
    </figcaption>
  
</figure>

<pre><code class="language-css">figure {
  clip-path: ellipse(45% 35% at 50% 50%);
}
</code></pre>

<p>However, the hard edges of a clip <code>clip-path</code> don’t create the effect I was aiming to achieve:</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="dPPaoXK"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s reviews page (ellipse) [forked]](https://codepen.io/smashingmag/pen/dPPaoXK) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/dPPaoXK">Mike Worth’s reviews page (ellipse) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>I experimented by combining a Gaussian blur filter with a mask defined in SVG, first by creating the filter, and then applying it to the mask’s ellipse:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;filter id="mask-blur" x="-50%" y="-50%" width="200%" height="200%"&gt;
  &lt;feGaussianBlur in="SourceGraphic" stdDeviation="10" /&gt;&lt;/filter&gt;
&lt;/defs&gt;

&lt;defs&gt;
  &lt;mask id="mask"&gt;
    &lt;ellipse cx="700" cy="480" fill="#FFF" rx="450" ry="320" filter="url(#mask-blur)"/&gt;
  &lt;/mask&gt;
&lt;/defs&gt;
</code></pre>
</div>

<p>And, while this achieved the result I was looking for, the implementation felt over-engineered for what was, after all, a simple effect. By far, the most efficient, elegant, and responsive implementation used a <code>radial-gradient</code>, achieving the effect I was hoping for with no bitmap or vector image and just a single CSS property:</p>

<div class="break-out">
<pre><code class="language-css">figure {
  mask-image: radial-gradient(ellipse farthest-corner at center center, #000 0%, transparent 75%);
}
</code></pre>
</div>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="MYYLwjj"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s review page (radial gradient mask) [forked]](https://codepen.io/smashingmag/pen/MYYLwjj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/MYYLwjj">Mike Worth’s review page (radial gradient mask) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>This approach enables me to fine-tune my <code>mask-image</code> size and even animate its colour stops, position, and size when someone interacts with its content.</p>

<div class="partners__lead-place"></div>

<h2 id="layering-multiple-masks">Layering Multiple Masks</h2>

<p>Lighting is especially important on Mike Worth’s review page, where his orangutan hero needs it to study his treasure map. Like background images, I can apply multiple mask images to create the lighting I need.</p>

<p>I could combine two mask images: a circular, semi-transparent <code>radial-gradient</code> to provide the ambiance, plus a <code>linear-gradient</code> at a 45-degree angle for the light rays. I applied them both to the <code>figure</code> element:</p>

<div class="break-out">
<pre><code class="language-css">figure {
  mask-image:
  radial-gradient(circle, rgba(255,16,76,.5) 45%, transparent 55%),
  linear-gradient(45deg, transparent 40%, #ff104c 50%, #ff104c 50%, transparent 60%);
  mask-repeat: no-repeat;
}
</code></pre>
</div>

<p>I positioned the masks individually, set the <code>radial-gradient</code> size to <code>80%</code>, and the <code>linear-gradient</code> light rays to cover the entire image:</p>

<pre><code class="language-css">figure {
  mask-position: 50% 50%, 0 0;
  mask-size: 80%, cover;
}
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="myyvJra"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s reviews page (layering multiple masks) [forked]](https://codepen.io/smashingmag/pen/myyvJra) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/myyvJra">Mike Worth’s reviews page (layering multiple masks) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>But, I needed more precise control over the position of the light rays to create the effect that they’re coming from the desk lamp. So, I replaced the <code>linear-gradient</code> with a soft-edged bitmap image:</p>

<div class="break-out">
<pre><code class="language-css">figure {
  mask-image:
    radial-gradient(circle, rgba(255,16,76,.5) 45%, transparent 55%),
  url(mask.webp);
  mask-size: 90%, cover;
}
</code></pre>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="180"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png"
			
			sizes="100vw"
			alt="A three-part image demonstrating the use of multiple masks in image editing."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Finally, to add an extra touch of realism, I added a keyframe animation &mdash; which changes the <code>mask-size</code> and creates the effect that the lamp light is flickering &mdash; and applied it to the figure:</p>

<pre><code class="language-css">@keyframes lamp-flicker {
  0%, 19.9%, 22%, 62.9%, 64%, 64.9%, 70%, 100% { 
    mask-size: 90%, auto;
  }

  20%, 21.9%, 63%, 63.9%, 65%, 69.9% { 
    mask-size: 90%, 0px;
  }
}

figure {
  animation: lamp-flicker 3s 3s linear infinite;
}
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="yyyZNba"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s review page (multiple mask-images) [forked]](https://codepen.io/smashingmag/pen/yyyZNba) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/yyyZNba">Mike Worth’s review page (multiple mask-images) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="animating-masks">Animating Masks</h2>

<p>Animating CSS masks creates exciting reveals and <strong>transitions between scenes</strong> and helps someone focus on critical content. It can also bring stories to life, making a person’s experience more engaging and immersive.</p>

<p>In this deleted scene from my design for his website, the orangutan adventurer mascot I created for Mike Worth can be seen driving across the landscape. To help tell the story that he’s being watched from afar by his archnemesis, I wanted to add a binocular-shaped mask.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="549"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png"
			
			sizes="100vw"
			alt="An orangutan adventurer mascot driving across the landscape demonstrating the use of a binocular-shaped mask"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I started by creating the binocular shape, complete with some viewfinder markers.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="549"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png"
			
			sizes="100vw"
			alt="A binocular shape"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then, I applied that image as a mask, setting its position, repeat, and size values to place it in the center of the figure element:</p>

<pre><code class="language-css">figure {
  mask-image: url(mask.svg);
  mask-position: 50% 50%;
  mask-repeat: no-repeat;
  mask-size: 85%;
}
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="jEEdPLq"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s journey animation with binocular-shaped mask [forked]](https://codepen.io/smashingmag/pen/jEEdPLq) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/jEEdPLq">Mike Worth’s journey animation with binocular-shaped mask [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>However, despite the infinitely scrolling background and the motion of the hero’s bumpy ride, the animation still felt static. So I added a subtle animation that shifts the <code>mask-position</code>, first by creating the keyframes:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes pan-mask {
  0% { mask-position: 45% 45%; } /&#42; Start lower-left &#42;/
  25% { mask-position: 55% 55%; } /&#42; Move to upper-right &#42;/
  50% { mask-position: 43% 52%; } /&#42; Shift more dramatically &#42;/
  75% { mask-position: 57% 48%; } /&#42; More variation &#42;/
  100% { mask-position: 45% 45%; } /&#42; Loop back &#42;/
}
</code></pre>
</div>

<p>Then, I applied it, along with the scrolling background animation, to the figure element:</p>

<pre><code class="language-css">/&#42; Run both animations simultaneously &#42;/
figure {
  animation:
  background-scroll 5s linear infinite,
  pan-mask 6s ease-in-out infinite alternate;
}
</code></pre>

<p>After seeing these results, I wondered whether I could connect someone to the story by linking the <code>mask-position</code> to the movement of their cursor. I added a script that selects the <code>figure</code> element, gets the cursor position relative to the viewport, and dynamically changes the <code>mask-position</code>:</p>

<pre><code class="language-javascript">&lt;script&gt;
  // Select the figure element.
  const figure = document.querySelector('figure');
  document.addEventListener('mousemove', (event) =&gt; {
  
  // Get the cursor position.
  const mouseX = event.clientX;
  const mouseY = event.clientY;
  
  // Normalise the mask-position.
  const maskX = (mouseX / window.innerWidth) &#42; 100;
  const maskY = (mouseY / window.innerHeight) &#42; 100;`
  
  // Dynamically set the mask-position.
  figure.style.maskPosition =${maskX}% ${maskY}%;
  });
&lt;/script&gt;
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="oggmXeM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s journey animation with binoculars following the cursor [forked]](https://codepen.io/smashingmag/pen/oggmXeM) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggmXeM">Mike Worth’s journey animation with binoculars following the cursor [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>With that, there was only one challenge left to complete the effect. Focusing binoculars on a distant target is rarely easy, and when the hero’s archnemesis has hands the size of a silverback gorilla, the task is even more challenging. I extended my script to blur the visible content seen through the binocular-shaped mask and then removed the filter when someone presses their keyboard’s spacebar or presses a mouse button:</p>

<pre><code class="language-javascript">&lt;script&gt;
  // When mouse button pressed, remove blur
  document.addEventListener('mousedown', () =&gt; {
    figure.style.filter = 'blur(0)';
  });
  
  // When mouse button released, reapply blur
  document.addEventListener('mouseup', () =&gt; {
    figure.style.filter = 'blur(5px)';
  });
  
  // When spacebar pressed, remove blur
  document.addEventListener('keydown', (event) =&gt; {
    if (event.key === ' ') {
      figure.style.filter = 'blur(0)';
    }
  });

  // When spacebar released, reapply blur
  document.addEventListener('keyup', (event) =&gt; {
    if (event.key === ' ') {
      figure.style.filter = 'blur(5px)';
    }
  });
&lt;/script&gt;
</code></pre>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="MYYLwEJ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s journey animation with focussing blur [forked]](https://codepen.io/smashingmag/pen/MYYLwEJ) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/MYYLwEJ">Mike Worth’s journey animation with focussing blur [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="more-masking-animation">More Masking Animation</h2>

<p>When someone using Mike Worth’s website follows the wrong path or takes a wrong turn, he ends up sinking into hot lava.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="182"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png"
			
			sizes="100vw"
			alt="A cartoon gorilla wearing a fedora and leather jacket is shown inside a circular cutout at the center of a brown background demonstrating a masking effect"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To let someone know they might’ve reached the end of their adventure, I wanted to ape the zooming-in effect I started this article with:</p>

<pre><code class="language-html">&lt;figure&gt;
  &lt;svg&gt;…&lt;/svg&gt;
&lt;/figure&gt;
</code></pre>

<p>I created a circular <code>clip-path</code> and set its default size to <code>75%</code>. Then, I defined the animation keyframes to resize the circle from 75% to 15% before attaching it to my figure with a one-second duration and a three-second delay:</p>

<pre><code class="language-css">@keyframes error {
  0% { clip-path: circle(75%); }
  100% { clip-path: circle(15%); }
}

figure {
  clip-path: circle(75%);
  animation: error 1s 3s ease-in forwards;
}
</code></pre>

<p>The animation now focuses someone’s attention on the hapless hero, before he sinks lower and lower into the bubblingly hot lava.</p>

<p>Try this yourself:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="qEEgdxy"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s error page [forked]](https://codepen.io/smashingmag/pen/qEEgdxy) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/qEEgdxy">Mike Worth’s error page [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="bringing-it-all-to-life">Bringing It All To Life</h2>

<p>Masking adds an <strong>extra dimension to web animation</strong> and makes stories more engaging and someone’s experience more compelling &mdash; all while keeping animations efficiently lightweight. Whether you’re revealing content, guiding focus, or adding more depth to a design, masks offer endless creative possibilities. So why not experiment with them in your next project? You might uncover a whole new way to bring your animations to life.</p>

<p>The end. Or is it? …</p>

<p>Mike Worth’s website will launch in June 2025, but you can <a href="https://codepen.io/collection/YwMKPb">see examples from this article on CodePen</a> now.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Andy Clarke</author><title>Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</title><link>https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/</link><pubDate>Wed, 07 May 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/</guid><description>Have you ever thought about how the limitations of early cartoon animations might relate to web design today? From looping backgrounds to minimal frame changes, these retro animation techniques have surprising parallels to modern CSS. In this article, pioneering author and web designer &lt;a href="https://stuffandnonsense.co.uk">Andy Clarke&lt;/a> shows how he applied these principles to Emmy-winning composer Mike Worth’s new website, using CSS to craft engaging and fun animations that bring his world to life.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/" />
              <title>Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</h1>
                  
                    
                    <address>Andy Clarke</address>
                  
                  <time datetime="2025-05-07T08:00:00&#43;00:00" class="op-published">2025-05-07T08:00:00+00:00</time>
                  <time datetime="2025-05-07T08:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Browser makers didn’t take long to add the movement capabilities to CSS. The simple <code>:hover</code> pseudo-class came first, and a bit later, the <code>transition</code>s between two states. Then came the ability to change states across a set of <code>@keyframes</code> and, most recently, scroll-driven animations that link keyframes to the scroll position.</p>

<p>Even with these added capabilities, <strong>CSS animations have remained relatively rudimentary</strong>. They remind me of the <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> animated series I grew up watching on TV.</p>

<p>These animated shorts lacked the budgets given to live-action or animated movies. They were also far lower than those available when William Hanna and Joseph Barbera made Tom and Jerry shorts while working for MGM Cartoons. This meant the animators needed to develop techniques to work around their cost restrictions and the technical limitations of the time.</p>

<p>They used fewer frames per second and far fewer cells. Instead of using a different image for each frame, they repeated each one several times. They reused cells as frequently as possible by zooming and overlaying additional elements to construct a new scene. They kept bodies mainly static and overlayed eyes, mouths, and legs to create the illusion of talking and walking. Instead of reducing the quality of these cartoons, these constraints created a charm often lacking in more recent, bigger-budget, and technically advanced productions.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show illustration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The simple and efficient techniques developed by Hanna-Barbera’s animators can be implemented using CSS. Modern layout tools allow web developers to layer elements. Scaleable Vector Graphics (SVG) can contain several frames, and developers needn’t resort to JavaScript; they can use CSS to change an element’s <code>opacity</code>, <code>position</code>, and <code>visibility</code>. But what are some reasons for doing this?</p>

<p>Animations bring static experiences to life. They can <strong>improve usability</strong> by guiding people’s actions and delighting or surprising them when interacting with a design. When carefully considered, animations can reinforce branding and help tell stories about a brand.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="550"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png"
			
			sizes="100vw"
			alt="Design by Andy Clarke of Mike Worth’s website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Design by Andy Clarke, Stuff & Nonsense. Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png'>Large preview</a>)
    </figcaption>
  
</figure>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="introducing-mike-worth">Introducing Mike Worth</h2>

<p>I’ve recently been working on a new website for Emmy-award-winning game composer Mike Worth. He hired me to create a bold, retro-style design that showcases his work. I used CSS animations throughout to delight and surprise his audience as they move through his website.</p>

<p>Mike loves ’80s and ’90s animation &mdash; especially Disney’s <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)"><em>Duck Tales</em></a>. Unsurprisingly, my taste in cartoons stretches back a little further to the 1960s <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear">Yogi Bear</a>.</p>

<p>So, to explain how this era of animation relates to CSS, I’ve chosen an episode of <em>The Yogi Bear Show</em>, “<a href="https://www.youtube.com/watch?v=CPnmzcmKgA0">Home Sweet Jellystone</a>,” first broadcast in 1961. In this story, Ranger Smith inherits a mansion and (spoiler alert) leaves Jellystone.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show. An illustration from “Home Sweet Jellystone episode”"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="dissecting-movement">Dissecting Movement</h2>

<p>In this episode, Hanna-Barbera’s techniques become apparent as soon as a postman arrives with a telegram for Ranger Smith. The camera pans sideways across a landscape painting by background artist Robert Gentle to create the illusion that the postman is moving.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show, author’s recreation of dissecting movement"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (Author’s recreation) (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The background loops when a scene lasts longer than a single pan of Robert Gentle’s landscape painting, with bushes and trees appearing repeatedly.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="80"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png"
			
			sizes="100vw"
			alt="A closer look at the landscape painting, with bushes and trees appearing repeatedly"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>This can be recreated using a single element and an animation that changes the position of its background image:</p>

<pre><code class="language-css">@keyframes background-scroll {
  0% { background-position: 2750px 0; }
  100% { background-position: 0 0; }
}

div {
  overflow: hidden;
  width: 100vw;
  height: 540px;
  background-image: url("…");
  background-size: 2750px 540px;
  background-repeat: repeat-x;
  animation: background-scroll 5s linear infinite;
}
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="NPPzgyq"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Yogi Bear background image scroll [forked]](https://codepen.io/smashingmag/pen/NPPzgyq) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/NPPzgyq">Yogi Bear background image scroll [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>Although beautifully executed, Robert Gentle’s background paintings were often remarkably simple. The mansion’s interior background rushes past to create the illusion of Ranger Smith dashing across it, so it needed very few details.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="314"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png"
			
			sizes="100vw"
			alt="The mansion’s interior background"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>The economy of movement</strong> was essential for producing these animated shorts cheaply and efficiently. The postman’s motorcycle bounces, and only his head position and facial expressions change, which adds a subtle hint of realism.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="131"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png"
			
			sizes="100vw"
			alt="An example of several drawings where only head position and facial expressions change to add movement on a motorcycle"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Sequence shortened. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Likewise, only Ranger Smith’s facial expression and leg positions change throughout his walk cycle as he dashes through his mansion. The rest of his body stays static.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="131"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png"
			
			sizes="100vw"
			alt="An example of several drawings where only Ranger Smith’s facial expression and leg positions change during a walk"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Sequence shortened. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In a discarded scene from my design for his website, the orangutan adventurer mascot I created for Mike Worth can be seen driving across the landscape.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="400"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png"
			
			sizes="100vw"
			alt="An illustration from Mike Worth’s website"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I drew directly from <strong>Hanna-Barbera’s bouncing and scrolling technique</strong> for this scene by using two keyframe animations: <code>background-scroll</code> and <code>bumpy-ride</code>. The infinitely scrolling background works just like before:</p>

<pre><code class="language-css">@keyframes background-scroll {
  0% { background-position: 960px 0; }
  100% { background-position: 0 0; }
}
</code></pre>

<p>I created the appearance of his bumpy ride by animating changes to the keyframes’ <code>translate</code> values:</p>

<pre><code class="language-css">@keyframes bumpy-ride {
  0% { translate: 0 0; }
  10% { translate: 0 -5px; }
  20% { translate: 0 3px; }
  30% { translate: 0 -3px; }
  40% { translate: 0 5px; }
  50% { translate: 0 -10px; }
  60% { translate: 0 4px; }
  70% { translate: 0 -2px; }
  80% { translate: 0 7px; }
  90% { translate: 0 -4px; }
  100% { translate: 0 0; }
}

figure {
  /&#42; ... &#42;/
  animation: background-scroll 5s linear infinite;
}

img {
  /&#42; ... &#42;/
  animation: bumpy-ride 1.5s infinite ease-in-out;
}
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ByyVZrB"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth background image scroll [forked]](https://codepen.io/smashingmag/pen/ByyVZrB) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ByyVZrB">Mike Worth background image scroll [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>As Michelle Barker wrote about <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">here at Smashing Magazine</a> back in 2021:</p>

<blockquote>“When working with motion on the web, it’s important to consider that not everyone experiences it the same way. What might feel smooth and slick to some might be annoying or distracting to others &mdash; or worse, induce feelings of sickness or even cause seizures.”<br /><br />&mdash; Michelle Barker</blockquote>

<p>You can prevent that from happening by turning off animations when someone has chosen reduced motion in their browser by using the <code>prefers-reduced-motion</code> media query:</p>

<pre><code class="language-css">@media (prefers-reduced-motion: reduce) {
  &#42; { animation: none !important; }
}
</code></pre>

<div class="partners__lead-place"></div>

<h2 id="reusing-elements">Reusing Elements</h2>

<p>Since each episode’s budget and production time were limited, William Hanna and Joseph Barbera created a streamlined process for producing their animations. They used as few as 2,000 individual drawings and just a few background paintings per episode, often reusing them on several episodes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="322"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png"
			
			sizes="100vw"
			alt="A background painting with highlighted trees from the Yogi Bear show"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Watch the episode and you’ll see these trees appear over and over again throughout “Home Sweet Jellystone.” Behind Yogi and Boo-Boo on the track, in the bushes, and scaled up in this close-up of Boo-Boo:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="196"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show paintings with the same trees"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The animators also frequently <strong>layered foreground elements onto these background paintings</strong> to create a variety of new scenes:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="293"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png"
			
			sizes="100vw"
			alt="The Yogi Bear Show paintings with layered foreground elements"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      On the left: The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. On the right: Author’s edit.(<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In my deleted scene from Mike Worth’s website, I introduced these rocks into the foreground to add depth to the animation:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="400"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png"
			
			sizes="100vw"
			alt="Painting from Mike Worth’s website with rocks on the background"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If I were using bitmap images, this would require just one additional image:</p>

<pre><code class="language-html">&lt;figure&gt;
  &lt;img id="bumpy-ride" src="..." alt="" /&gt;
  &lt;img id="apes-rock" src="..." alt="" /&gt;
&lt;/figure&gt;
</code></pre>

<pre><code class="language-css">figure {
  position: relative; 

  #bumpy-ride { ... }

  #apes-rock {
    position: absolute;
    width: 960px;
    left: calc(50% - 480px);
    bottom: 0;
  }
}
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="xbbzraj"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth layered animation [forked]](https://codepen.io/smashingmag/pen/xbbzraj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/xbbzraj">Mike Worth layered animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>Had I continued developing this scene, I might’ve added a slower scrolling animation to those rocks to introduce a parallax effect for even greater realism.</p>

<h2 id="looping-frames-create-movement">Looping Frames Create Movement</h2>

<p>To meet their limited budget and production schedules, Hanna Barbera’s animators carefully planned their animations and cleverly <strong>only animated specific elements</strong>. While heads and facial expressions make characters talk and their legs change to make them walk, most characters’ bodies remain relatively static. So, throughout this entire scene of Ranger Smith walking and talking his way across his cabin, only his face and legs are animated:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="157"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png"
			
			sizes="100vw"
			alt="Looping Frames of Ranger Smith walking, where only his face and legs are animated"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Likewise, when the ranger reads his telegram, only his eyes and mouth move:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="400"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png"
			
			sizes="100vw"
			alt="Looping Frames of Ranger Smith reading a telegram"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If you’ve wondered why both Ranger Smith and Yogi Bear wear collars and neckties, it’s so the line between their animated heads and faces and static bodies is obscured:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="600"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png"
			
			sizes="100vw"
			alt="Yogi Bear wearing a collar"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Author’s recreation. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aSVG%20delivers%20incredible%20performance%20and%20also%20offers%20fantastic%20flexibility%20when%20animating%20elements.%20The%20ability%20to%20embed%20one%20SVG%20inside%20another%20and%20to%20manipulate%20groups%20and%20other%20elements%20using%20CSS%20makes%20it%20ideal%20for%20animations.%0a&url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-1-classic-cartoons-inspire-css%2f">
      
SVG delivers incredible performance and also offers fantastic flexibility when animating elements. The ability to embed one SVG inside another and to manipulate groups and other elements using CSS makes it ideal for animations.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>I replicated how Hanna-Barbera made Ranger Smith and other characters’ mouths move by first including a group that contains the ranger’s body and head, which remain static throughout. Then, I added six more groups, each containing one frame of his mouth moving:</p>

<pre><code class="language-svg">&lt;svg&gt;
  &lt;!-- static elements --&gt;
  &lt;g&gt;...&lt;/g&gt;

  &lt;!-- animation frames --&gt;
  &lt;g class="frame-1"&gt;...&lt;/g&gt;
  &lt;g class="frame-2"&gt;...&lt;/g&gt;
  &lt;g class="frame-3"&gt;...&lt;/g&gt;
  &lt;g class="frame-4"&gt;...&lt;/g&gt;
  &lt;g class="frame-5"&gt;...&lt;/g&gt;
  &lt;g class="frame-6"&gt;...&lt;/g&gt;
&lt;/svg&gt;
</code></pre>

<p>I used CSS custom properties to define the speed at which characters’ mouths move and how many frames are in the animation:</p>

<pre><code class="language-css">:root {
  --animation-duration: 1s;
  --frame-count: 6;
}
</code></pre>

<p>Then, I applied a keyframe animation to show and hide each frame:</p>

<pre><code class="language-css">@keyframes ranger-talking {
  0% { visibility: visible; }
  16.67% { visibility: hidden; }
  100% { visibility: hidden; }
}

[class&#42;="frame"] {
  visibility: hidden;
  animation: ranger-talking var(--animation-duration) infinite;
}
</code></pre>

<p>Before finally setting a delay, which makes each frame visible at the correct time:</p>

<div class="break-out">
<pre><code class="language-css">.frame-1 {
  animation-delay: calc(var(--animation-duration) &#42; 0 / var(--frame-count));
}

/&#42; ... &#42;/

.frame-6 {
  animation-delay: calc(var(--animation-duration) &#42; 5 / var(--frame-count));
}
</code></pre>
</div>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="QwwxgJE"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Ranger Smith talking [forked]](https://codepen.io/smashingmag/pen/QwwxgJE) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/QwwxgJE">Ranger Smith talking [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<div class="partners__lead-place"></div>

<h2 id="working-with-mike-worth">Working With Mike Worth</h2>

<p>When Mike Worth and I sat down to discuss working together, we both understood that there would be neither the budget nor the time to create a short animated cartoon for his website. We also knew that video would be the correct format for a fully animated production, but we were keen to explore how CSS could bring what would’ve otherwise been static images to life. So, this begs the question of <em>why</em> and <em>when</em> to use CSS animations.</p>

<h3 id="ambient-animations">Ambient Animations</h3>

<blockquote>Subtle ambient animations contribute to a website’s atmosphere and help with storytelling without distracting from its content or functionality.</blockquote>

<p>For Mike Worth’s about-page illustration, I shone shafts of light onto a stone tablet to add depth to an otherwise flat image. Inside my SVG, I added a <code>path</code> for the light and reduced its <code>opacity</code> to <code>.25</code>:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;svg&gt;
  &lt;!-- ... --&gt;
  &lt;path class="light-shaft" fill="#F1DCA9" opacity=".25" d=""/&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>I then defined an SVG filter to blur the edges of my light shafts and linked it to my <code>path</code>:</p>

<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;filter id="light-shaft" width="100%" height="100%" x="0" y="0" filterUnits="objectBoundingBox"&gt;
  &lt;feGaussianBlur in="SourceGraphic" stdDeviation="20"/&gt;
  &lt;/filter&gt;
&lt;/defs&gt;

&lt;svg&gt;
  &lt;!-- ... --&gt;
  &lt;path class="light-shaft" filter="url(#light-shaft)" … /&gt;
&lt;/svg&gt;
</code></pre>
</div>

<p>Finally, I added a subtle ambient animation that rotates the light shafts and creates a more natural feel:</p>

<pre><code class="language-css">@keyframes shaft-rotate {
  0% { rotate: 2deg; }
  50% { rotate: -2deg; }
  100% { rotate: 2deg; }
}

.light-shaft {
  animation: shaft-rotate 20s infinite;
  transform-origin: 100% 0;
}
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="bNNKROE"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s about page animation [forked]](https://codepen.io/smashingmag/pen/bNNKROE) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNNKROE">Mike Worth’s about page animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<p>Can you throw more light on Mike’s navigation?</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ZYYRyVJ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Light up Mike Worth about page [forked]](https://codepen.io/smashingmag/pen/ZYYRyVJ) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ZYYRyVJ">Light up Mike Worth about page [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h3 id="animations-on-interactions">Animations On Interactions</h3>

<blockquote>In the same way that a <code>:hover</code> pseudo-class provides valuable visual feedback when someone interacts with an element, CSS animations can create a deeper connection between people and a design.</blockquote>

<p>I included an Easter egg interaction on Mike Worth’s review page illustration. The big red button turns the desk lamp on and off, much to the consternation of Mike’s orangutan mascot, who’s trying to study his map. To implement this, I applied a <code>data-</code> attribute to the SVG illustration:</p>

<pre><code class="language-svg">&lt;svg … data-lights="lights-on"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>

<p>And added a red button for any curious visitor to press:</p>

<div class="break-out">
<pre><code class="language-html">&lt;a href="javascript:void(0);" id="light-switch" title="Lights on/off"&gt;
  &lt;path fill="#0a0908" d="..."/&gt;
  &lt;ellipse fill="#9c1621" … /&gt;
  &lt;path fill="#fff" d="..."/&gt;
&lt;/a&gt;
</code></pre>
</div>

<p>When someone presses that red button, the light goes out, which is made possible by changing the value of the SVG’s <code>data-</code> attribute from <code>lights-on</code> to <code>lights-off</code>.</p>

<p>Several elements within the illustration light up when the desk lamp is on. To make this happen, I applied a class value to those specific items:</p>

<pre><code class="language-svg">&lt;path class="lamp-glow" /&gt;
</code></pre>

<p>And used the data-attribute value to toggle the glow on and off when someone presses the lamp’s button:</p>

<pre><code class="language-css">[data-lights="lights-on"] .lamp-glow {
  opacity: 1;
  transition: opacity .25s linear;
}

[data-lights="lights-off"] .lamp-glow {
  opacity: .25;
  transition: opacity .25s linear;
}
</code></pre>

<p>When someone turns the lamp on, it flickers at what appears to be random intervals. I first applied a class value to the flickering elements:</p>

<pre><code class="language-svg">&lt;path class="lamp-flicker" /&gt;
</code></pre>

<p>Then, I hid them when the lamp was turned off:</p>

<pre><code class="language-css">[data-lights="lights-off"] .lamp-flicker {
  visibility: hidden;
}
</code></pre>

<p>Finally, I created a keyframe animation that flickers the lamp light’s <code>opacity</code> at seemingly random intervals:</p>

<div class="break-out">
<pre><code class="language-css">@keyframes lamp-flicker {
  0%, 19.9%, 22%, 62.9%, 64%, 64.9%, 70%, 100% { opacity: 1; }
  20%, 21.9%, 63%, 63.9%, 65%, 69.9% { opacity: .5; }
}

[data-lights="lights-on"] .lamp-flicker {
  animation: lamp-flicker 3s 3s linear infinite;
}
</code></pre>
</div>

<p>Animations can also tempt people to venture deeper into a design, so I made the crystal skull on the desk vibrate to hint at something more to discover:</p>

<pre><code class="language-html">&lt;a href="/easter-egg"&gt;
  &lt;g id="crystal-skull"&gt;...&lt;/g&gt;
&lt;/a&gt;
</code></pre>

<div class="break-out">
<pre><code class="language-css">@keyframes crystal-skull-vibrate {
  0% { translate: 0 0; }
  20% { translate: -2px 2px; }
  40% { translate: -2px -2px; }
  60% { translate: 2px 2px; }
  80% { translate: 2px -2px; }
  100% { translate: 0 0; }
}

&#35;crystal-skull:hover {
  animation: crystal-skull-vibrate .5s ease 0s infinite normal forwards;
}
</code></pre>
</div>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="YPPvQBg"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s review page animation [forked]](https://codepen.io/smashingmag/pen/YPPvQBg) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/YPPvQBg">Mike Worth’s review page animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h3 id="animations-tell-stories">Animations Tell Stories</h3>

<blockquote>When carefully considered, animations can reflect a brand’s identity and help tell its story.</blockquote>

<p>Mike Worth’s brand is high-energy &mdash; just like his personality &mdash; and the story he tells about his work as a video game composer is engaging and playful. Mike wanted every interaction with his website to bring his personality to the screen.</p>

<p>Should someone get lost along their journey, they’ll end up on Mike’s 404 page, where his hero has a sinking feeling. While Mike’s orangutan adventurer slips deeper and deeper into the quicksand, animated bubbles rise:</p>

<pre><code class="language-svg">&lt;g&gt;
  &lt;circle class="four-oh-dear-bubble" ... /&gt;
  &lt;circle class="four-oh-dear-bubble" ... /&gt;
  &lt;circle class="four-oh-dear-bubble" ... /&gt;
  &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>

<div class="break-out">
<pre><code class="language-css">@keyframes four-oh-dear-bubbles {
  0% { 
    animation-timing-function: ease-in; 
    opacity: 1; 
    transform: translateY(45px);
  }
  24% { 
    opacity: 1; 
  }
  40% { 
    animation-timing-function: ease-in; 
    translate: 0 24px;
  }
  65% { 
    animation-timing-function: ease-in;
    translate: 0 12px;
  }
  82% { 
    animation-timing-function: ease-in;
    translate: 0 6px;
  }
  93% { 
    animation-timing-function: ease-in;
    translate: 0 4px;
  }
  25%, 55%, 75%, 87% { 
    animation-timing-function: ease-out;
    translate: 0 0;
  }
  100% { 
    animation-timing-function: ease-out;
    opacity: 1; 
    translate: 0 0;
  }
}

.four-oh-dear-bubble {
  animation: four-oh-dear-bubbles 2s ease 0s infinite alternate forwards; }
</code></pre>
</div>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ZYYRyPX"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Mike Worth’s 404 page animation [forked]](https://codepen.io/smashingmag/pen/ZYYRyPX) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ZYYRyPX">Mike Worth’s 404 page animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption>
</figure>

<h2 id="bringing-it-all-to-life">Bringing It All To Life</h2>

<p>Just as the animators at Hanna-Barbera turned technical limitations into their signature style, CSS animations enable web professionals to craft characterful experiences. By layering elements, looping frames, and applying subtle movement, you can inject personality into a design while improving someone’s experience.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png"
			
			sizes="100vw"
			alt="Yogi Bear’s painting"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In my design for Mike Worth’s website, <strong>animation isn’t just for decoration; it tells a compelling story about him and his work</strong>. Every movement reflects his brand identity and makes his website an extension of his creative world.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aThink%20beyond%20movement%20the%20next%20time%20you%20reach%20for%20a%20CSS%20animation.%20Consider%20emotions,%20identity,%20and%20mood,%20too.%20After%20all,%20a%20well-considered%20animation%20can%20do%20more%20than%20catch%20someone%e2%80%99s%20eye.%20It%20can%20capture%20their%20imagination.%0a&url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-1-classic-cartoons-inspire-css%2f">
      
Think beyond movement the next time you reach for a CSS animation. Consider emotions, identity, and mood, too. After all, a well-considered animation can do more than catch someone’s eye. It can capture their imagination.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="450"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png"
			
			sizes="100vw"
			alt="The end painting"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Mike Worth’s website will launch in June 2025, but you can <a href="https://codepen.io/collection/YwMKPb">see examples from this article on CodePen</a> now.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Gabriel Shoyombo</author><title>Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module?</title><link>https://www.smashingmagazine.com/2025/05/masonry-css-should-grid-evolve-stand-aside-new-module/</link><pubDate>Tue, 06 May 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/masonry-css-should-grid-evolve-stand-aside-new-module/</guid><description>There were duelling proposals floating around for adding support for masonry-style layouts in CSS. In one corner is a proposal that extends the existing CSS Grid specification. In the other corner is a second proposal that sets up masonry as a standalone module. Well, not until recently. Now, there are three proposals with Apple WebKit’s “Item Flow” as the third option. The first two sides make strong points, and the third one merges them into one, all of which you will learn about in this article.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/masonry-css-should-grid-evolve-stand-aside-new-module/" />
              <title>Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module?</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module?</h1>
                  
                    
                    <address>Gabriel Shoyombo</address>
                  
                  <time datetime="2025-05-06T13:00:00&#43;00:00" class="op-published">2025-05-06T13:00:00+00:00</time>
                  <time datetime="2025-05-06T13:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>You’ve got a Pinterest-style layout to build, but you’re tired of JavaScript. Could CSS finally have the answer? Well, for a beginner, taking a look at the pins on your Pinterest page, you might be convinced that the CSS grid layout is enough, but not until you begin to build do you realise <code>display: grid</code> with additional tweaks is less than enough. In fact, Pinterest built its layout with JavaScript, but how cool would it be if it were just CSS? If there were a CSS display property that gave such a layout without any additional JavaScript, how awesome would that be?</p>

<p>Maybe there is. The CSS grid layout has an <strong>experimental masonry value</strong> for <code>grid-template-rows</code>. The masonry layout is an irregular, flowing grid. Irregular in the sense that, instead of following a rigid grid pattern with spaces left after shorter pieces, the items in the next row of a masonry layout rise to fill the spaces on the masonry axis. It’s the dream for portfolios, image galleries, and social feeds &mdash; designs that thrive on organic flow. But here’s the catch: while this experimental feature exists (think Firefox Nightly with a flag enabled), it’s not the seamless solution you might expect, thanks to limited browser support and some rough edges in its current form.</p>

<p>Maybe there isn’t. CSS lacks native masonry support, forcing developers to use hacks or JavaScript libraries like <a href="https://masonry.desandro.com">Masonry.js</a>. Developers with a good design background have expressed their criticism about the CSS grid form of masonry, with <a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/">Rachel</a> highlighting that <strong>masonry’s organic flow contrasts with Grid’s strict two-dimensional structure</strong>, potentially confusing developers expecting Grid-like behaviour or <a href="https://ishadeed.com/article/should-masonry-be-part-of-css-grid/">Ahmad Shadeed</a> fussing about how it makes the grid layout more complex than it should be, potentially overwhelming developers who value Grid’s clarity for structured layouts. <a href="https://css-tricks.com/css-masonry-css-grid/">Geoff</a> also echoes Rachel Andrew’s concern that <em>“teaching and learning grid to get to understand masonry behaviour unnecessarily lumps two different formatting contexts into one,”</em> complicating education for designers and developers who rely on clear mental models.</p>

<p>Perhaps there might be hope. The Apple WebKit team just sprung up a new contender, which claims not only to merge the pros of grid and masonry into a unified system shorthand but also includes flexbox concepts. Imagine the best of three CSS layout systems in one.</p>

<p>Given these complaints and criticisms &mdash; and a new guy in the game &mdash; the question is:</p>

<blockquote>Should CSS Grid expand to handle Masonry, or should a new, dedicated module take over, or should <code>item-flow</code> just take the reins?</blockquote>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="the-state-of-masonry-in-css-today">The State Of Masonry In CSS Today</h2>

<p>Several developers have attempted to create workarounds to achieve a masonry layout in their web applications using CSS Grid with manual row-span hacks, CSS Columns, and JavaScript libraries. Without native masonry, developers often turn to Grid hacks like this: a <code>grid-auto-rows</code> trick paired with JavaScript to fake the flow. It works &mdash; sort of &mdash; but the cracks show fast.</p>

<p>For instance, the example below relies on JavaScript to measure each item’s height after rendering, calculate the number of 10px rows (plus gaps) the item should span while setting <code>grid-row-end</code> dynamically, and use event listeners to adjust the layout upon page load and window resize.</p>

<div class="break-out">
<pre><code class="language-html">/&#42; HTML &#42;/
&lt;div class="masonry-grid"&gt;
  &lt;div class="masonry-item"&gt;&lt;img src="image1.jpg" alt="Image 1"&gt;&lt;/div&gt;
  &lt;div class="masonry-item"&gt;&lt;p&gt;Short text content here.&lt;/p&gt;&lt;/div&gt;
  &lt;div class="masonry-item"&gt;&lt;img src="image2.jpg" alt="Image 2"&gt;&lt;/div&gt;
  &lt;div class="masonry-item"&gt;&lt;p&gt;Longer text content that spans multiple lines to show height variation.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
</code></pre>
</div>

<div class="break-out">
<pre><code class="language-css">/&#42; CSS &#42;/
.masonry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /&#42; Responsive columns &#42;/
  grid-auto-rows: 10px; /&#42; Small row height for precise spanning &#42;/
  grid-auto-flow: column; /&#42; Fills columns left-to-right &#42;/
  gap: 10px; /&#42; Spacing between items &#42;/
}

.masonry-item {
  /&#42; Ensure content doesn’t overflow &#42;/
  overflow: hidden;
}

.masonry-item img {
  width: 100%;
  height: auto;
  display: block;
}

.masonry-item p {
  margin: 0;
  padding: 10px;
}
</code></pre>
</div>

<div class="break-out">
<pre><code class="language-javascript">// JavaScript

function applyMasonry() {
  const grid = document.querySelector('.masonry-grid');
  const items = grid.querySelectorAll('.masonry-item');

  items.forEach(item =&gt; {
    // Reset any previous spans
    item.style.gridRowEnd = 'auto';

    // Calculate the number of rows to span based on item height
    const rowHeight = 10; 
    const gap = 10; 
    const itemHeight = item.getBoundingClientRect().height;
    const rowSpan = Math.ceil((itemHeight + gap) / (rowHeight + gap));

    // Apply the span
    item.style.gridRowEnd = `span ${rowSpan}`;
  });
}

// Run on load and resize
window.addEventListener('load', applyMasonry);
window.addEventListener('resize', applyMasonry);
</code></pre>
</div>

<p>This Grid hack gets us close to a masonry layout &mdash; items stack, gaps fill, and it looks decent enough. But let’s be real: it’s not there yet. The code sample above, unlike native <code>grid-template-rows: masonry</code> (which is experimental and only exists on Firefox Nightly), relies on JavaScript to calculate spans, defeating the “no JavaScript” dream. The JavaScript logic works by recalculating spans on resize or content change. As <a href="https://css-tricks.com/native-css-masonry-layout-in-css-grid/">Chris Coyier</a> noted in his critique of similar hacks, this can lead to lag on complex pages.</p>

<p>Also, the logical DOM order might not match the visual flow, a concern <a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/">Rachel Andrew</a> raised about masonry layouts generally. Finally, if images load slowly or content shifts (e.g., lazy-loaded media), the spans need recalculation, risking layout jumps. It’s not really the ideal hack; I’m sure you’d agree.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aDevelopers%20need%20a%20smooth%20experience,%20and%20ergonomically%20speaking,%20hacking%20Grid%20with%20scripts%20is%20a%20mental%20juggling%20act.%20It%20forces%20you%20to%20switch%20between%20CSS%20and%20JavaScript%20to%20tweak%20a%20layout.%20A%20native%20solution,%20whether%20Grid-powered%20or%20a%20new%20module,%20has%20to%20nail%20effortless%20responsiveness,%20neat%20rendering,%20and%20a%20workflow%20that%20does%20not%20make%20you%20break%20your%20tools.%0a&url=https://smashingmagazine.com%2f2025%2f05%2fmasonry-css-should-grid-evolve-stand-aside-new-module%2f">
      
Developers need a smooth experience, and ergonomically speaking, hacking Grid with scripts is a mental juggling act. It forces you to switch between CSS and JavaScript to tweak a layout. A native solution, whether Grid-powered or a new module, has to nail effortless responsiveness, neat rendering, and a workflow that does not make you break your tools.

    </a>
  </p>
  <div class="pull-quote__quotation">
    <div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
  </div>
</blockquote>

<p>That’s why this debate matters &mdash; our daily grind demands it.</p>

<div class="partners__lead-place"></div>

<h2 id="option-1-extending-css-grid-for-masonry">Option 1: Extending CSS Grid For Masonry</h2>

<p>One way forward is to strengthen the CSS Grid with masonry powers. As of this writing, CSS grids have been extended to accommodate masonry. <code>grid-template-rows: masonry</code> is a draft of CSS Grid Level 3 that is currently experimental in Firefox Nightly. The columns of this layout will remain as a grid axis while the row takes on masonry. The child elements are then laid out item by item along the rows, as with the grid layout’s automatic placement. With this layout, items flow vertically, respecting column tracks but not row constraints.</p>

<p>This option leaves Grid as your go-to layout system but allows it to handle the flowing, gap-filling stacks we crave.</p>

<div class="break-out">
<pre><code class="language-css">.masonry-grid {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry;
}
</code></pre>
</div>

<p>First off, the grid-masonry style builds on CSS Grid’s familiarity and robust tooling (e.g., DevTools support). As a front-end developer, there’s a chance you’ve played with <code>grid-template-columns</code> or <code>grid-area</code>, so you’re halfway up the learning matrix. Masonry only extends the existing capabilities, eliminating the need to learn a whole new syntax from scratch. Also, Grid’s robust tooling comes along with Chrome DevTools’ grid overlay or Firefox’s layout inspector, removing the need for JavaScript hacks.</p>

<p>Not so fast: there are limitations. Grid’s specifications already include properties like <code>align-content</code> and <code>grid-auto-flow</code>. Stacking masonry on the list risks turning it into a labyrinth.</p>

<p>Then there are <strong>the edge cases</strong>. What happens when you want an item to span multiple columns and flow masonry-style? Or when gaps between items don’t align across columns? The specs are still foggy here, and early tests hint at bugs like items jumping unpredictably if content loads dynamically. This issue could break layouts, especially on responsive designs. <strong>The browser compatibility issue</strong> also exists. It’s still experimental, and even with polyfills, it does not work on other browsers except Firefox Nightly. Not something you’d want to try in your next client’s project, right?</p>

<h2 id="option-2-a-standalone-masonry-module">Option 2: A Standalone Masonry Module</h2>

<p>What if we had a <code>display: masonry</code> approach instead? Indulge me for a few minutes. This isn’t just wishful thinking. Early CSS Working Group chats have floated the idea, and it’s worth picturing how it could improve layouts. Let’s dive into the vision, <em>how</em> it might work, and <em>what</em> it gains or loses in the process.</p>

<p>Imagine a layout system that doesn’t lean on Grid’s rigid tracks or Flexbox’s linear flow but instead thrives on vertical stacking with a horizontal twist. The goal? A clean slate for masonry’s signature look: items cascading down columns, filling gaps naturally, no hacks required. Inspired by murmurs in CSSWG discussions and the Chrome team’s alternative proposal, this module would <strong>prioritise fluidity over structure</strong>, giving designers a tool that feels as intuitive as the layouts they’re chasing. Think Pinterest but without JavaScript scaffolding.</p>

<p>Here’s the pitch: a <code>display</code> value named <code>masonry</code> kicks off a flow-based system where items stack vertically by default, adjusting horizontally to fit the container. You’d control the direction and spacing with simple properties like the following:</p>

<pre><code class="language-css">.masonry {
  display: masonry;
  masonry-direction: column;
  gap: 1rem;
}
</code></pre>

<p>Want more control? Hypothetical extras like <code>masonry-columns: auto</code> could mimic Grid’s <code>repeat(auto-fill, minmax())</code>, while <code>masonry-align: balance</code> might even out column lengths for a polished look. It’s less about precise placement (Grid’s strength) and more about letting content breathe and flow, adapting to whatever screen size is thrown at it. The big win here is a clean break from Grid’s rigid order. A standalone module keeps them distinct: <strong>Grid for order, Masonry for flow</strong>. No more wrestling with Grid properties that don’t quite fit; you get a system tailored to the job.</p>

<p>Of course, it’s not all smooth sailing. A brand-new spec means starting from zero. Browser vendors would need to rally behind it, which can be slow. Also, it might lead to <strong>confusion of choice</strong>, with developers asking questions like: <em>“Do I use Grid or Masonry for this gallery?”</em> But hear me out: This proposed module might muddy the waters before it clears them, but after the water is clear, it’s safe for use by all and sundry.</p>

<h2 id="item-flow-a-unified-layout-resolution">Item Flow: A Unified Layout Resolution</h2>

<p>In March 2025, <a href="https://webkit.org/blog/16587/item-flow-part-1-a-new-unified-concept-for-layout/">Apple’s WebKit team proposed Item Flow</a>, a new system that unifies concepts from Flexbox, Grid, and masonry into a single set of properties. Rather than choosing between enhancing Grid or creating a new masonry module, Item Flow merges their strengths, replacing <code>flex-flow</code> and <code>grid-auto-flow</code> with a shorthand called <code>item-flow</code>. This system introduces four longhand properties:</p>

<ul>
<li><code>item-direction</code><br />
Controls flow direction (e.g., <code>row</code>, <code>column</code>, <code>row-reverse</code>).</li>
<li><code>item-wrap</code><br />
Manages wrapping behaviour (e.g., <code>wrap</code>, <code>nowrap</code>, <code>wrap-reverse</code>).</li>
<li><code>item-pack</code><br />
Determines packing density (e.g., <code>sparse</code>, <code>dense</code>, <code>balance</code>).</li>
<li><code>item-slack</code><br />
Adjusts tolerance for layout adjustments, allowing items to shrink or shift to fit.</li>
</ul>

<p>Item Flow aims to make masonry a natural outcome of these properties, not a separate feature. For example, a masonry layout could be achieved with:</p>

<pre><code class="language-css">.container {
  display: grid; /&#42; or flex &#42;/
  item-flow: column wrap dense;

  /&#42; long hand version &#42;/
  item-direction: column;
  item-wrap: wrap;
  item-pack: dense;

  gap: 1rem;
}
</code></pre>

<p>This setup allows items to flow vertically, wrap into columns, and pack tightly, mimicking masonry’s organic arrangement. The dense packing option, inspired by Grid’s <code>auto-flow: dense</code>, reorders items to minimise gaps, while <code>item-slack</code> could fine-tune spacing for visual balance.</p>

<p>Item Flow’s promise lies in its <strong>wide use case</strong>. It enhances Grid and Flexbox with features like <code>nowrap</code> for Grid or <code>balance</code> packing for Flexbox, addressing long-standing developer wishlists. However, the proposal is still in discussion, and <a href="https://grok.com/chat/caeffa67-49d2-478e-834d-611d9a7bf204">properties like <code>item-slack</code> face naming debates due to clarity issues for non-native English speakers</a>.</p>

<p>The downside? Item Flow is a <strong>future-facing concept</strong>, and it has not yet been implemented in browsers as of April 2025. Developers must wait for standardisation and adoption, and the CSS Working Group is still gathering feedback.</p>

<div class="partners__lead-place"></div>

<h2 id="what-s-the-right-path">What’s The Right Path?</h2>

<p>While there is no direct answer to that question, the masonry debate hinges on balancing simplicity, performance, and flexibility. Extending the Grid with masonry is tempting but risks <strong>overcomplicating</strong> an already robust system. A standalone <code>display: masonry</code> module offers clarity but <strong>adds to CSS’s learning curve.</strong> Item Flow, the newest contender, proposes a unified system that could make masonry a natural extension of Grid and Flexbox, potentially putting the debate to rest at last.</p>

<p>Each approach has trade-offs:</p>

<ul>
<li><strong>Grid with Masonry</strong>: Familiar but potentially clunky, with accessibility and spec concerns.</li>
<li><strong>New Module</strong>: Clean and purpose-built, but requires learning new syntax.</li>
<li><strong>Item Flow</strong>: Elegant and versatile but not yet available, with ongoing debates over naming and implementation.</li>
</ul>

<p>Item Flow’s ability to enhance existing layouts while supporting masonry makes it a compelling option, but its success depends on browser adoption and community support.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So, where do we land after all this? The masonry showdown boils down to three paths: the extension of masonry into CSS Grid, a standalone module for masonry, or Item Flow. Now, the question is, <strong>will CSS finally free us from JavaScript for masonry</strong>, or are we still dreaming?</p>

<p>Grid’s teasing us with a taste, and a standalone module’s whispering promises &mdash; but the finish line’s unclear, and WebKit swoops in with a killer merge shorthand, Item Flow. Browser buy-in, community push, and a few more spec revisions might tell us. For now, it’s your move &mdash; test, tweak, and weigh in. The answer’s coming, one layout at a time.</p>

<h3 id="references">References</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/">Native CSS Masonry Layout in CSS Grid</a>” by Rachel Andrew</li>
<li>“<a href="https://ishadeed.com/article/css-grid-masonry/">Should Masonry be part of CSS Grid?</a>” by Ahmad Shadeed</li>
<li>“<a href="https://css-tricks.com/css-masonry-css-grid/">CSS Masonry &amp; CSS Grid</a>” by Geoff Graham</li>
<li>“<a href="https://css-irl.info/masonry-in-css/">Masonry? In CSS?!</a>” by Michelle Barker</li>
<li>“<a href="https://css-tricks.com/native-css-masonry-layout-in-css-grid/">Native CSS Masonry Layout in CSS Grids</a>” by Chris Coyier</li>
<li>“<a href="https://webkit.org/blog/16587/item-flow-part-1-a-new-unified-concept-for-layout/">Item Flow Part 1: A Unified Concept for Layout</a>” by WebKit</li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Brecht De Ruyte</author><title>Transitioning Top-Layer Entries And The Display Property In CSS</title><link>https://www.smashingmagazine.com/2025/01/transitioning-top-layer-entries-display-property-css/</link><pubDate>Wed, 29 Jan 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/01/transitioning-top-layer-entries-display-property-css/</guid><description>We are getting spoiled with so many new features involving animations with CSS, from &lt;a href="https://www.smashingmagazine.com/2024/12/introduction-css-scroll-driven-animations/">scroll-driven animations&lt;/a> to &lt;a href="https://www.smashingmagazine.com/2023/12/view-transitions-api-ui-animations-part1/">view transitions&lt;/a>, and plenty of things in between. But it’s not always the big features that make our everyday lives easier; sometimes, it’s those ease-of-life features that truly enhance our projects. In this article, Brecht De Ruyte puts two features on display: &lt;code>@starting-style&lt;/code> and &lt;code>transition-behavior&lt;/code> &amp;mdash; two properties that are absolutely welcome additions to your everyday work with CSS animations.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/01/transitioning-top-layer-entries-display-property-css/" />
              <title>Transitioning Top-Layer Entries And The Display Property In CSS</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Transitioning Top-Layer Entries And The Display Property In CSS</h1>
                  
                    
                    <address>Brecht De Ruyte</address>
                  
                  <time datetime="2025-01-29T10:00:00&#43;00:00" class="op-published">2025-01-29T10:00:00+00:00</time>
                  <time datetime="2025-01-29T10:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Animating from and to <code>display: none;</code> was something we could only achieve with JavaScript to change classes or create other hacks. The reason why we couldn’t do this in CSS is explained in the new <a href="https://www.w3.org/TR/css-transitions-2/#defining-before-change-style">CSS Transitions Level 2 specification</a>:</p>

<blockquote>“In Level 1 of this specification, transitions can only start during a style change event for elements that have a defined before-change style established by the previous style change event. That means a transition could not be started on an element that was not being rendered for the previous style change event.”</blockquote>

<p>In simple terms, this means that we couldn’t start a transition on an element that is hidden or that has just been created.</p>

<h3 id="what-does-transition-behavior-allow-discrete-do">What Does <code>transition-behavior: allow-discrete</code> Do?</h3>

<p><code>allow-discrete</code> is a bit of a strange name for a CSS property value, right? We are going on about transitioning <code>display: none</code>, so why isn’t this named <code>transition-behavior: allow-display</code> instead? The reason is that this does a bit more than handling the CSS <code>display</code> property, as there are other “discrete” properties in CSS. A simple rule of thumb is that discrete properties do not transition but usually flip right away between two states. Other examples of discrete properties are <code>visibility</code> and <code>mix-blend-mode</code>. I’ll include an example of these at the end of this article.</p>

<p>To summarise, setting the <code>transition-behavior</code> property to <code>allow-discrete</code> allows us to tell the browser it can swap the values of a discrete property (e.g., <code>display</code>, <code>visibility</code>, and <code>mix-blend-mode</code>) at the 50% mark instead of the 0% mark of a transition.</p>

<h3 id="what-does-starting-style-do">What Does <code>@starting-style</code> Do?</h3>

<p>The <code>@starting-style</code> rule defines the styles of an element right before it is rendered to the page. This is highly needed in combination with <code>transition-behavior</code> and this is why:</p>

<p>When an item is added to the DOM or is initially set to <code>display: none</code>, it needs some sort of “starting style” from which it needs to transition. To take the example further, <a href="https://css-tricks.com/clarifying-the-relationship-between-popovers-and-dialogs/">popovers and dialog elements</a> are added to a top layer which is a layer that is outside of your document flow, you can kind of look at it as a sibling of the <code>&lt;html&gt;</code> element in your page’s structure. Now, when opening this dialog or popover, they get created inside that top layer, so they don’t have any styles to start transitioning from, which is why we set <code>@starting-style</code>. Don’t worry if all of this sounds a bit confusing. The demos might make it more clearly. The important thing to know is that we can give the browser something to start the animation with since it otherwise has nothing to animate from.</p>

<h3 id="a-note-on-browser-support">A Note On Browser Support</h3>

<p>At the moment of writing, the <code>transition-behavior</code> is available in Chrome, Edge, Safari, and Firefox. It’s the same for <code>@starting-style</code>, but Firefox currently does not support animating from <code>display: none</code>. But remember that everything in this article can be perfectly used as a progressive enhancement.</p>

<p>Now that we have the theory of all this behind us, let’s get practical. I’ll be covering three use cases in this article:</p>

<ul>
<li>Animating from and to <code>display: none</code> in the DOM.</li>
<li>Animating dialogs and popovers entering and exiting the top layer.</li>
<li>More “discrete properties” we can handle.

<br /></li>
</ul>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="animating-from-and-to-display-none-in-the-dom">Animating From And To <code>display: none</code> In The DOM</h2>

<p>For the first example, let’s take a look at <code>@starting-style</code> alone. I created this demo purely to explain the magic. Imagine you want two buttons on a page to add or remove list items inside of an unordered list.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050954730"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>This could be your starting HTML:</p>

<pre><code class="language-html">&lt;button type="button" class="btn-add"&gt;
  Add item
&lt;/button&gt;
&lt;button type="button" class="btn-remove"&gt;
  Remove item
&lt;/button&gt;
&lt;ul role="list"&gt;&lt;/ul&gt;
</code></pre>

<p>Next, we add actions that add or remove those list items. This can be any method of your choosing, but for demo purposes, I quickly wrote a bit of JavaScript for it:</p>

<pre><code class="language-javascript">document.addEventListener("DOMContentLoaded", () =&gt; {
  const addButton = document.querySelector(".btn-add");
  const removeButton = document.querySelector(".btn-remove");
  const list = document.querySelector('ul[role="list"]');

  addButton.addEventListener("click", () =&gt; {
    const newItem = document.createElement("li");
    list.appendChild(newItem);
  });

  removeButton.addEventListener("click", () =&gt; {
    if (list.lastElementChild) {
      list.lastElementChild.classList.add("removing");
      setTimeout(() =&gt; {
        list.removeChild(list.lastElementChild);
      }, 200);
    }
  });
});
</code></pre>

<p>When clicking the <code>addButton</code>, an empty list item gets created inside of the unordered list. When clicking the <code>removeButton</code>, the last item gets a new <code>.removing</code> class and finally gets taken out of the DOM after 200ms.</p>

<p>With this in place, we can write some CSS for our items to animate the removing part:</p>

<pre><code class="language-css">ul {
    li {
      transition: opacity 0.2s, transform 0.2s;

      &.removing {
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050955824"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>This is great! Our <code>.removing</code> animation is already looking perfect, but what we were looking for here was a way to animate the entry of items coming inside of our DOM. For this, we will need to define those starting styles, as well as the final state of our list items.</p>

<p>First, let’s update the CSS to have the final state inside of that list item:</p>

<pre><code class="language-css">ul {
    li {
      opacity: 1;
      transform: translate(0, 0);
      transition: opacity 0.2s, transform 0.2s;

      &.removing {
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>

<p>Not much has changed, but now it’s up to us to let the browser know what the starting styles should be. We could set this the same way we did the <code>.removing</code> styles like so:</p>

<pre><code class="language-css">ul {
    li {
      opacity: 1;
      transform: translate(0, 0);
      transition: opacity 0.2s, transform 0.2s;

      @starting-style {
        opacity: 0;
        transform: translate(0, 50%);
      }

      &.removing {
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>

<p>Now we’ve let the browser know that the <code>@starting-style</code> should include zero opacity and be slightly nudged to the bottom using a <code>transform</code>. The final result is something like this:</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050956394"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>But we don’t need to stop there! We could use different animations for entering and exiting. We could, for example, update our starting style to the following:</p>

<pre><code class="language-css">@starting-style {
  opacity: 0;
  transform: translate(0, -50%);
}
</code></pre>

<p>Doing this, the items will enter from the top and exit to the bottom. See the full example in this CodePen:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="XJroPgg"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [@starting-style demo - up-in, down-out [forked]](https://codepen.io/smashingmag/pen/XJroPgg) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/XJroPgg">@starting-style demo - up-in, down-out [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<h3 id="when-to-use-transition-behavior-allow-discrete">When To Use <code>transition-behavior: allow-discrete</code></h3>

<p>In the previous example, we added and removed items from our DOM. In the next demo, we will show and hide items using the CSS <code>display</code> property. The basic setup is pretty much the same, except we will add eight list items to our DOM with the <code>.hidden</code> class attached to it:</p>

<pre><code class="language-html">  &lt;button type="button" class="btn-add"&gt;
    Show item
  &lt;/button&gt;
  &lt;button type="button" class="btn-remove"&gt;
    Hide item
  &lt;/button&gt;

&lt;ul role="list"&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>

<p>Once again, for demo purposes, I added a bit of JavaScript that, this time, removes the <code>.hidden</code> class of the next item when clicking the <code>addButton</code> and adds the <code>hidden</code> class back when clicking the <code>removeButton</code>:</p>

<div class="break-out">
<pre><code class="language-javascript">document.addEventListener("DOMContentLoaded", () =&gt; {
  const addButton = document.querySelector(".btn-add");
  const removeButton = document.querySelector(".btn-remove");
  const listItems = document.querySelectorAll('ul[role="list"] li');

  let activeCount = 0;

  addButton.addEventListener("click", () =&gt; {
    if (activeCount &lt; listItems.length) {
      listItems[activeCount].classList.remove("hidden");
      activeCount++;
    }
  });

  removeButton.addEventListener("click", () =&gt; {
    if (activeCount &gt; 0) {
      activeCount--;
      listItems[activeCount].classList.add("hidden");
    }
  });
});
</code></pre>
</div>

<p>Let’s put together everything we learned so far, add a <code>@starting-style</code> to our items, and do the basic setup in CSS:</p>

<pre><code class="language-css">ul {
    li {
      display: block;
      opacity: 1;
      transform: translate(0, 0);
      transition: opacity 0.2s, transform 0.2s;

      @starting-style {
        opacity: 0;
        transform: translate(0, -50%);
      }

      &.hidden {
        display: none;
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>

<p>This time, we have added the <code>.hidden</code> class, set it to <code>display: none</code>, and added the same <code>opacity</code> and <code>transform</code> declarations as we previously did with the <code>.removing</code> class in the last example. As you might expect, we get a nice fade-in for our items, but removing them is still very abrupt as we set our items directly to <code>display: none</code>.</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050961069"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>This is where the <code>transition-behavior</code> property comes into play. To break it down a bit more, let’s remove the <code>transition</code> property shorthand of our previous CSS and open it up a bit:</p>

<pre><code class="language-css">ul {
    li {
      display: block;
      opacity: 1;
      transform: translate(0, 0);
      transition-property: opacity, transform;
      transition-duration: 0.2s;
    }
  }
</code></pre>

<p>All that is left to do is transition the <code>display</code> property and set the <code>transition-behavior</code> property to <code>allow-discrete</code>:</p>

<pre><code class="language-css">ul {
    li {
      display: block;
      opacity: 1;
      transform: translate(0, 0);
      transition-property: opacity, transform, display;
      transition-duration: 0.2s;
      transition-behavior: allow-discrete;
      /&#42; etc. &#42;/
    }
  }
</code></pre>

<p>We are now animating the element from <code>display: none</code>, and the result is exactly as we wanted it:</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050961316"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>We can use the <code>transition</code> shorthand property to make our code a little less verbose:</p>

<div class="break-out">
<pre><code class="language-css">transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete;
</code></pre>
</div>

<p>You can add <code>allow-discrete</code> in there. But if you do, take note that if you declare a shorthand transition after <code>transition-behavior</code>, it will be overruled. So, instead of this:</p>

<pre><code class="language-css">transition-behavior: allow-discrete;
transition: opacity 0.2s, transform 0.2s, display 0.2s;
</code></pre>

<p>…we want to declare <code>transition-behavior</code> <em>after</em> the <code>transition</code> shorthand:</p>

<pre><code class="language-css">transition: opacity 0.2s, transform 0.2s, display 0.2s;
transition-behavior: allow-discrete;
</code></pre>

<p>Otherwise, the <code>transition</code> shorthand property overrides <code>transition-behavior</code>.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="GgKPXda"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [@starting-style and transition-behavior: allow-discrete [forked]](https://codepen.io/smashingmag/pen/GgKPXda) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/GgKPXda">@starting-style and transition-behavior: allow-discrete [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<div class="partners__lead-place"></div>

<h2 id="animating-dialogs-and-popovers-entering-and-exiting-the-top-layer">Animating Dialogs And Popovers Entering And Exiting The Top Layer</h2>

<p>Let’s add a few use cases with dialogs and popovers. Dialogs and popovers are good examples because they get added to the top layer when opening them.</p>

<h3 id="what-is-that-top-layer">What Is That Top Layer?</h3>

<p>We’ve already likened the “top layer” to a sibling of the <code>&lt;html&gt;</code> element, but you might also think of it as a special layer that sits above everything else on a web page. It&rsquo;s like a transparent sheet that you can place over a drawing. Anything you draw on that sheet will be visible on top of the original drawing.</p>

<p>The original drawing, in this example, is the DOM. This means that the top layer is out of the document flow, which provides us with a few benefits. For example, as I stated before, dialogs and popovers are added to this top layer, and that makes perfect sense because they should always be on top of everything else. No more <code>z-index: 9999</code>!</p>

<p>But it’s more than that:</p>

<ul>
<li><strong><code>z-index</code> is irrelevant</strong>: Elements on the top layer are always on top, regardless of their <code>z-index</code> value.</li>
<li><strong>DOM hierarchy doesn’t matter</strong>: An element’s position in the DOM doesn’t affect its stacking order on the top layer.</li>
<li><strong>Backdrops</strong>: We get access to a new <code>::backdrop</code> pseudo-element that lets us style the area between the top layer and the DOM beneath it.</li>
</ul>

<p>Hopefully, you are starting to understand the importance of the top layer and how we can transition elements in and out of it as we would with popovers and dialogues.</p>

<h3 id="transitioning-the-dialog-element-in-the-top-layer">Transitioning The Dialog Element In The Top Layer</h3>

<p>The following HTML contains a button that opens a <code>&lt;dialog&gt;</code> element, and that <code>&lt;dialog&gt;</code> element contains another button that closes the <code>&lt;dialog&gt;</code>. So, we have one button that opens the <code>&lt;dialog&gt;</code> and one that closes it.</p>

<div class="break-out">
<pre><code class="language-html">&lt;button class="open-dialog" data-target="my-modal"&gt;Show dialog&lt;/button&gt;

&lt;dialog id="my-modal"&gt;
  &lt;p&gt;Hi, there!&lt;/p&gt;
  &lt;button class="outline close-dialog" data-target="my-modal"&gt;
    close
  &lt;/button&gt;
&lt;/dialog&gt;
</code></pre>
</div>

<p>A lot is happening in HTML with <a href="https://utilitybend.com/blog/an-update-on-invokers-invoker-commands-in-html">invoker commands</a> that will make the following step a bit easier, but for now, let’s add a bit of JavaScript to make this modal actually work:</p>

<pre><code class="language-javascript">// Get all open dialog buttons.
const openButtons = document.querySelectorAll(".open-dialog");
// Get all close dialog buttons.
const closeButtons = document.querySelectorAll(".close-dialog");

// Add click event listeners to open buttons.
openButtons.forEach((button) =&lt; {
  button.addEventListener("click", () =&lt; {
    const targetId = button.getAttribute("data-target");
    const dialog = document.getElementById(targetId);
    if (dialog) {
      dialog.showModal();
    }
  });
});

// Add click event listeners to close buttons.
closeButtons.forEach((button) =&lt; {
  button.addEventListener("click", () =&lt; {
    const targetId = button.getAttribute("data-target");
    const dialog = document.getElementById(targetId);
    if (dialog) {
      dialog.close();
    }
  });
});
</code></pre>

<p>I’m using the following styles as a starting point. Notice how I’m styling the <code>::backdrop</code> as an added bonus!</p>

<pre><code class="language-css">dialog {
  padding: 30px;
  width: 100%;
  max-width: 600px;
  background: #fff;
  border-radius: 8px;
  border: 0;
  box-shadow: 
    rgba(0, 0, 0, 0.3) 0px 19px 38px,
    rgba(0, 0, 0, 0.22) 0px 15px 12px;
    
  &::backdrop {
    background-image: linear-gradient(
      45deg in oklab,
      oklch(80% 0.4 222) 0%,
      oklch(35% 0.5 313) 100%
    );
  }
}
</code></pre>

<p>This results in a pretty hard transition for the entry, meaning it’s not very smooth:</p>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050964394"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>Let’s add transitions to this dialog element and the backdrop. I’m going a bit faster this time because by now, you likely see the pattern and know what’s happening:</p>

<pre><code class="language-css">dialog {
  opacity: 0;
  translate: 0 30%;
  transition-property: opacity, translate, display;
  transition-duration: 0.8s;

  transition-behavior: allow-discrete;
  
  &[open] {
    opacity: 1;
    translate: 0 0;

    @starting-style {
      opacity: 0;
      translate: 0 -30%;
    }
  }
}
</code></pre>

<p>When a dialog is open, the browser slaps an <code>open</code> attribute on it:</p>

<pre><code class="language-html">&lt;dialog open&gt; ... &lt;/dialog&gt;
</code></pre>

<p>And that’s something else we can target with CSS, like <code>dialog[open]</code>. So, in this case, we need to set a <code>@starting-style</code> for when the dialog is in an <code>open</code> state.</p>

<p>Let’s add a transition for our backdrop while we’re at it:</p>

<pre><code class="language-css">dialog {
  /&#42; etc. &#42;/
  &::backdrop {
    opacity: 0;
    transition-property: opacity;
    transition-duration: 1s;
  }

  &[open] {
    /&#42; etc. &#42;/
    &::backdrop {
      opacity: 0.8;

      @starting-style {
        opacity: 0;
      }
    }
  }
}
</code></pre>


<figure class="video-embed-container">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1050964896"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>Now you’re probably thinking: <em>A-ha! But you should have added the <code>display</code> property and the <code>transition-behavior: allow-discrete</code> on the backdrop!</em></p>

<p>But no, that is not the case. Even if I would change my backdrop pseudo-element to the following CSS, the result would stay the same:</p>

<pre><code class="language-css"> &::backdrop {
    opacity: 0;
    transition-property: opacity, display;
    transition-duration: 1s;
    transition-behavior: allow-discrete;
  }
</code></pre>

<p>It turns out that we are working with a <code>::backdrop</code> and when working with a <code>::backdrop</code>, we’re implicitly also working with the CSS <code>overlay</code> property, which specifies whether an element appearing in the top layer is currently rendered in the top layer.</p>

<p>And <code>overlay</code> just so happens to be another discrete property that we need to include in the <code>transition-property</code> declaration:</p>

<pre><code class="language-css">dialog {
  /&#42; etc. &#42;/

&::backdrop {
  transition-property: opacity, display, overlay;
  /&#42; etc. &#42;/
}
</code></pre>

<p>Unfortunately, this is currently only supported in Chromium browsers, but it can be perfectly used as a progressive enhancement.</p>

<p>And, yes, we need to add it to the <code>dialog</code> styles as well:</p>

<pre><code class="language-css">dialog {
  transition-property: opacity, translate, display, overlay;
  /&#42; etc. &#42;/

&::backdrop {
  transition-property: opacity, display, overlay;
  /&#42; etc. &#42;/
}
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="pvzqOGe"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Dialog: starting-style, transition-behavior, overlay [forked]](https://codepen.io/smashingmag/pen/pvzqOGe) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvzqOGe">Dialog: starting-style, transition-behavior, overlay [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<p>It’s pretty much the same thing for a popover instead of a dialog. I’m using the same technique, only working with popovers this time:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="emObLxe"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Popover transition with @starting-style [forked]](https://codepen.io/smashingmag/pen/emObLxe) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/emObLxe">Popover transition with @starting-style [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<div class="partners__lead-place"></div>

<h2 id="other-discrete-properties">Other Discrete Properties</h2>

<p>There are a few other discrete properties besides the ones we covered here. If you remember the second demo, where we transitioned some items from and to <code>display: none</code>, the same can be achieved with the <code>visibility</code> property instead. This can be handy for those cases where you want items to preserve space for the element’s box, even though it is invisible.</p>

<p>So, here’s the same example, only using <code>visibility</code> instead of <code>display</code>.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="LEPMJqX"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Transitioning the visibility property [forked]](https://codepen.io/smashingmag/pen/LEPMJqX) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LEPMJqX">Transitioning the visibility property [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<p>The CSS <strong><code>mix-blend-mode</code></strong> property is another one that is considered discrete. To be completely honest, I can’t find a good use case for a demo. But I went ahead and created a somewhat trite example where two <code>mix-blend-mode</code>s switch right in the middle of the transition instead of right away.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="bNbOxZp"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Transitioning mix-blend-mode [forked]](https://codepen.io/smashingmag/pen/bNbOxZp) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNbOxZp">Transitioning mix-blend-mode [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>That’s an overview of how we can transition elements in and out of the top layer! In an ideal world, we could get away without needing a completely new property like <code>transition-behavior</code> just to transition otherwise “un-transitionable” properties, but here we are, and I’m glad we have it.</p>

<p>But we also got to learn about <code>@starting-style</code> and how it provides browsers with a set of styles that we can apply to the start of a transition for an element that’s in the top layer. Otherwise, the element has nothing to transition from at first render, and we’d have no way to transition them smoothly in and out of the top layer.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Cosima Mielke</author><title>New Front-End Features For Designers In 2025</title><link>https://www.smashingmagazine.com/2024/12/new-front-end-features-for-designers-in-2025/</link><pubDate>Tue, 31 Dec 2024 12:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/12/new-front-end-features-for-designers-in-2025/</guid><description>Searching for the most flexible front-end workflows and toolkits, it’s easy to forget how powerful some of the fundamentals on the web have become these days. This post is a journey through new front-end features and what they are capable of.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/12/new-front-end-features-for-designers-in-2025/" />
              <title>New Front-End Features For Designers In 2025</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>New Front-End Features For Designers In 2025</h1>
                  
                    
                    <address>Cosima Mielke</address>
                  
                  <time datetime="2024-12-31T12:00:00&#43;00:00" class="op-published">2024-12-31T12:00:00+00:00</time>
                  <time datetime="2024-12-31T12:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Component-specific styling, styling parents based on their children, relative colors — the web platform is going through <strong>exciting times</strong>, and many things that required JavaScript in the past can today be achieved with <strong>one simple line of HTML and CSS</strong>.</p>

<p>As we are moving towards 2025, it’s a good time to revisit some of the <strong>incredible new technologies</strong> that are broadly available and supported in modern browsers today. Let’s dive right in and explore how they can simplify your day-to-day work and help you build modern UI components.</p>

<h3 id="table-of-contents">Table of Contents</h3>

<p>Below you’ll find quick jumps to topics you may be interested in, or <a href="#css-container-queries-and-style-queries">skip the table of contents</a>.</p>

<ul class="toc-components">
  <li><a href="#anchor-positioning-for-tooltips-and-popovers">anchor-positioning</a></li>
  <li><a href="#auto-field-sizing-for-forms">auto field-sizing</a></li>
  <li><a href="#css-container-queries-and-style-queries">container queries</a></li>
  <li><a href="#reliable-dialog-and-popover"><code>&lt;dialog&gt;</code></a></li>
  <li><a href="#exclusive-accordions">exclusive accordions</a></li>
  <li><a href="#making-focus-visible"><code>:focus-visible</code></a></li>
  <li><a href="#styling-parents-based-on-children"><code>:has</code></a></li>
  <li><a href="#making-hidden-content-searchable"><code>hidden=until-found</code></a></li>
  <li><a href="#high-definition-colors-with-oklch-and-oklab">high-definition colors</a></li>
  <li><a href="#styling-groups-within-select-menus"><code>&lt;hr&gt;</code> in select</a></li>
  <li><a href="#the-right-virtual-keyboard-on-mobile"><code>inputmode</code></a></li>
  <li><a href="#interpolate-between-values-for-type-and-spacing"><code>min()</code>, <code>max()</code>, <code>clamp()</code></a></li>
  <li><a href="#relative-colors-in-css">relative colors</a></li>
  <li><a href="#responsive-html-video-and-audio">responsive videos</a></li>
  <li><a href="#smooth-scrolling-behavior">scroll behavior</a></li>
  <li><a href="#simpler-snapping-for-scrollable-containers">scroll snap</a></li>
  <li><a href="#no-more-typographic-orphans-and-widows"><code>text-wrap: balance</code></a></li>
  <li><a href="#live-and-late-validation"><code>:user-valid</code> and <code>:user-invalid</code></a></li>
  <li><a href="#smooth-transitions-with-the-view-transitions-api">View Transitions API</a></li>
</ul>

<h2 id="css-container-queries-and-style-queries">CSS Container Queries And Style Queries</h2>

<p><strong>Component-specific styling</strong>? What has long sounded like a dream to any developer, is slowly but surely becoming reality. Thanks to container queries, we can now query the width and style of the container in which components live.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/docs/css-ui/style-queries">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="521"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png"
			
			sizes="100vw"
			alt="CSS Container Queries And Style Queries"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://developer.chrome.com/docs/css-ui/style-queries'>Style queries</a> give us more logical control of styles in CSS. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>As Una Kravets points out in her <a href="https://developer.chrome.com/docs/css-ui/style-queries">introduction to style queries</a>, this currently only works with CSS custom property values, but there are already some real-world use cases where style queries shine: They come in particularly handy when you have a <strong>reusable component with multiple variations</strong> or when you don’t have control over all of your styles but need to apply changes in certain cases.</p>

<p>If you want to dive deeper into what’s possible with container style queries and the things we can — maybe — look forward to in the future, also be sure to take a look at <a href="https://css-tricks.com/digging-deeper-into-container-style-queries/">Geoff Graham’s post</a>. He dug deep into the <strong>more nuanced aspects of style queries</strong> and summarized the things that stood out to him.</p>

<h2 id="no-more-typographic-orphans-and-widows">No More Typographic Orphans And Widows</h2>

<p>We all know those headlines where the last word breaks onto a new line and stands there alone, breaking the visual and looking, well, odd. Of course, there’s the good ol’ <code>&lt;br&gt;</code> to break the text manually or a <code>&lt;span&gt;</code> to <strong>divide the content into different parts</strong>. But have you heard of <code>text-wrap: balance</code> already?</p>














<figure class="
  
  
  ">
  
    <a href="https://ishadeed.com/article/css-text-wrap-balance/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="369"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png"
			
			sizes="100vw"
			alt="No More Typographic Orphans And Widows"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      No more odd line breaks, thanks to <a href='https://ishadeed.com/article/css-text-wrap-balance/'><code>text-wrap: balance</code></a>. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>By applying the <code>text-wrap: balance</code> property, the browser will automatically <strong>calculate the number of words</strong> and divide them equally between two lines — perfect for <strong>page titles</strong>, card titles, tooltips, modals, and FAQs, for example. Ahmad Shadeed wrote a helpful <a href="https://ishadeed.com/article/css-text-wrap-balance/">guide to <code>text-wrap: balance</code></a> in which he takes a detailed look at the property and how it can help you make your headlines look more consistent.</p>

<p>When dealing with large blocks of text, such as <strong>paragraphs</strong>, you might want to look into <code>text-wrap: pretty</code> to <a href="https://developer.chrome.com/blog/css-text-wrap-pretty">prevent orphans on the last line</a>.</p>

<h2 id="auto-field-sizing-for-forms">Auto Field-Sizing For Forms</h2>

<p>Finding just the right size for an input field usually involves a lot of guesswork — or JavaScript — to count characters and increase the field’s height or width as a user enters text. CSS <code>field-sizing</code> is here to change that. With field-sizing, we can <strong>auto-grow inputs and text areas</strong>, but also auto-shrink short select menus, so the form always fits content size perfectly. All we need to make it happen is one line of CSS.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/docs/css-ui/css-field-sizing">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="496"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png"
			
			sizes="100vw"
			alt="Auto Field-Sizing For Forms"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://developer.chrome.com/docs/css-ui/css-field-sizing'>Auto field-sizing</a> allows us to automatically grow or shrink inputs and text areas depending on the content size. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Adam Argyle summarized <a href="https://developer.chrome.com/docs/css-ui/css-field-sizing">everything you need to know about field-sizing</a>, exploring in detail how <code>field-sizing</code> affects different <code>&lt;form&gt;</code> elements. To prevent your input fields from becoming too small or too large, it is also a good idea to insert some <strong>additional styles</strong> that keep them in shape. Adam shares a code snippet that you can copy-and-paste right away.</p>

<h2 id="making-hidden-content-searchable">Making Hidden Content Searchable</h2>

<p>Accordions are a popular UI pattern, but they come with a caveat: The content inside the collapsed sections is impossible to search with <strong>find-in-page search</strong>. By using the <code>hidden=until-found</code> attribute and the <code>beforematch</code> event, we can solve the problem and even make the content accessible to search engines.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/docs/css-ui/hidden-until-found">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="475"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png"
			
			sizes="100vw"
			alt="Making Hidden Content Searchable"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://developer.chrome.com/docs/css-ui/hidden-until-found'><code>hidden=until-found</code></a> makes hidden content in accordions searchable. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>As Joey Arhar explains in his <a href="https://developer.chrome.com/docs/css-ui/hidden-until-found">guide to making collapsed content searchable</a>, you can <strong>replace the styles that hide the section</strong> with the <code>hidden=until-found</code> attribute. If your page also has another state that needs to be kept in sync with whether or not your section is revealed, he recommends adding a <code>beforematch</code> event listener. It will be fired on the <code>hidden=until-found</code> element right before the element is revealed by the browser.</p>

<h2 id="styling-groups-within-select-menus">Styling Groups Within Select Menus</h2>

<p>It’s a small upgrade for the <code>&lt;select&gt;</code> element, but a mighty one: We can now add <code>&lt;hr&gt;</code> into the list of select options, and they will <a href="https://developer.chrome.com/blog/hr-in-select">appear as separators</a> to help <strong>visually break up the options</strong> in the list.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/blog/hr-in-select">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="565"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png"
			
			sizes="100vw"
			alt="Styling Groups Within Select Menus"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Perfect when your select menu has a lot of options: It’s now possible to <a href='https://developer.chrome.com/blog/hr-in-select'>group content</a>. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If you want to refine things further, also be sure to take a look at <code>&lt;optgroup&gt;</code>. The HTML element lets you <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup">group options within a <code>&lt;select&gt;</code> element</a> by adding a <strong>subheading</strong> for each group.</p>

<h2 id="simpler-snapping-for-scrollable-containers">Simpler Snapping For Scrollable Containers</h2>

<p>Sometimes, you need a quick and easy way to make an element a scrollable container. CSS scroll snap makes it possible. The CSS feature enables us to create a <strong>well-controlled scrolling experience</strong> that lets users precisely swipe left and right and snap to a specific item in the container. No JavaScript required.</p>














<figure class="
  
  
  ">
  
    <a href="https://ishadeed.com/article/css-scroll-snap/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="546"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png"
			
			sizes="100vw"
			alt="Simpler Snapping For Scalable Containers"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Have you ever wished there was a CSS feature that makes it easy to create a scrollable container? <a href='https://ishadeed.com/article/css-scroll-snap/'>CSS scroll snap</a> is here to help. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Ahmad Shadeed wrote a <a href="https://ishadeed.com/article/css-scroll-snap/">practical guide</a> that walks you step by step through the process of setting up a container with scroll snap. You can use it to create <strong>image galleries</strong>, avatar lists, or other components where you want a user to scroll and snap through the content, whether it’s horizontally or vertically.</p>

<h2 id="anchor-positioning-for-tooltips-and-popovers">Anchor Positioning For Tooltips And Popovers</h2>

<p>Whether you use it for footnotes, tooltips, connector lines, visual cross-referencing, or dynamic labels in charts, the CSS Anchor Positioning API enables us to <strong>natively position elements relative to other elements</strong>, known as <em>anchors</em>.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/blog/anchor-positioning-api">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="449"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png"
			
			sizes="100vw"
			alt="Anchor Positioning For Tooltips And Popovers"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The <a href='https://developer.chrome.com/blog/anchor-positioning-api'>CSS Anchor Positioning API</a> helps us create layered interfaces without any third-party libraries. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In her <a href="https://developer.chrome.com/blog/anchor-positioning-api">introduction to the CSS Anchor Positioning API</a>, Una Kravets summarized in detail how anchor positioning works. She takes a closer look at the mechanism behind anchor positioning, how to tether to one and <strong>multiple anchors</strong>, and how to size and position an anchor-positioned element based on the size of its anchor. Browser support is still limited, so you might want to use the API with some precautions. Una’s guide includes what to watch out for.</p>

<div class="partners__lead-place"></div>

<h2 id="high-definition-colors-with-oklch-and-oklab">High-Definition Colors With OKLCH And OKLAB</h2>

<p>With high-definition colors with LCH, okLCH, LAB, and okLAB that give us access to <strong>50% more colors</strong>, the times of RGB/HSL might be over soon. To get you familiar with the new color spaces, Vitaly wrote a quick <a href="https://www.linkedin.com/posts/vitalyfriedman_colors-design-css-activity-7062428890362699776-LdLe/">overview of what you need to know</a>.</p>














<figure class="
  
  
  ">
  
    <a href="https://www.linkedin.com/posts/vitalyfriedman_colors-design-css-activity-7062428890362699776-LdLe/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="631"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png"
			
			sizes="100vw"
			alt="High-Definition Colors With OKLCH And OKLAB"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The times of RGB/HSL might be over soon. Say hello to <a href='https://www.linkedin.com/posts/vitalyfriedman_colors-design-css-activity-7062428890362699776-LdLe/'>high-definition colors</a>. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Both OKLCH and OKLAB are based on human perception and can specify any color the human eye can see. While OKLAB works best for rich gradients, OKLCH is a fantastic fit for color palettes in <strong>design systems</strong>. OKLCH/OKLAB colors are fully supported in Chrome, Edge, Safari, Firefox, and Opera. Figma doesn’t support them yet.</p>

<h2 id="relative-colors-in-css">Relative Colors In CSS</h2>

<p>Let’s say you have a background color and want to reduce its <strong>luminosity</strong> by 25%, or you want to use a complementary color without having to calculate it yourself. The relative color syntax (RCS) makes it possible to create a new color based on a given color.</p>














<figure class="
  
  
  ">
  
    <a href="https://smashing-freiburg-2024.netlify.app/24-relative-color/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="483"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png"
			
			sizes="100vw"
			alt="Relative Colors In CSS"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://smashing-freiburg-2024.netlify.app/24-relative-color/'>Relative colors</a> allow us to automatically calculate a new color based on an existing color. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To derive and compute a new color, we can use the <code>from</code> keyword for color functions (<code>color()</code>, <code>hsl()</code>, <code>oklch()</code>, etc.) to <strong>modify the values of the input color</strong>. Adam Argyle shares some <a href="https://smashing-freiburg-2024.netlify.app/24-relative-color/">code snippets</a> of what this looks like in practice, or check the <a href="https://drafts.csswg.org/css-color-5/#relative-colors">spec</a> for more details.</p>

<h2 id="smooth-transitions-with-the-view-transitions-api">Smooth Transitions With The View Transitions API</h2>

<p>There are a number of use cases where a smooth visual transition can make the user experience more engaging. When a thumbnail image on a product listing page transitions into a full-size image on the product detail page, for example, or when you have a <strong>fixed navigation bar</strong> that stays in place as you navigate from one page to another. The View Transitions API helps us create seamless visual transitions between different views on a site.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/docs/web-platform/view-transitions">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="506"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png"
			
			sizes="100vw"
			alt="Smooth Transitions With The View Transitions API"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The <a href='https://developer.chrome.com/docs/web-platform/view-transitions'>View Transitions API</a> creates seamless visual transitions between different views. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>View transitions can be triggered not only on a single document but also <strong>between two different documents</strong>. Both rely on the same principle: The browser takes snapshots of the old and new states, the DOM gets updated while rendering is suppressed, and the transitions are powered by CSS Animations. The only difference lies in how you trigger them, as Bramus Van Damme explains in his <a href="https://developer.chrome.com/docs/web-platform/view-transitions">guide to the View Transitions API</a>. A good <a href="https://www.debugbear.com/blog/view-transitions-spa-without-framework">alternative to single page apps</a> that often rely on heavy JavaScript frameworks.</p>

<h2 id="exclusive-accordions">Exclusive Accordions</h2>

<p>The ‘exclusive accordion’ is a variation of the accordion component. It only allows one disclosure widget to be open at the same time, so when a user opens a new one, the one that is already open will be <strong>closed automatically</strong> to save space. Thanks to CSS, we can now create the effect without a single line of JavaScript.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.chrome.com/docs/css-ui/exclusive-accordion">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="495"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png"
			
			sizes="100vw"
			alt="Exclusive Accordions"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      An <a href='https://developer.chrome.com/docs/css-ui/exclusive-accordion'>exclusive accordion</a> automatically closes a disclosure widget when a new one is opened. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To build an exclusive accordion, we need to add a <code>name</code> attribute to the <code>&lt;details&gt;</code> elements. When this attribute is used, all <code>&lt;details&gt;</code> elements that have the same <code>name</code> value form a <strong>semantic group</strong> and behave as an exclusive accordion. Bramus Van Damme <a href="https://developer.chrome.com/docs/css-ui/exclusive-accordion">summarized in detail how it works</a>.</p>

<h2 id="live-and-late-validation">Live And Late Validation</h2>

<p>When we use <code>:valid</code> and <code>:invalid</code> to apply styling based on a user’s input, there’s a downside: a form control that is <strong>required and empty</strong> will match <code>:invalid</code> even if a user hasn’t started interacting with it yet. To prevent this from happening, we usually had to write stateful code that keeps track of input a user has changed. But not anymore.</p>














<figure class="
  
  
  ">
  
    <a href="https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="461"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png"
			
			sizes="100vw"
			alt="Live And Late Validation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes'><code>:user-valid</code> and <code>:user-invalid</code></a> improve the user experience of input validation. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>With <code>:user-valid</code> and <code>:user-invalid</code>, we now have a <a href="https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes">native CSS solution that handles all of this automatically</a>. Contrary to <code>:valid</code> and <code>:invalid</code>, the <code>:user-valid</code> and <code>:user-invalid</code> pseudo-classes give users feedback about mistakes only <strong>after they have changed the input</strong>. <code>:user-valid</code> and <code>:user-invalid</code> work with input, select, and textarea controls.</p>

<h2 id="smooth-scrolling-behavior">Smooth Scrolling Behavior</h2>

<p>Imagine you have a scrolling box and a series of links that <strong>target an anchored position</strong> inside the box. When a user clicks on one of the links, it will take them to the content section inside the scrolling box — with a rather abrupt jump. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior"><code>scroll-behavior</code> property</a> makes the scrolling transition a lot smoother, only with CSS.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="419"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png"
			
			sizes="100vw"
			alt="Smooth Scrolling Behavior"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior'><code>scroll-behavior</code></a> sets the behavior for a scrolling box when scrolling is triggered by the navigation. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>When setting the <code>scroll-behavior</code> value to <code>smooth</code>, the scrolling box will scroll in a <strong>smooth fashion</strong> using a user-agent-defined easing function over a user-agent-defined period of time. Of course, you can also use <code>scroll-behavior: auto</code>, and the scrolling box will scroll instantly.</p>

<h2 id="making-focus-visible">Making Focus Visible</h2>

<p>Focus styles are essential to help keyboard users navigate a page. However, for mouse users, it can be irritating when a focus ring appears around a button or link as they click on it. <code>:focus-visible</code> is here to help us create the best experience for both user groups: It displays <strong>focus styles for keyboard users</strong> and hides them for mouse users.</p>














<figure class="
  
  
  ">
  
    <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="379"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png"
			
			sizes="100vw"
			alt="Making Focus Visible"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible'><code>:focus-visible</code></a> shows focus styles only when necessary. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><code>:focus-visible</code> applies while an element matches the <code>:focus</code> pseudo-class and the User Agent determines via <strong>heuristics</strong> that the focus should be made visible on the element. Curious how it works in practice? MDN Web Docs highlights the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible">differences between <code>:focus</code> and <code>:focus-visible</code></a>, what you need to consider accessibility-wise, and how to provide a fallback for old browser versions that don’t support <code>:focus-visible</code>.</p>

<h2 id="styling-parents-based-on-children">Styling Parents Based On Children</h2>

<p>Historically, CSS selectors have worked in a top-down fashion, allowing us to style a child based on its parent. The new CSS pseudo-class <code>:has</code> works the other way round: We can now <strong>style a parent based on its children</strong>. But that’s not all yet. Josh W. Comeau wrote a fantastic <a href="https://www.joshwcomeau.com/css/has/">introduction to <code>:has</code></a> in which he explores real-world use cases that show what the pseudo-class is capable of.</p>














<figure class="
  
  
  ">
  
    <a href="https://www.joshwcomeau.com/css/has/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="546"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png"
			
			sizes="100vw"
			alt="Styling Parents Based On Children"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://www.joshwcomeau.com/css/has/'><code>:has</code></a> makes it possible to style one element based on the property or status of any other element. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><code>:has</code> is not limited to parent-child relationships or direct siblings. Instead, it lets us style one element based on the properties or status of any other element in a totally <strong>different container</strong>. And it can be used as a sort of global event listener, as Josh shows — to disable scrolling on a page when a modal is open or to create a JavaScript-free dark mode toggle, for example.</p>

<div class="partners__lead-place"></div>

<h2 id="interpolate-between-values-for-type-and-spacing">Interpolate Between Values For Type And Spacing</h2>

<p>CSS comparison functions <code>min()</code>, <code>max()</code>, and <code>clamp()</code> are today supported in all major browsers, providing us with an effective way to create dynamic layouts with <strong>fluid type scales</strong>, grids, and spacing systems.</p>














<figure class="
  
  
  ">
  
    <a href="https://ishadeed.com/article/css-min-max-clamp/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="384"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png"
			
			sizes="100vw"
			alt="Interpolate Between Values For Type And Spacing"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The future of design is <a href='https://ishadeed.com/article/css-min-max-clamp/'>fluid</a>. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To get you fit for using the functions in your projects right away, Ahmad Shadeed wrote a <a href="https://ishadeed.com/article/css-min-max-clamp/">comprehensive guide</a> in which he explains everything you need to know about <code>min()</code>, <code>max()</code>, and <code>clamp()</code>, with <strong>practical examples</strong> and use cases and including all the points of confusion you might encounter.</p>

<p>If you’re looking for a quick and easy way to create fluid scales, the <a href="https://utopia.fyi/type/calculator/">Fluid Type Scale Calculator</a> by Utopia has got your back. All you need to do is define min and max viewport widths and the number of scale steps, and the <strong>calculator</strong> provides you with a responsive preview of the scale and the CSS code snippet.</p>

<h2 id="reliable-dialog-and-popover">Reliable Dialog And Popover</h2>

<p>If you’re looking for a quick way to create a modal or popup, the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><code>&lt;dialog&gt;</code> HTML element</a> finally offers a native (and accessible!) solution to help you get the job done. It represents a <strong>modal or non-modal dialog box</strong> or other interactive component, such as a confirmation prompt or a subwindow used to enter data.</p>














<figure class="
  
  
  ">
  
    <a href="https://smashing-freiburg-2024.netlify.app/27-dialog/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="425"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png"
			
			sizes="100vw"
			alt="Reliable dialog And Popover"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      We now have accessible <a href='https://smashing-freiburg-2024.netlify.app/27-dialog/'><code>&lt;dialog&gt;</code> menus</a> for blocking pop-ups and popovers for non-blocking menus. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>While modal dialog boxes interrupt interaction with a page, non-modal dialog boxes <strong>allow interaction</strong> with the page while the dialog is open. Adam Argyle published some <a href="https://smashing-freiburg-2024.netlify.app/27-dialog/">code snippets</a> that show how <code>&lt;dialog&gt;</code> can block pop-ups and popovers for non-blocking menus, out of the box.</p>

<h2 id="responsive-html-video-and-audio">Responsive HTML Video And Audio</h2>

<p>In 2014, media attribute support for HTML video sources was deleted from the HTML standard. Last year, it made a comeback, which means that we can use <strong>media queries</strong> for delivering responsive HTML videos.</p>














<figure class="
  
  
  ">
  
    <a href="https://scottjehl.com/posts/using-responsive-video/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="468"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png"
			
			sizes="100vw"
			alt="Responsive HTML Video And Audio"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://scottjehl.com/posts/using-responsive-video/'>Adjusting video and audio files based on the browser’s viewport</a> reduces page payload. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Scott Jehl summarized <a href="https://scottjehl.com/posts/using-responsive-video/">how responsive HTML video — and even audio — works</a>, what you need to consider when writing the markup, and what other types of media queries can be used in combination with HTML video.</p>

<h2 id="the-right-virtual-keyboard-on-mobile">The Right Virtual Keyboard On Mobile</h2>

<p>It’s a small detail, but one that adds to a well-considered user experience: displaying the <strong>most comfortable touchscreen keyboard</strong> to help a user enter their information without having to switch back and forth to insert numbers, punctuation, or special characters like an <code>@</code> symbol.</p>














<figure class="
  
  
  ">
  
    <a href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="271"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png"
			
			sizes="100vw"
			alt="Right Virtual Keyboards On Mobile"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The <a href='https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/'>right virtual keyboard</a> improves the user experience for mobile users. (<a href='https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To show the right keyboard layout, we can use <code>inputmode</code>. It <a href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/">instructs the browser which keyboard to display</a> and supports values for <strong>numeric</strong>, telephone, decimal, <strong>email</strong>, <strong>URL</strong>, and search keyboards. To further improve the UX, we can add the <code>enterkeyhint</code> attribute: it <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint">adjusts the text on the Enter key</a>. If no <code>enterkeyhint</code> is used, the user agent might use contextual information from the <code>inputmode</code> attribute.</p>

<h2 id="a-look-into-the-future">A Look Into The Future</h2>

<p>As we are starting to adopt all of these shiny new front-end features in our projects, the web platform is, of course, constantly evolving — and there are some exciting things on the horizon already! For example, we are very close to getting <strong>masonry layout</strong>, fully <strong>customizable drop-downs</strong> with <code>&lt;selectmenu&gt;</code>, and <strong>text-box trimming</strong> for adjusting fonts to be perfectly aligned within the grid. Kudos to all the wonderful people who are working tirelessly to push the web forward! 👏</p>

<p>In the meantime, we hope you found something helpful in this post that you can apply to your product or application right away. Happy tinkering!</p>

<h2 id="smashing-weekly-newsletter">Smashing Weekly Newsletter</h2>

<p><a href="https://www.smashingmagazine.com/the-smashing-newsletter/"><img loading="lazy" decoding="async" style="float:right;margin-top:1em;margin-left:1.5em;margin-bottom:1em;border-radius:11px;max-width:50%" src="https://www.smashingmagazine.com/images/smashing-cat/cat-with-slippers.svg" width="200" alt="The weekly Smashing Newsletter" /></a>You want to stay on top of what’s happening in the world of front-end and UX? With our <a href="https://www.smashingmagazine.com/the-smashing-newsletter/">weekly newsletter</a>, we aim to bring you <strong>useful, practical tidbits</strong> and share some of the helpful things that folks are working on in the web industry. Every issue is curated, written, and edited with love and care. No third-party mailings or hidden advertising.<br /><br />Also, when you <a href="https://www.smashingmagazine.com/the-smashing-newsletter/">subscribe</a>, you really help us pay the bills. Thank you for your kind support!</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(vf, il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Mariana Beldi</author><title>An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines</title><link>https://www.smashingmagazine.com/2024/12/introduction-css-scroll-driven-animations/</link><pubDate>Wed, 11 Dec 2024 15:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/12/introduction-css-scroll-driven-animations/</guid><description>It’s been 10 years since scroll-driven animations were introduced in a spec proposal, and after five years in development, we’re finally beginning to see pop up in websites. There are &lt;a href="https://codepen.io/andrewrock/pen/NWoRavN">scrolly-telling&lt;/a> and &lt;a href="https://codepen.io/amit_sheen/pen/ZENNgMw">maze games&lt;/a> as well as &lt;a href="https://codepen.io/bramus/pen/GRdGoKy">cover flow animations&lt;/a> and &lt;a href="https://codepen.io/leemeyer/pen/XWvrMBr">3D rotation with scroll&lt;/a>… but what exactly is new here? It’s not like we haven’t seen scroll animations before, &lt;strong>but what we have now requires no JavaScript, no dependencies, no libraries &amp;mdash; just pure CSS&lt;/strong>. And if that’s not exciting enough, these animations run off the &lt;a href="https://www.smashingmagazine.com/2023/10/speedcurve-fight-main-thread/">main thread&lt;/a>, delivering smooth, high-performance, GPU-accelerated experiences.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/12/introduction-css-scroll-driven-animations/" />
              <title>An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines</h1>
                  
                    
                    <address>Mariana Beldi</address>
                  
                  <time datetime="2024-12-11T15:00:00&#43;00:00" class="op-published">2024-12-11T15:00:00+00:00</time>
                  <time datetime="2024-12-11T15:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>You can safely use scroll-driven animations in Chrome as of December 2024. Firefox supports them, too, though you’ll need to enable a flag. Safari? Not yet, but don’t worry &mdash; you can still offer a seamless experience across all browsers with a <a href="https://github.com/flackr/scroll-timeline">polyfill</a>. Just keep in mind that adding a polyfill involves a JavaScript library, so you won’t get the same performance boost.</p>

<p>There are plenty of valuable resources to dive into scroll-driven animations, which I’ll be linking throughout the article. My starting point was <a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICM3ttukz9x5LCNOHfWBVnn">Bramus’ video tutorial</a>, which pairs nicely with <a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/">Geoff’s in-depth notes</a> <a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/">Graham</a> that build on the tutorial.</p>

<p>In this article, we’ll walk through the <a href="https://www.w3.org/TR/scroll-animations-1/">latest published version by the W3C</a> and explore the two types of scroll-driven timelines &mdash; <strong>scroll progress timelines</strong> and <strong>view progress timelines</strong>. By the end, I hope that you are familiar with both timelines, not only being able to tell them apart but also feeling confident enough to use them in your work.</p>

<p><strong>Note</strong>: <em>All demos in this article only work in Chrome 116 or later at the time of writing.</em></p>

<h2 id="scroll-progress-timelines">Scroll Progress Timelines</h2>

<p>The scroll progress timeline links an animation’s timeline to the scroll position of a scroll container along a specific axis. So, the animation is tied directly to scrolling. As you scroll forward, so does the animation. You’ll see me refer to them as <code>scroll-timeline</code> animations in addition to calling them scroll progress timelines.</p>

<p>Just as we have two types of scroll-driven animations, we have two types of <code>scroll-timeline</code> animations: <strong>anonymous timelines</strong> and <strong>named timelines</strong>.</p>

<h3 id="anonymous-scroll-timeline">Anonymous <code>scroll-timeline</code></h3>

<p>Let’s start with a classic example: creating a scroll progress bar at the top of a blog post to track your reading progress.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="RNbRqoj"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scroll Progress Timeline example - before animation-timeline scroll() [forked]](https://codepen.io/smashingmag/pen/RNbRqoj) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/RNbRqoj">Scroll Progress Timeline example - before animation-timeline scroll() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>In this example, there’s a <code>&lt;div&gt;</code> with the ID “progress.” At the end of the CSS file, you’ll see it has a background color, a defined width and height, and it’s fixed at the top of the page. There’s also an animation that scales it from <code>0</code> to <code>1</code> along the x-axis &mdash; pretty standard if you’re familiar with CSS animations!</p>

<p>Here’s the relevant part of the styles:</p>

<pre><code class="language-css">&#35;progress {
  /&#42; ... &#42;/
  animation: progressBar 1s linear;
}


@keyframes progressBar {
  from { transform: scaleX(0); }
}
</code></pre>

<p>The <code>progressBar</code> animation runs once and lasts one second with a linear timing function. Linking this animation scrolling is just a single line in CSS:</p>

<pre><code class="language-css">animation-timeline: scroll();
</code></pre>

<p>No need to specify seconds for the duration &mdash; the scrolling behavior itself will dictate the timing. And that’s it! You’ve just created your first scroll-driven animation! Notice how the animation’s direction is directly tied to the scrolling direction &mdash; scroll down, and the progress indicator grows wider; scroll up, and it becomes narrower.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ByBzGpO"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scroll Progress Timeline example - animation-timeline scroll() [forked]](https://codepen.io/smashingmag/pen/ByBzGpO) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ByBzGpO">Scroll Progress Timeline example - animation-timeline scroll() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<h3 id="scroll-timeline-property-parameters"><code>scroll-timeline</code> Property Parameters</h3>

<p>In a <code>scroll-timeline</code> animation, the <code>scroll()</code> function is used inside the <code>animation-timeline</code> property. It only takes two parameters: <code>&lt;scroller&gt;</code> and <code>&lt;axis&gt;</code>.</p>

<ul>
<li><strong><code>&lt;scroller&gt;</code></strong> refers to the scroll container, which can be set as <code>nearest</code> (the default), <code>root</code>, or <code>self</code>.</li>
<li><strong><code>&lt;axis&gt;</code></strong> refers to the scroll axis, which can be <code>block</code> (the default), <code>inline</code>, <code>x</code>, or <code>y</code>.</li>
</ul>

<p>In the reading progress example above, we didn’t declare any of these because we used the defaults. But we could achieve the same result with:</p>

<pre><code class="language-css">animation-timeline: scroll(nearest block);
</code></pre>

<p>Here, the <code>nearest</code> scroll container is the root scroll of the HTML element. So, we could also write it this way instead:</p>

<pre><code class="language-css">animation-timeline: scroll(root block);
</code></pre>

<p>The <code>block</code> axis confirms that the scroll moves top to bottom in a left-to-right writing mode. If the page has a wide horizontal scroll, and we want to animate along that axis, we could use the <code>inline</code> or <code>x</code>  values (depending on whether we want the scrolling direction to always be left-to-right or adapt based on the writing mode).</p>

<p>We’ll dive into <code>self</code> and <code>inline</code> in more examples later, but the best way to learn is to play around with all the combinations, and <a href="https://scroll-driven-animations.style/tools/scroll-timeline/params/">this tool by Bramus</a> lets you do exactly that. Spend a few minutes before we jump into the next property associated with scroll timelines.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h3 id="the-animation-range-property">The <code>animation-range</code> Property</h3>

<p>The <code>animation-range</code> for <code>scroll-timeline</code> defines which part of the scrollable content controls the start and end of an animation’s progress based on the scroll position. It allows you to decide when the animation starts or ends while scrolling through the container.</p>

<p>By default, the <code>animation-range</code> is set to <code>normal</code>, which is shorthand for the following:</p>

<pre><code class="language-css">animation-range-start: normal;
animation-range-end: normal;
</code></pre>

<p>This translates to <code>0%</code> (<code>start</code>) and <code>100%</code> (<code>end</code>) in a <code>scroll-timeline</code> animation:</p>

<pre><code class="language-css">animation-range: normal normal;
</code></pre>

<p>…which is the same as:</p>

<pre><code class="language-css">animation-range: 0% 100%;
</code></pre>

<p>You can declare any <a href="https://css-tricks.com/css-length-units/">CSS length units</a> or even <a href="https://www.smashingmagazine.com/2015/12/getting-started-css-calc-techniques/">calculations</a>. For example, let’s say I have a footer that’s <code>500px</code> tall. It’s filled with banners, ads, and related posts. I don’t want the scroll progress bar to include any of that as part of the reading progress. What I want is for the animation to start at the top and end <code>500px</code> before the bottom. Here we go:</p>

<pre><code class="language-css">animation-range: 0% calc(100% - 500px);
</code></pre>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="azoZQym"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scroll Progress Timeline example - animation-timeline, animation-range [forked]](https://codepen.io/smashingmag/pen/azoZQym) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/azoZQym">Scroll Progress Timeline example - animation-timeline, animation-range [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>Just like that, we’ve covered the key properties of <code>scroll-timeline</code> animations. Ready to take it a step further?</p>

<h3 id="named-scroll-timeline">Named <code>scroll-timeline</code></h3>

<p>Let’s say I want to use the scroll position of a different scroll container for the same animation. The <code>scroll-timeline-name</code> property allows you to specify which scroll container the scroll animation should be linked to. You give it a name (a dashed-ident, e.g., <code>--my-scroll-timeline</code>) that maps to the scroll container you want to use. This container will then control the animation’s progress as the user scrolls through it.</p>

<p>Next, we need to define the scroll axis for this new container by using the <code>scroll-timeline-axis</code>, which tells the animation which axis will trigger the motion. Here’s how it looks in the code:</p>

<pre><code class="language-css">.my-class { 
  /&#42; This is my new scroll-container &#42;/
  scroll-timeline-name: --my-custom-name;
  scroll-timeline-axis: inline;
}
</code></pre>

<p>If you omit the axis, then the default <code>block</code> value will be used. However, you can also use the shorthand <code>scroll-timeline</code> property to combine both the name and axis in a single declaration:</p>

<pre><code class="language-css">.my-class { 
  /&#42; Shorthand for scroll-container with axis &#42;/
  scroll-timeline: --my-custom-name inline;
}
</code></pre>

<p>I think it’s easier to understand all this with a practical example. Here’s the same progress indicator we’ve been working with, but with inline scrolling (i.e., along the x-axis):</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="pvzbQrM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Named Scroll Progress Timeline [forked]](https://codepen.io/smashingmag/pen/pvzbQrM) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvzbQrM">Named Scroll Progress Timeline [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>We have two animations running:</p>

<ol>
<li>A progress bar grows wider when scrolling in an inline direction.</li>
<li>The container’s background color changes the further you scroll.</li>
</ol>

<p>The HTML structure looks like the following:</p>

<div class="break-out">
<pre><code class="language-html">&lt;div class="gallery"&gt;
  &lt;div class="gallery-scroll-container"&gt;
    &lt;div class="gallery-progress" role="progressbar" aria-label="progress"&gt;&lt;/div&gt;
    &lt;img src="image1.svg" alt="Alt text" draggable="false" width="500"&gt;
    &lt;img src="image2.svg" alt="Alt text" draggable="false" width="500"&gt;
    &lt;img src="image3.svg" alt="Alt text" draggable="false" width="500"&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
</div>

<p>In this case, the <code>gallery-scroll-container</code> has horizontal scrolling and changes its background color as you scroll. Normally, we could just use <code>animation-timeline: scroll(self inline)</code> to achieve this. However, we also want the <code>gallery-progress</code> element to use the same scroll for its animation.</p>

<p>The <code>gallery-progress</code> element is the first inside <code>gallery-scroll-container</code>, and we will lose it when scrolling unless it’s absolutely positioned. But when we do this, the element no longer occupies space in the normal document flow, and that affects how the element behaves with its parent and siblings. We need to specify which scroll container we want it to listen to.</p>

<p>That’s where naming the scroll container comes in handy. By giving <code>gallery-scroll-container</code> a <code>scroll-timeline-name</code> and <code>scroll-timeline-axis</code>, we can ensure both animations sync to the same scroll:</p>

<pre><code class="language-css">.gallery-scroll-container {
  /&#42; ... &#42;/
  animation: bg steps(1);
  scroll-timeline: --scroller inline;
}
</code></pre>

<p>And is using that scrolling to define its own <code>animation-timeline</code>:</p>

<pre><code class="language-css">.gallery-scroll-container {
  /&#42; ... &#42;/
  animation: bg steps(1);
  scroll-timeline: --scroller inline;
  animation-timeline: --scroller;
}
</code></pre>

<p>Now we can scale this name to the progress bar that is using a different animation but listening to the same scroll:</p>

<pre><code class="language-css">.gallery-progress {
  /&#42; ... &#42;/
  animation: progressBar linear;
  animation-timeline: --scroller;
}
</code></pre>

<p>This allows both animations (the growing progress bar and changing background color) to follow the same scroll behavior, even though they are separate elements and animations.</p>

<div class="partners__lead-place"></div>

<h3 id="the-timeline-scope-property">The <code>timeline-scope</code> Property</h3>

<p>What happens if we want to animate something based on the scroll position of a sibling or even a higher ancestor? This is where the <code>timeline-scope</code> property comes into play. It allows us to extend the scope of a <code>scroll-timeline</code> beyond the current element’s subtree. The value of <code>timeline-scope</code> must be a custom identifier, which again is a dashed-ident.</p>

<p>Let’s illustrate this with a new example. This time, scrolling in one container runs an animation inside another container:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="jENrQGo"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scroll Driven Animations - timeline-scope [forked]](https://codepen.io/smashingmag/pen/jENrQGo) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/jENrQGo">Scroll Driven Animations - timeline-scope [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>We can play the animation on the image when scrolling the text container because they are siblings in the HTML structure:</p>

<pre><code class="language-html">&lt;div class="main-container"&gt;
  &lt;div class="sardinas-container"&gt;
    &lt;img ...&gt;
  &lt;/div&gt;

  &lt;div class="scroll-container"&gt;
    &lt;p&gt;Long text...&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>Here, only the <code>.scroll-container</code> has scrollable content, so let’s start by naming this:</p>

<pre><code class="language-css">.scroll-container {
  /&#42; ... &#42;/
  overflow-y: scroll;
  scroll-timeline: --containerText;
}
</code></pre>

<p>Notice that I haven’t specified the scroll axis, as it defaults to <code>block</code> (vertical scrolling), and that’s the value I want.</p>

<p>Let’s move on to the image inside the <code>sardinas-container</code>. We want this image to animate as we scroll through the <code>scroll-container</code>. I’ve added a <code>scroll-timeline-name</code> to its <code>animation-timeline</code> property:</p>

<pre><code class="language-css">.sardinas-container img {
  /&#42; ... &#42;/
  animation: moveUp steps(6) both;
  animation-timeline: --containerText;
}
</code></pre>

<p>At this point, however, the animation still won’t work because the <code>scroll-container</code> is not directly related to the images. To make this work, we need to extend the <code>scroll-timeline-name</code> so it becomes reachable. This is done by adding the <code>timeline-scope</code> to the parent element (or a higher ancestor) shared by both elements:</p>

<pre><code class="language-css">.main-container {
  /&#42; ... &#42;/
  timeline-scope: --containerText;
}
</code></pre>

<p>With this setup, the scroll of the <code>scroll-container</code> will now control the animation of the image inside the <code>sardinas-container</code>!</p>

<p>Now that we’ve covered how to use <code>timeline-scope</code>, we’re ready to move on to the next type of scroll-driven animations, where the same properties will apply but with slight differences in how they behave.</p>

<h2 id="view-progress-timelines">View Progress Timelines</h2>

<p>We just looked at <strong>scroll progress animations</strong>. That’s the first type of scroll-driven animation of the two. Next, we’re turning our attention to <strong>view progress animations</strong>. There’s a lot of similarities between the two! But they’re different enough to warrant their own section for us to explore how they work. You’ll see me refer to these as <code>view-timeline</code> animations in addition to calling them view progress animations, as they revolve around a <code>view()</code> function.</p>

<p>The <strong>view progress timeline</strong> is the second type of type of scroll-driven animation that we’re looking at. It tracks an element as it enters or exits the scrollport (the visible area of the scrollable content). This behavior is quite similar to <a href="https://css-tricks.com/an-explanation-of-how-the-intersection-observer-watches/?ref=csslayout.news">how an <code>IntersectionObserver</code> works in JavaScript</a> but can be done entirely in CSS.</p>

<p>We have anonymous and named view progress timelines, just as we have anonymous and named scroll progress animations. Let’s unpack those.</p>

<h3 id="anonymous-view-timeline">Anonymous View Timeline</h3>

<p>Here’s a simple example to help us see the basic idea of anonymous view timelines. Notice how the image fades into view when you scroll down to a certain point on the page:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="KwPMrQO"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [View Timeline Animation - view() [forked]](https://codepen.io/smashingmag/pen/KwPMrQO) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/KwPMrQO">View Timeline Animation - view() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>Let’s say we want to animate an image that fades in as it appears in the scrollport. The image’s opacity will go from <code>0</code> to <code>1</code>. This is how you might write that same animation in classic CSS using <code>@keyframes</code>:</p>

<pre><code class="language-css">img {
  /&#42; ... &#42;/
  animation: fadeIn 1s;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
</code></pre>

<p>That’s great, but we want the image to <code>fadeIn</code> when it’s in view. Otherwise, the animation is sort of like a tree that falls in a forest with no one there to witness it… did the animation ever happen? We’ll never know!</p>

<p>We have a <code>view()</code> function that makes this a view progress animation with a single line of CSS:</p>

<pre><code class="language-css">img {
  /&#42; ... &#42;/
  animation: fadeIn;
  animation-timeline: view();
}
</code></pre>

<p>And notice how we no longer need to declare an <code>animation-duration</code> like we did in classic CSS. The animation is no longer tied by time but by space. The animation is triggered as the image becomes visible in the scrollport.</p>

<h3 id="view-timeline-parameters">View Timeline Parameters</h3>

<p>Just like the <code>scroll-timeline</code> property, the <strong><code>view-timeline</code></strong> property accepts parameters that allow for more customization:</p>

<pre><code class="language-css">animation-timeline: view(<inset> <axis>);
</code></pre>

<ul>
<li><strong><code>&lt;inset&gt;</code></strong><br />
Controls when the animation starts and ends relative to the element’s visibility within the scrollport. It defines the margin between the edges of the scrollport and the element being tracked. The default value is <code>auto</code>, but it can also take length percentages as well as start and end values.</li>
<li><strong><code>&lt;axis&gt;</code></strong><br />
This is similar to the scroll-timeline’s axis parameter. It defines which axis (horizontal or vertical) the animation is tied to. The default is <code>block</code>, which means it tracks the vertical movement. You can also use <code>inline</code> to track horizontal movement or simple <code>x</code> or <code>y</code>.</li>
</ul>

<p>Here’s an example that uses both <code>inset</code> and <code>axis</code> to customize when and how the animation starts:</p>

<pre><code class="language-css">img {
  animation-timeline: view(20% block);
}
</code></pre>

<p>In this case:</p>

<ol>
<li>The animation starts when the image is 20% visible in the scrollport.</li>
<li>The animation is triggered by vertical scrolling (<code>block</code> axis).</li>
</ol>

<h3 id="parallax-effect">Parallax Effect</h3>

<p>With the <code>view()</code> function, it’s also easy to create parallax effects by simply adjusting the animation properties. For example, you can have an element move or scale as it enters the scrollport without any JavaScript:</p>

<pre><code class="language-css">img {
  animation: parallaxMove 1s;
  animation-timeline: view();
}

@keyframes parallaxMove {
  to { transform: translateY(-50px); }
}
</code></pre>

<p>This makes it incredibly simple to create dynamic and engaging scroll animations with just a few lines of CSS.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="mybEQLK"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Parallax effect with CSS Scroll driven animations - view() [forked]](https://codepen.io/smashingmag/pen/mybEQLK) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/mybEQLK">Parallax effect with CSS Scroll driven animations - view() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<h3 id="the-animation-range-property-1">The <code>animation-range</code> Property</h3>

<p>Using the CSS <strong><code>animation-range</code></strong> property with view timelines defines how much of an element’s visibility within the scrollport controls the start and end points of the animation’s progress. This can be used to fine-tune when the animation begins and ends based on the element’s visibility in the viewport.</p>

<p>While the default value is <code>normal</code>, in view timelines, it translates to tracking the full visibility of the element from the moment it starts entering the scrollport until it fully leaves. This is represented by the following:</p>

<pre><code class="language-css">animation-range: normal normal;
/&#42; Equivalent to &#42;/
animation-range: cover 0% cover 100%;
</code></pre>

<p>Or, more simply:</p>

<pre><code class="language-css">animation-range: cover;
</code></pre>

<p>There are six possible values or <code>timeline-range-names</code>:</p>

<ol>
<li><strong><code>cover</code></strong><br />
Tracks the full visibility of the element, from when it starts entering the scrollport to when it completely leaves it.</li>
<li><strong><code>contain</code></strong><br />
Tracks when the element is fully visible inside the scrollport, from the moment it’s fully contained until it no longer is.</li>
<li><strong><code>entry</code></strong><br />
Tracks the element from the point it starts entering the scrollport until it’s fully inside.</li>
<li><strong><code>exit</code></strong><br />
Tracks the element from the point it starts, leaving the scrollport until it’s fully outside.</li>
<li><strong><code>entry-crossing</code></strong><br />
Tracks the element as it crosses the starting edge of the scrollport, from start to full crossing.</li>
<li><strong><code>exit-crossing</code></strong><br />
Tracks the element as it crosses the end edge of the scrollport, from start to full crossing.</li>
</ol>

<p>You can mix different <code>timeline-range-names</code> to control the start and end points of the animation range. For example, you could make the animation start when the element enters the scrollport and end when it exits:</p>

<pre><code class="language-css">animation-range: entry exit;
</code></pre>

<p>You can also combine these values with percentages to define more custom behavior, such as starting the animation halfway through the element’s entry and ending it halfway through its exit:</p>

<pre><code class="language-css">animation-range: entry 50% exit 50%;
</code></pre>

<p>Exploring all these values and combinations is best done interactively. Tools like Bramus’ <a href="https://scroll-driven-animations.style/tools/view-timeline/ranges/">view-timeline range visualizer</a> make it easier to understand.</p>

<div class="partners__lead-place"></div>

<h3 id="target-range-inside-keyframes">Target Range Inside <code>@keyframes</code></h3>

<p>One of the powerful features of <code>timeline-range-names</code> is their ability to be used inside <code>@keyframes</code>:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="zxOBMaK"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [target range inside @keyframes - view-timeline, timeline-range-name [forked]](https://codepen.io/smashingmag/pen/zxOBMaK) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/zxOBMaK">target range inside @keyframes - view-timeline, timeline-range-name [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>Two different animations are happening in that demo:</p>

<ol>
<li><strong><code>slideIn</code></strong><br />
When the element enters the scrollport, it scales up and becomes visible.</li>
<li><strong><code>slideOut</code></strong><br />
When the element leaves, it scales down and fades out.</li>
</ol>

<pre><code class="language-css">@keyframes slideIn {
  from {
    transform: scale(.8) translateY(100px); 
    opacity: 0;
  }
  to { 
    transform: scale(1) translateY(0); 
    opacity: 1;
  }
}

@keyframes slideOut {
  from {
    transform: scale(1) translateY(0); 
    opacity: 1;    
  }
  to { 
    transform: scale(.8) translateY(-100px); 
    opacity: 0 
  }
}
</code></pre>

<p>The new thing is that now we can merge these two animations using the <code>entry</code> and <code>exit</code> <code>timeline-range-names</code>, simplifying it into one animation that handles both cases:</p>

<pre><code class="language-css">@keyframes slideInOut {
  /&#42; Animation for when the element enters the scrollport &#42;/
  entry 0% {
    transform: scale(.8) translateY(100px); 
    opacity: 0;
  }
  entry 100% { 
    transform: scale(1) translateY(0); 
    opacity: 1;
  }
  /&#42; Animation for when the element exits the scrollport &#42;/
  exit 0% {
    transform: scale(1) translateY(0); 
    opacity: 1;    
  }
  exit 100% { 
    transform: scale(.8) translateY(-100px); 
    opacity: 0;
  }
}
</code></pre>

<ul>
<li><strong><code>entry 0%</code></strong><br />
Defines the state of the element at the beginning of its entry into the scrollport (scaled down and transparent).</li>
<li><strong><code>entry 100%</code></strong><br />
Defines the state when the element has fully entered the scrollport (fully visible and scaled up).</li>
<li><strong><code>exit 0%</code></strong><br />
Starts tracking the element as it begins to leave the scrollport (visible and scaled up).</li>
<li><strong><code>exit 100%</code></strong><br />
Defines the state when the element has fully left the scrollport (scaled down and transparent).</li>
</ul>

<p>This approach allows us to animate the element’s behavior smoothly as it both enters and leaves the scrollport, all within a single <code>@keyframes</code> block.</p>

<h3 id="named-view-timeline-and-timeline-scope">Named <code>view-timeline</code> And <code>timeline-scope</code></h3>

<p>The concept of using <code>view-timeline</code> with named timelines and linking them across different elements can truly expand the possibilities for scroll-driven animations. In this case, we are linking the scroll-driven animation of images with the animations of unrelated paragraphs in the DOM structure by using <strong>a named <code>view-timeline</code> and <code>timeline-scope</code></strong>.</p>

<p>The <code>view-timeline</code> property works similarly to the <code>scroll-timeline</code> property. It’s the shorthand for declaring the <code>view-timeline-name</code> and <code>view-timeline-axis</code> properties in one line. However, the difference from <code>scroll-timeline</code> is that we can link the animation of an element when the linked elements enter the scrollport. I took the previous demo and added an animation to the paragraphs so you can see how the opacity of the text is animated when scrolling the images on the left:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="KwPMrBP"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [View-timeline, timeline-scope [forked]](https://codepen.io/smashingmag/pen/KwPMrBP) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/KwPMrBP">View-timeline, timeline-scope [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption>
</figure>

<p>This one looks a bit verbose, but I found it hard to come up with a better example to show the power of it. Each image in the vertical scroll container is assigned a named <code>view-timeline</code> with a unique identifier:</p>

<div class="break-out">
<pre><code class="language-css">.vertical-scroll-container img:nth-of-type(1) { view-timeline: --one; }
.vertical-scroll-container img:nth-of-type(2) { view-timeline: --two; }
.vertical-scroll-container img:nth-of-type(3) { view-timeline: --three; }
.vertical-scroll-container img:nth-of-type(4) { view-timeline: --four; }
</code></pre>
</div>

<p>This makes the scroll timeline of each image have its own custom name, such as <code>--one</code> for the first image, <code>--two</code> for the second, and so on.</p>

<p>Next, we connect the animations of the paragraphs to the named timelines of the images. The corresponding paragraph should animate when the images enter the scrollport:</p>

<div class="break-out">
<pre><code class="language-css">.vertical-text p:nth-of-type(1) { animation-timeline: --one; }
.vertical-text p:nth-of-type(2) { animation-timeline: --two; }
.vertical-text p:nth-of-type(3) { animation-timeline: --three; }
.vertical-text p:nth-of-type(4) { animation-timeline: --four; }
</code></pre>
</div>

<p>However, since the images and paragraphs are not directly related in the DOM, we need to declare a <code>timeline-scope</code> on their common ancestor. This ensures that the named timelines (<code>--one</code>, <code>--two</code>, and so on) can be referenced and shared between the elements:</p>

<pre><code class="language-css">.porto {
  /&#42; ... &#42;/
  timeline-scope: --one, --two, --three, --four;
}
</code></pre>

<p>By declaring the <code>timeline-scope</code> with all the named timelines (<code>--one</code>, <code>—two</code>, <code>--three</code>, <code>--four</code>), both the images and the paragraphs can participate in the same scroll-timeline logic, despite being in separate parts of the DOM tree.</p>

<h2 id="final-notes">Final Notes</h2>

<p>We’ve covered the vast majority of what’s currently defined in the <a href="https://drafts.csswg.org/scroll-animations-1/">CSS Scroll-Driven Animations Module Leve 1 specification</a> today in December 2024. But I want to highlight a few key takeaways that helped me better understand these new rules that you may not get directly from the spec:</p>

<ul>
<li><strong>Scroll container essentials</strong><br />
It may seem obvious, but a scroll container is necessary for scroll-driven animations to work. Issues often arise when elements like text or containers are resized or when animations are tested on larger screens, causing the scrollable area to disappear.</li>
<li><strong>Impact of <code>position: absolute</code></strong><br />
Using absolute positioning can sometimes interfere with the intended behavior of scroll-driven animations. The relationship between elements and their parent elements gets tricky when <code>position: absolute</code> is applied.</li>
<li><strong>Tracking an element’s initial state</strong><br />
The browser evaluates the element’s state <em>before</em> any transformations (like <code>translate</code>) are applied. This affects when animations, particularly view timelines, begin. Your animation might trigger earlier or later than expected due to the initial state.</li>
<li><strong>Avoid hiding overflow</strong><br />
Using <code>overflow: hidden</code> can disrupt the scroll-seeking mechanism in scroll-driven animations. The recommended solution is to switch to <code>overflow: clip</code>. Bramus has <a href="https://www.bram.us/2024/02/14/scroll-driven-animations-you-want-overflow-clip-not-overflow-hidden/">a great article about this</a> and <a href="https://www.youtube.com/watch?v=72pUm4tQesw">a video from Kevin Powell</a> also suggests that we may no longer need <code>overflow: hidden</code>.</li>
<li><strong>Performance</strong><br />
For the best results, stick to animating GPU-friendly properties like transforms, opacity, and some filters. These skip the heavy lifting of recalculating layout and repainting. On the other hand, animating things like <code>width</code>, <code>height</code>, or <code>box-shadow</code> can slow things down since they require re-rendering. Bramus <a href="https://www.bram.us/2024/05/30/scroll-driven-animations-with-css-webexpo/">mentioned</a> that soon, more properties &mdash; like <code>background-color</code>, <code>clip-path</code>, <code>width</code>, and <code>height</code> &mdash; will be animatable on the compositor, making the performance even better.</li>
<li><strong>Use <code>will-change</code> wisely</strong><br />
Leverage this property to promote elements to the GPU, but use it sparingly. Overusing <code>will-change</code> can lead to excessive memory usage since the browser reserves resources even if the animations don’t frequently change.</li>
<li><strong>The order matters</strong><br />
If you are using the <code>animation</code> shorthand, always place the <code>animation-timeline</code> after it.</li>
<li><strong>Progressive enhancement and accessibility</strong><br />
Combine media queries for reduced motion preferences with the <code>@supports</code> rule to ensure animations only apply when the user has no motion restrictions, and the browser supports them.</li>
</ul>

<p>For example:</p>

<div class="break-out">
<pre><code class="language-css">@media screen and (prefers-reduce-motion: no-preference) {
  @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) { 
    .my-class {
      animation: moveCard linear both;    
      animation-timeline: view(); 
    }
  } 
}
</code></pre>
</div>

<p>My main struggle while trying to build the demos was more about CSS itself than the scroll animations. Sometimes, building the layout and generating the scroll was more difficult than applying the scroll animation. Also, some things that confused me at the beginning as the spec keeps evolving, and some of these are not there anymore (remember, it has been under development for more than five years now!):</p>

<ul>
<li><strong>x and y axes</strong><br />
These used to be called the “horizontal” and “vertical” axes, and while Firefox may still support the old terminology, it has been updated.</li>
<li><strong>Old <code>@scroll-timeline</code> syntax</strong><br />
In the past, <code>@scroll-timeline</code> was used to declare scroll timelines, but this has changed in the most recent version of the spec.</li>
<li><strong>Scroll-driven vs. scroll-linked animations</strong><br />
Scroll-<em>driven</em> animations were originally called scroll-<em>linked</em> animations. If you come across this older term in articles, double-check whether the content has been updated to reflect the latest spec, particularly with features like <code>timeline-scope</code>.</li>
</ul>

<h3 id="resources">Resources</h3>

<ul>
<li>All demos from this article can be found <a href="https://codepen.io/collection/WvVpQR">in this collection</a>, and I might include <a href="https://codepen.io/marianab/pen/bGXdEoB">more</a> as I experiment further.</li>
<li>A collection of <a href="https://codepen.io/collection/aMgBZp">demos from CodePen</a> that I find interesting (send me yours, and I’ll include it!)</li>
<li>This <a href="https://github.com/w3c/csswg-drafts/labels/scroll-animations-1">GitHub repo</a> is where you can report issues or join discussions about scroll-driven animations.</li>
<li><a href="https://scroll-driven-animations.style/">Demos, tools, videos</a>, and (even) more information from Bramus</li>
<li>Google Chrome <a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICM3ttukz9x5LCNOHfWBVnn">video tutorial</a></li>
</ul>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Victor Ayomipo</author><title>CSS min() All The Things</title><link>https://www.smashingmagazine.com/2024/10/css-min-all-the-things/</link><pubDate>Thu, 17 Oct 2024 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/10/css-min-all-the-things/</guid><description>Victor Ayomipo experiments with the CSS &lt;code>min()&lt;/code> function, exploring its flexibility with different units to determine if it is the be-all, end-all for responsiveness. Discover the cautions he highlights against dogmatic approaches to web design based on his findings.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/10/css-min-all-the-things/" />
              <title>CSS min() All The Things</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>CSS min() All The Things</h1>
                  
                    
                    <address>Victor Ayomipo</address>
                  
                  <time datetime="2024-10-17T10:00:00&#43;00:00" class="op-published">2024-10-17T10:00:00+00:00</time>
                  <time datetime="2024-10-17T10:00:00&#43;00:00" class="op-modified">2025-10-14T04:02:41+00:00</time>
                </header>
                
                

<p>Did you see <a href="https://frontendmasters.com/blog/what-if-you-used-container-units-for-everything/">this post</a> that Chris Coyier published back in August? He experimented with CSS container query units, going all in and using them for every single numeric value in a demo he put together. And <a href="https://codepen.io/chriscoyier/pen/OJYKLXz">the result</a> was… not too bad, actually.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ExqWXOQ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Container Units for All Units [forked]](https://codepen.io/smashingmag/pen/ExqWXOQ) by <a href="https://codepen.io/chriscoyier">Chris Coyier</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ExqWXOQ">Container Units for All Units [forked]</a> by <a href="https://codepen.io/chriscoyier">Chris Coyier</a>.</figcaption>
</figure>

<p>What I found interesting about this is how it demonstrates the complexity of sizing things. We’re constrained to absolute and relative units in CSS, so we’re either stuck at a specific size (e.g., <code>px</code>) or computing the size based on sizing declared on another element (e.g., <code>%</code>, <code>em</code>, <code>rem</code>, <code>vw</code>, <code>vh</code>, and so on). Both come with compromises, so it’s not like there is a “correct” way to go about things &mdash; it’s about the element’s context &mdash; and leaning heavily in any one direction doesn’t remedy that.</p>

<p>I thought I’d try my own experiment but with the CSS <code>min()</code> function instead of container query units. Why? Well, first off, we can supply the function with <strong>any type of length unit we want</strong>, which makes the approach a little more flexible than working with one type of unit. But the real reason I wanted to do this is personal interest more than anything else.</p>

<h2 id="the-demo">The Demo</h2>

<p>I won’t make you wait for the end to see how my <code>min()</code> experiment went:</p>

<p><blockquote class="twitter-tweet" data-media-max-width="560"><p lang="en" dir="ltr">Taking website responsiveness to a whole new level 🌐 <a href="https://t.co/pKmHl5d0Dy">pic.twitter.com/pKmHl5d0Dy</a></p>&mdash; Vayo (@vayospot) <a href="https://twitter.com/vayospot/status/1630863145014112257?ref_src=twsrc%5Etfw">March 1, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p>

<p><br /></p>

<p>We’ll talk about that more after we walk through the details.</p>

<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">

<aside class="feature-panel" style="">
<div class="feature-panel-left-col">

<div class="feature-panel-description"><p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<a data-instant href="smashing-workshops" class="btn btn--green btn--large" style="">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link">
<div class="feature-panel-image">
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="/images/smashing-cat/cat-scubadiving-panel.svg"
    alt="Feature Panel"
    width="257"
    height="355"
/>

</div>
</a>
</div>
</aside>
</div>

<h2 id="a-little-about-min">A Little About <code>min()</code></h2>

<p>The <code>min()</code> function takes two values and applies the smallest one, whichever one happens to be in the element’s context. For example, we can say we want an element to be as wide as <code>50%</code> of whatever container it is in. And if <code>50%</code> is <em>greater</em> than, say <code>200px</code>, cap the width there instead.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="LYwWLMg"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [[forked]](https://codepen.io/smashingmag/pen/LYwWLMg) by <a href="https://codepen.io/geoffgraham">Geoff Graham</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LYwWLMg">[forked]</a> by <a href="https://codepen.io/geoffgraham">Geoff Graham</a>.</figcaption>
</figure>

<p>So, <code>min()</code> is sort of like container query units in the sense that it is aware of how much available space it has in its container. But it’s different in that <code>min()</code> isn’t querying its container dimensions to compute the final value. We supply it with two acceptable lengths, and it determines which is best given the context. That makes <code>min()</code> (and <code>max()</code> for that matter) <strong>a useful tool for responsive layouts that adapt to the viewport’s size</strong>. It uses conditional logic to determine the “best” match, which means it can help adapt layouts without needing to reach for CSS media queries.</p>

<pre><code class="language-css">.element {
  width: min(200px, 50%);
}

/&#42; Close to this: &#42;/
.element {
  width: 200px;

  @media (min-width: 600px) {
    width: 50%;
  }
}
</code></pre>

<p>The difference between <code>min()</code> and <code>@media</code> in that example is that we’re telling the browser to set the element’s width to <code>50%</code> at a specific <em>breakpoint</em> of <code>600px</code>. With <code>min()</code>, it switches things up automatically as the amount of available space changes, whatever viewport size that happens to be.</p>

<p>When I use the <code>min()</code>, I think of it as having the ability to make smart decisions based on context. We don’t have to do the thinking or calculations to determine which value is used. However, using <code>min()</code> coupled with just any CSS unit isn’t enough. For instance, relative units work better for responsiveness than absolute units. You might even think of <code>min()</code> as <a href="https://css-tricks.com/min-max-and-clamp-are-css-magic/">setting a <em>maximum</em> value</a> in that it never goes below the first value but also caps itself at the second value.</p>

<p>I mentioned earlier that we could use any type of unit in <code>min()</code>. Let’s take the same approach that Chris did and lean heavily into a type of unit to see how <code>min()</code> behaves when it is used exclusively for a responsive layout. Specifically, we’ll use <strong>viewport units</strong> as they are directly relative to the size of the viewport.</p>

<p>Now, there are different flavors of viewport units. We can use the viewport’s width (<code>vw</code>) and height (<code>vh</code>). We also have the <code>vmin</code> and <code>vmax</code> units that are slightly more intelligent in that they evaluate an element’s width and height and apply either the smaller (<code>vmin</code>) or larger (<code>vmax</code>) of the two. So, if we declare <code>100vmax</code> on an element, and that element is <code>500px</code> wide by <code>250px</code> tall, the unit computes to <code>500px</code>.</p>

<p>That is how I am approaching this experiment. What happens if we eschew media queries in favor of only using <code>min()</code> to establish a responsive layout and lean into viewport units to make it happen? We’ll take it one piece at a time.</p>

<h2 id="font-sizing">Font Sizing</h2>

<p>There are various approaches for responsive type. Media queries are quickly becoming the “old school” way of doing it:</p>

<pre><code class="language-css">p { font-size: 1.1rem; }

@media (min-width: 1200px) {
  p { font-size: 1.2rem; }
}

@media (max-width: 350px) {
  p { font-size: 0.9rem; }
}
</code></pre>

<p>Sure, this works, but what happens when the user uses a 4K monitor? Or a foldable phone? There are other tried and true approaches; in fact, <a href="https://www.smashingmagazine.com/2023/11/addressing-accessibility-concerns-fluid-type/"><code>clamp()</code> is the prevailing go-to</a>. But we’re leaning all-in on <code>min()</code>. As it happens, just one line of code is all we need to wipe out all of those media queries, substantially reducing our code:</p>

<pre><code class="language-css">p { font-size: min(6vmin, calc(1rem + 0.23vmax)); }
</code></pre>

<p>I’ll walk you through those values…</p>

<ol>
<li><code>6vmin</code> is essentially 6% of the browser’s width or height, whichever is smallest. This allows the font size to shrink as much as needed for smaller contexts.</li>
<li>For <code>calc(1rem + 0.23vmax)</code>, <code>1rem</code> is the base font size, and <code>0.23vmax</code> is a tiny fraction of the viewport‘s width or height, whichever happens to be the largest.</li>
<li>The <code>calc()</code> function adds those two values together. Since <code>0.23vmax</code> is evaluated differently depending on which viewport edge is the largest, it’s crucial when it comes to scaling the font size between the two arguments. I’ve tweaked it into something that scales gradually one way or the other rather than blowing things up as the viewport size increases.</li>
<li>Finally, the <code>min()</code> returns the smallest value suitable for the font size of the current screen size.</li>
</ol>

<p>And speaking of how flexible the <code>min()</code> approach is, it can restrict how far the text grows. For example, we can cap this at a maximum <code>font-size</code> equal to <code>2rem</code> as a third function parameter:</p>

<pre><code class="language-css">p { font-size: min(6vmin, calc(1rem + 0.23vmax), 2rem); }
</code></pre>

<p>This isn’t a silver bullet tactic. I’d say it’s probably best used for body text, like paragraphs. We might want to adjust things a smidge for headings, e.g., <code>&lt;h1&gt;</code>:</p>

<pre><code class="language-css">h1 { font-size: min(7.5vmin, calc(2rem + 1.2vmax)); }
</code></pre>

<p>We’ve bumped up the minimum size from <code>6vmin</code> to <code>7.5vmin</code> so that it stays larger than the body text at any viewport size. Also, in the <code>calc()</code>, the base size is now <code>2rem</code>, which is smaller than the default UA styles for <code>&lt;h1&gt;</code>. We’re using <code>1.2vmax</code> as the multiplier this time, meaning it grows more than the body text, which is multiplied by a smaller value, <code>.023vmax</code>.</p>

<p>This works for me. You can always tweak these values and see which works best for your use. Whatever the case, the <code>font-size</code> for this experiment is completely fluid and completely based on the <code>min()</code> function, adhering to my self-imposed constraint.</p>

<div class="partners__lead-place"></div>

<h2 id="margin-and-padding">Margin And Padding</h2>

<p>Spacing is a big part of layout, responsive or not. We need <code>margin</code> and <code>padding</code> to properly situate elements alongside other elements and give them breathing room, both inside and outside their box.</p>

<p>We’re going all-in with <code>min()</code> for this, too. We could use absolute units, like pixels, but those aren’t exactly responsive.</p>

<p><code>min()</code> can combine relative and absolute units so they are more effective. Let’s pair <code>vmin</code> with <code>px</code> this time:</p>

<pre><code class="language-css">div { margin: min(10vmin, 30px); }
</code></pre>

<p><code>10vmin</code> is likely to be smaller than <code>30px</code> when viewed on a small viewport. That’s why I’m allowing the margin to shrink dynamically this time around. As the viewport size increases, whereby <code>10vmin</code> exceeds <code>30px</code>, <code>min()</code> caps the value at <code>30px</code>, going no higher than that.</p>

<p>Notice, too, that I didn’t reach for <code>calc()</code> this time. Margins don’t really need to grow indefinitely with screen size, as too much spacing between containers or elements generally looks awkward on larger screens. This concept also works extremely well for padding, but we don’t have to go there. Instead, it might be better to stick with a single unit, preferably <code>em</code>, since it is relative to the element’s <code>font-size</code>. We can essentially “pass” the work that <code>min()</code> is doing on the <code>font-size</code> to the <code>margin</code> and <code>padding</code> properties because of that.</p>

<pre><code class="language-css">.card-info {
  font-size: min(6vmin, calc(1rem + 0.12vmax));
  padding: 1.2em;
}
</code></pre>

<p>Now, padding scales with the <code>font-size</code>, which is powered by <code>min()</code>.</p>

<h2 id="widths">Widths</h2>

<p>Setting <code>width</code> for a responsive design doesn’t have to be complicated, right? We could simply use a single percentage or viewport unit value to specify how much available horizontal space we want to take up, and the element will adjust accordingly. Though, container query units could be a happy path outside of this experiment.</p>

<p>But we’re <code>min()</code> all the way!</p>

<p><code>min()</code> comes in handy when setting constraints on how much an element responds to changes. We can set an upper limit of <code>650px</code> and, if the computed width tries to go larger, have the element settle at a full width of <code>100%</code>:</p>

<pre><code class="language-css">.container { width: min(100%, 650px); }
</code></pre>

<p>Things get interesting with text width. When the width of a text box is too long, it becomes uncomfortable to read through the texts. There are competing theories about how many characters per line of text is best for an optimal reading experience. For the sake of argument, let’s say that number should be between 50-75 characters. In other words, we ought to pack no more than 75 characters on a line, and we can do that with the <code>ch</code> unit, which is based on the <code>0</code> character’s size for whatever font is in use.</p>

<pre><code class="language-css">p {
  width: min(100%, 75ch);
}
</code></pre>

<p>This code basically says: <em>get as wide as needed but never wider than 75 characters.</em></p>

<div class="partners__lead-place"></div>

<h2 id="sizing-recipes-based-on-min">Sizing Recipes Based On <code>min()</code></h2>

<p>Over time, with a lot of tweaking and modifying of values, I have drafted a list of pre-defined values that I find work well for responsively styling different properties:</p>

<pre><code class="language-css">:root {
  --font-size-6x: min(7.5vmin, calc(2rem + 1.2vmax));
  --font-size-5x: min(6.5vmin, calc(1.1rem + 1.2vmax));
  --font-size-4x: min(4vmin, calc(0.8rem + 1.2vmax));
  --font-size-3x: min(6vmin, calc(1rem + 0.12vmax));
  --font-size-2x: min(4vmin, calc(0.85rem + 0.12vmax));
  --font-size-1x: min(2vmin, calc(0.65rem + 0.12vmax));
  --width-2x: min(100vw, 1300px);
  --width-1x: min(100%, 1200px);
  --gap-3x: min(5vmin, 1.5rem);
  --gap-2x: min(4.5vmin, 1rem);
  --size-10x: min(15vmin, 5.5rem);
  --size-9x: min(10vmin, 5rem);
  --size-8x: min(10vmin, 4rem);
  --size-7x: min(10vmin, 3rem);
  --size-6x: min(8.5vmin, 2.5rem);
  --size-5x: min(8vmin, 2rem);
  --size-4x: min(8vmin, 1.5rem);
  --size-3x: min(7vmin, 1rem);
  --size-2x: min(5vmin, 1rem);
  --size-1x: min(2.5vmin, 0.5rem);
}
</code></pre>

<p>This is how I approached my experiment because it helps me know what to reach for in a given situation:</p>

<pre><code class="language-css">h1 { font-size: var(--font-size-6x); }

.container {
  width: var(--width-2x);
  margin: var(--size-2x);
}

.card-grid { gap: var(--gap-3x); }
</code></pre>

<p>There we go! We have a heading that scales flawlessly, a container that’s responsive and never too wide, and a grid with dynamic spacing &mdash; all without a single media query. The <code>--size-</code> properties declared in the variable list are the most versatile, as they can be used for properties that require scaling, e.g., margins, paddings, and so on.</p>

<h2 id="the-final-result-again">The Final Result, Again</h2>

<p>I shared a video of the result, but here’s a link to the demo.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="wvVdPxL"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [min() website [forked]](https://codepen.io/smashingmag/pen/wvVdPxL) by <a href="https://codepen.io/vayospot">Vayo</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/wvVdPxL">min() website [forked]</a> by <a href="https://codepen.io/vayospot">Vayo</a>.</figcaption>
</figure>

<p>So, is <code>min()</code> the be-all, end-all for responsiveness? Absolutely not. Neither is a diet consisting entirely of container query units. I mean, it’s cool that we can scale an entire webpage like this, but the web is never a one-size-fits-all beanie.</p>

<p>If anything, I think this and what Chris demoed are <strong>warnings against dogmatic approaches to web design</strong> as a whole, not solely unique to responsive design. CSS features, including length units and functions, are tools in a larger virtual toolshed. Rather than getting too cozy with one feature or technique, explore the shed because you might find a better tool for the job.</p>

<div class="signature">
  <img src="https://www.smashingmagazine.com/images/logo/logo--red.png" alt="Smashing Editorial" width="35" height="46" loading="lazy" decoding="async" />
  <span>(gg, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item></channel></rss>