<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tools on Smashing Magazine — For Web Designers And Developers</title><link>https://www.smashingmagazine.com/category/tools/index.xml</link><description>Recent content in Tools on Smashing Magazine — For Web Designers And Developers</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 25 Dec 2025 10:03:08 +0000</lastBuildDate><item><author>Vitaly Friedman</author><title>How To Measure The Impact Of Features</title><link>https://www.smashingmagazine.com/2025/12/how-measure-impact-features-tars/</link><pubDate>Fri, 19 Dec 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/12/how-measure-impact-features-tars/</guid><description>Meet TARS — a simple, repeatable, and meaningful UX metric designed specifically to track the performance of product features. Upcoming part of the &lt;a href="https://measure-ux.com/">Measure UX &amp;amp; Design Impact&lt;/a> (use the code 🎟 &lt;code>IMPACT&lt;/code> to save 20% off today).</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/12/how-measure-impact-features-tars/" />
              <title>How To Measure The Impact Of Features</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>How To Measure The Impact Of Features</h1>
                  
                    
                    <address>Vitaly Friedman</address>
                  
                  <time datetime="2025-12-19T10:00:00&#43;00:00" class="op-published">2025-12-19T10:00:00+00:00</time>
                  <time datetime="2025-12-19T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>So we design and ship a <strong>shiny new feature</strong>. How do we know if it’s working? How do we measure and track its impact? There is <a href="https://measuringu.com/an-overview-of-70-ux-metrics/">no shortage in UX metrics</a>, but what if we wanted to establish a <strong>simple, repeatable</strong>, meaningful UX metric &mdash; specifically for our features? Well, let’s see how to do just that.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="975"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg"
			
			sizes="100vw"
			alt="Adrian Raudaschl&#39;s framework for measuring feature impact."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      With <a href='https://uxdesign.cc/tars-a-product-metric-game-changer-c523f260306a?sk=v2%2F2a9d7d1e-bae9-4875-9063-4b6a10ae110c'>TARS</a>, we can assess how effective features are and how well they are performing.(<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/1-impact-features-tars.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>I first heard about the <strong>TARS framework</strong> from Adrian H. Raudschl’s wonderful article on “<a href="https://uxdesign.cc/tars-a-product-metric-game-changer-c523f260306a?sk=v2%2F2a9d7d1e-bae9-4875-9063-4b6a10ae110c">How To Measure Impact of Features</a>”. Here, Adrian highlighted how his team tracks and decides which features to focus on &mdash; and then maps them against each other in a <strong>2×2 quadrants matrix</strong>.</p>

<p>It turned out to be a very useful framework to <strong>visualize</strong> the impact of UX work through the lens of business metrics.</p>

<p>Let’s see how it works.</p>

<h2 id="1-target-audience">1. Target Audience (%)</h2>

<p>We start by quantifying the <strong>target audience</strong> by exploring what percentage of a product’s users have the specific problem that a feature aims to solve. We can study existing or similar features that try to solve similar problems, and how many users engage with them.</p>

<p>Target audience <strong>isn’t the same</strong> as feature usage though. As Adrian noted, if we know that an existing Export Button feature is used by 5% of all users, it doesn’t mean that the target audience is 5%. <strong>More users</strong> might have the problem that the export feature is trying to solve, but they can’t find it.</p>

<blockquote>Question we ask: “What percentage of all our product’s users have that specific problem that a new feature aims to solve?”</blockquote>

<h2 id="2-a-adoption">2. A = Adoption (%)</h2>

<p>Next, we measure how well we are <strong>“acquiring”</strong> our target audience. For that, we track how many users actually engage <em>successfully</em> with that feature over a specific period of time.</p>

<p>We <strong>don’t focus on CTRs or session duration</strong> there, but rather if users <em>meaningfully</em> engage with it. For example, if anything signals that they found it valuable, such as sharing the export URL, the number of exported files, or the usage of filters and settings.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="395"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg"
			
			sizes="100vw"
			alt="The TARS Framework Step"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Adoption rates: from low adoption (<20%) to high adoption (>60%). Illustration by <a href='https://uxdesign.cc/tars-a-product-metric-game-changer-c523f260306a?sk=v2%2F2a9d7d1e-bae9-4875-9063-4b6a10ae110c'>Adrian Raudaschl</a>. (<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/2-impact-features-tars.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>High <strong>feature adoption</strong> (&gt;60%) suggests that the problem was impactful. Low adoption (&lt;20%) might imply that the problem has simple workarounds that people have relied upon. Changing habits takes time, too, and so low adoption in the beginning is expected.</p>

<p>Sometimes, low feature adoption has nothing to do with the feature itself, but rather <strong>where it sits in the UI</strong>. Users might never discover it if it’s hidden or if it has a confusing label. It must be obvious enough for people to stumble upon it.</p>

<p>Low adoption doesn’t always equal failure. If a problem only affects 10% of users, hitting 50–75% adoption within that specific niche means the feature is a <strong>success</strong>.</p>

<blockquote>Question we ask: “What percentage of active target users actually use the feature to solve that problem?”</blockquote>

<h2 id="3-retention">3. Retention (%)</h2>

<p>Next, we study whether a feature is actually used repeatedly. We measure the frequency of use, or specifically, how many users who engaged with the feature actually keep using it over time. Typically, it’s a strong signal for <strong>meaningful impact</strong>.</p>

<p>If a feature has &gt;50% retention rate (avg.), we can be quite confident that it has a <strong>high strategic importance</strong>. A 25–35% retention rate signals medium strategic significance, and retention of 10–20% is then low strategic importance.</p>

<blockquote>Question we ask: “Of all the users who meaningfully adopted a feature, how many came back to use it again?”</blockquote>

<h2 id="4-satisfaction-score-ces">4. Satisfaction Score (CES)</h2>

<p>Finally, we measure the <strong>level of satisfaction</strong> that users have with that feature that we’ve shipped. We don’t ask everyone &mdash; we ask only “retained” users. It helps us spot hidden troubles that might not be reflected in the retention score.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="395"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg"
			
			sizes="100vw"
			alt="Customer Satisfaction Score, measured with a survey"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      We ask users how easy it was to solve a problem after they used a feature. Illustration by <a href='https://uxdesign.cc/tars-a-product-metric-game-changer-c523f260306a?sk=v2%2F2a9d7d1e-bae9-4875-9063-4b6a10ae110c'>Adrian Raudaschl</a>. (<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/3-impact-features-tars.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once users actually used a feature multiple times, we ask them <strong>how easy it was to solve</strong> a problem after they used that feature &mdash; between “much more difficult” and “much easier than expected”. We know how we want to score.</p>

<h2 id="using-tars-for-feature-strategy">Using TARS For Feature Strategy</h2>

<p>Once we start measuring with TARS, we can calculate an <strong>S÷T score</strong> &mdash; the percentage of Satisfied Users ÷ Target Users. It gives us a sense of how well a feature is performing for our intended target audience. Once we do that for every feature, we can map all features across 4 quadrants in a <strong>2×2 matrix</strong>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg">
    
    <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/how-measure-impact-features-tars/4-impact-features-tars.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg"
			
			sizes="100vw"
			alt="Feature retention curves"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Evaluating features on a 2×2 matrix based on S/T score Illustration by <a href='https://uxdesign.cc/tars-a-product-metric-game-changer-c523f260306a?sk=v2%2F2a9d7d1e-bae9-4875-9063-4b6a10ae110c'>Adrian Raudaschl</a>. (<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/4-impact-features-tars.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Overperforming features</strong> are worth paying attention to: they have low retention but high satisfaction. It might simply be features that users don’t have to use frequently, but when they do, it’s extremely effective.</p>

<p><strong>Liability features</strong> have high retention but low satisfaction, so perhaps we need to work on them to improve them. And then we can also identify <strong>core features</strong> and project features &mdash; and have a conversation with designers, PMs, and engineers on what we should work on next.</p>

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

<h2 id="conversion-rate-is-not-a-ux-metric">Conversion Rate Is Not a UX Metric</h2>

<p>TARS doesn’t cover conversion rate, and for a good reason. As <a href="https://www.linkedin.com/posts/fabian-lenz-digital-experience-leadership_conversion-rate-is-not-a-ux-metric-yes-activity-7394261839506739200-78G9">Fabian Lenz noted</a>, conversion is often considered to be the <strong>ultimate indicator of success</strong> &mdash; yet in practice it’s always very difficult to present a clear connection between smaller design initiatives and big conversion goals.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="274"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png"
			
			sizes="100vw"
			alt="Chart comparing Leading vs Lagging Measures for UX metrics"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Leading vs. Lagging Measures by <a href='https://measuringu.com/leading-vs-lagging/'>Jeff Sauro and James R. Lewis</a>. (But please do avoid NPS at all costs). (<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/5-impact-features-tars.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The truth is that almost everybody on the team is working towards better conversion. An uptick might be connected to <strong>many different initiatives</strong> &mdash; from sales and marketing to web performance boost to seasonal effects to UX initiatives.</p>

<p>UX can, of course, improve conversion, but it’s not really a UX metric. Often, people simply <strong>can’t choose the product</strong> they are using. And often a desired business outcome comes out of necessity and struggle, rather than trust and appreciation.</p>

<h3 id="high-conversion-despite-bad-ux">High Conversion Despite Bad UX</h3>

<p>As Fabian <a href="https://www.linkedin.com/posts/fabian-lenz-digital-experience-leadership_conversion-rate-is-not-a-ux-metric-yes-activity-7394261839506739200-78G9/">writes</a>, <strong>high conversion rate</strong> can happen despite poor UX, because:</p>

<ul>
<li><strong>Strong brand power</strong> pulls people in,</li>
<li>Aggressive but effective <strong>urgency tactics</strong>,</li>
<li>Prices are extremely attractive,</li>
<li>Marketing performs brilliantly,</li>
<li>Historical customer loyalty,</li>
<li>Users simply have no alternative.</li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="509"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg"
			
			sizes="100vw"
			alt="UX Scorecard and design metrics overview"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A practical overview of design metrics and UX scorecards: <a href='https://uxplanet.org/measuring-ux-your-first-step-towards-objective-evaluation-a408b312777b'>Measuring UX: Your First Step Towards Objective Evaluation</a> by Roman Videnov. (<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/6-impact-features-tars.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="low-conversion-despite-great-ux">Low Conversion Despite Great UX</h3>

<p>At the same time, a low conversion rate can occur despite great UX, because:</p>

<ul>
<li><strong>Offers aren’t relevant</strong> to the audience,</li>
<li><strong>Users don’t trust the brand</strong>,</li>
<li>Poor business model or high risk of failure,</li>
<li>Marketing doesn’t reach the right audience,</li>
<li>External factors (price, timing, competition).</li>
</ul>

<p>An improved conversion is the <strong>positive outcome of UX initiatives</strong>. But good UX work typically improves task completion, reduces time on task, minimizes errors, and avoids decision paralysis. And there are plenty of <a href="https://www.linkedin.com/posts/vitalyfriedman_how-to-measure-ux-httpslnkdine5uedtzy-activity-7332664809382952960-HERA">actionable design metrics we could use</a> to track UX and drive sustainable success.</p>

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

<p><strong>Product metrics</strong> alone don’t always provide an accurate view of how well a product performs. Sales might perform well, but users might be extremely inefficient and frustrated. Yet the churn is low because users can’t choose the tool they are using.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg">
    
    <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/how-measure-impact-features-tars/7-impact-features-tars.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg"
			
			sizes="100vw"
			alt="Chart comparing Leading vs Lagging Measures for UX metrics"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://www.linkedin.com/posts/vitalyfriedman_ux-design-activity-7140641630507687936-YTI7'>Design KPIs and UX Metrics</a>, a quick overview by yours truly. Numbers are, of course, placeholders. (<a href='https://files.smashing.media/articles/how-measure-impact-features-tars/7-impact-features-tars.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We need UX metrics to understand and improve user experience. What I love most about TARS is that it’s a neat way to connect customers’ usage and <strong>customers’ experience with relevant product metrics</strong>. Personally, I would extend TARS with <a href="https://www.linkedin.com/posts/vitalyfriedman_ux-design-activity-7140641630507687936-YTI7">UX-focused metrics and KPIs</a> as well &mdash; depending on the needs of the project.</p>

<p>Huge thanks to <a href="https://www.linkedin.com/in/adrian-raudaschl/">Adrian H. Raudaschl</a> for putting it together. And if you are interested in metrics, I highly recommend you follow him for practical and useful guides all around just that!</p>

<h2 id="meet-how-to-measure-ux-and-design-impact">Meet “How To Measure UX And Design Impact”</h2>

<p>You can find more details on <strong>UX Strategy</strong> in 🪴&nbsp;<a href="https://measure-ux.com/"><strong>Measure UX &amp; Design Impact</strong></a> (8h), a practical guide for designers and UX leads to measure and show your UX impact on business. Use the code 🎟 <code>IMPACT</code> to save 20% off today. <a href="https://measure-ux.com/">Jump to the details</a>.</p>

<figure style="margin-bottom:0;padding-bottom:0" class="article__image">
    <a href="https://measure-ux.com/" title="How To Measure UX and Design Impact, with Vitaly Friedman">
    <img width="900" height="466" style="border-radius: 11px" src="https://files.smashing.media/articles/ux-metrics-video-course-release/measure-ux-and-design-impact-course.png" alt="How to Measure UX and Design Impact, with Vitaly Friedman.">
    </a>
</figure>

<div class="book-cta__inverted"><div class="book-cta" data-handler="ContentTabs" data-mq="(max-width: 480px)"><nav class="content-tabs content-tabs--books"><ul><li class="content-tab"><a href="#"><button class="btn btn--small btn--white btn--white--bordered">
Video + UX Training</button></a></li><li class="content-tab"><a href="#"><button class="btn btn--small btn--white btn--white--bordered">Video only</button></a></li></ul></nav><div class="book-cta__col book-cta__hardcover content-tab--content"><h3 class="book-cta__title"><span>Video + UX Training</span></h3><span class="book-cta__price"><span><span class=""><span class="currency-sign">$</span>&nbsp;<span>495<span class="sup">.00</span></span></span> <span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>799<span class="sup">.00</span></span></span></span></span>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3081832?price_id=3951439" class="btn btn--full btn--medium btn--text-shadow">
Get Video + UX Training<div></div></a><p class="book-cta__desc">25 video lessons (8h) + <a href="https://smashingconf.com/online-workshops/workshops/vitaly-friedman-impact-design/">Live UX Training</a>.<br>100 days money-back-guarantee.</p></div><div class="book-cta__col book-cta__ebook content-tab--content"><h3 class="book-cta__title"><span>Video only</span></h3><div data-audience="anonymous free supporter" data-remove="true"><span class="book-cta__price" data-handler="PriceTag"><span><span class=""><span class="currency-sign">$</span>&nbsp;<span>250<span class="sup">.00</span></span></span><span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>395<span class="sup">.00</span></span></span></span></div>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3081832?price_id=3950630" class="btn btn--full btn--medium btn--text-shadow">
Get the video course<div></div></a><p class="book-cta__desc" data-audience="anonymous free supporter" data-remove="true">25 video lessons (8h). Updated yearly.<br>Also available as a <a href="https://smart-interface-design-patterns.thinkific.com/enroll/3570306?price_id=4503439">UX Bundle with 3 video courses.</a></p></div><span></span></div></div>

<h2 id="useful-resources">Useful Resources</h2>

<ul>
<li>“<a href="https://measure-ux.com">How To Measure UX and Design Impact</a>”, by yours truly</li>
<li>“<a href="https://thecdo.school/books">Business Thinking For Designers</a>”, by Ryan Rumsey</li>
<li>“<a href="https://www.linkedin.com/feed/update/urn:li:activity:7338462034763661312/">ROI of Design Project</a></li>
<li>“<a href="https://articles.centercentre.com/how-the-right-ux-metrics-show-game-changing-value/">How the Right UX Metrics Show Game-Changing Value</a>”, by Jared Spool</li>
<li>“<a href="https://www.linkedin.com/posts/vitalyfriedman_ux-design-research-activity-7164173642887606274-rEqq">Research Sample Size Calculators</a>”</li>
</ul>

<h3 id="further-reading">Further Reading</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2025/11/designing-for-stress-emergency/">Designing For Stress And Emergency</a>”, Vitaly Friedman</li>
<li>“<a href="https://www.smashingmagazine.com/2025/10/ai-ux-achieve-more-with-less/">AI In UX: Achieve More With Less</a>”, Paul Boag</li>
<li>“<a href="https://www.smashingmagazine.com/2025/11/accessibility-problem-authentication-methods-captcha/">The Accessibility Problem With Authentication Methods Like CAPTCHA</a>”, Eleanor Hecks</li>
<li>“<a href="https://www.smashingmagazine.com/2025/09/from-prompt-to-partner-designing-custom-ai-assistant/">From Prompt To Partner: Designing Your Custom AI Assistant</a>”, Lyndon Cerejo</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>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Brecht De Ruyte</author><title>State, Logic, And Native Power: CSS Wrapped 2025</title><link>https://www.smashingmagazine.com/2025/12/state-logic-native-power-css-wrapped-2025/</link><pubDate>Tue, 09 Dec 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/12/state-logic-native-power-css-wrapped-2025/</guid><description>CSS Wrapped 2025 is out! We’re entering a world where CSS can increasingly handle logic, state, and complex interactions once reserved for JavaScript. It’s no longer just about styling documents, but about crafting dynamic, ergonomic, and robust applications with a native toolkit more powerful than ever. Here’s an unpacking of the highlights and how they connect to the broader evolution of modern CSS.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/12/state-logic-native-power-css-wrapped-2025/" />
              <title>State, Logic, And Native Power: CSS Wrapped 2025</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>State, Logic, And Native Power: CSS Wrapped 2025</h1>
                  
                    
                    <address>Brecht De Ruyte</address>
                  
                  <time datetime="2025-12-09T10:00:00&#43;00:00" class="op-published">2025-12-09T10:00:00+00:00</time>
                  <time datetime="2025-12-09T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>If I were to divide CSS evolutions into categories, we have moved far beyond the days when we simply asked for <code>border-radius</code> to feel like we were living in the future. We are currently living in a moment where the platform is handing us tools that don’t just tweak the visual layer, but fundamentally redefine how we architect interfaces. I thought the number of features announced in 2024 couldn’t be topped. I’ve never been so happily wrong.</p>

<p>The Chrome team’s “<a href="https://chrome.dev/css-wrapped-2025/"><strong>CSS Wrapped 2025</strong></a>” is not just a list of features; it is a manifesto for a dynamic, native web. As someone who has spent a couple of years documenting these evolutions &mdash; from <a href="https://www.smashingmagazine.com/2024/08/css5-era-evolution/">defining “CSS5” eras</a> to the intricacies of <a href="https://www.smashingmagazine.com/2024/05/modern-css-layouts-no-framework/">modern layout utilities</a> &mdash; I find myself looking at this year’s wrap-up with a huge sense of excitement. We are seeing a shift towards “Optimized Ergonomics” and “Next-gen interactions” that allow us to stop fighting the code and start sculpting interfaces in their natural state.</p>

<p>In this article, you can find <strong>a comprehensive look at the standout features from Chrome’s report</strong>, viewed through the lens of my recent experiments and hopes for the future of the platform.</p>

<h2 id="the-component-revolution-finally-a-native-customizable-select">The Component Revolution: Finally, A Native Customizable Select</h2>

<p>For years, we have relied on heavy JavaScript libraries to style dropdowns, a “decades-old problem” that the platform has finally solved. As I detailed in <a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">my deep dive into the history of the customizable select</a> (and related articles), this has been a long road involving <a href="https://open-ui.org/">Open UI</a>, bikeshedding names like <code>&lt;selectmenu&gt;</code> and <code>&lt;selectlist&gt;</code>, and finally landing on a solution that re-uses the existing <code>&lt;select&gt;</code> element.</p>

<p>The introduction of <code>appearance: base-select</code> is a strong foundation. It allows us to fully customize the <code>&lt;select&gt;</code> element &mdash; including the button and the dropdown list (via <code>::picker(select)</code>) &mdash; using standard CSS. Crucially, this is built with progressive enhancement in mind. By wrapping our styles in a feature query, we ensure a seamless experience across all browsers.</p>

<p>We can opt in to this new behavior without breaking older browsers:</p>

<pre><code class="language-css">select {
  /&#42; Opt-in for the new customizable select &#42;/
  @supports (appearance: base-select) {
    &, &::picker(select) {
      appearance: base-select;
    }
  }
}
</code></pre>

<p>The fantastic addition to allow rich content inside options, such as images or flags, is a lot of fun. We can create all sorts of selects nowadays:</p>

<ul>
<li><strong>Demo:</strong> I created a <a href="https://codepen.io/utilitybend/pen/ByawgNN">Poké-adventure demo</a> showing how the new <code>&lt;selectedcontent&gt;</code> element can clone rich content (like a Pokéball icon) from an option directly into the button.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="JoXwwoZ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [A customizable select with images inside of the options and the selectedcontent [forked]](https://codepen.io/smashingmag/pen/JoXwwoZ) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/JoXwwoZ">A customizable select with images inside of the options and the selectedcontent [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<ul>
<li><strong>Demo:</strong> A comprehensive look at <a href="https://codepen.io/utilitybend/pen/GgRrLWb">styling the select with only pseudo-elements</a>.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="pvyqqJR"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [A customizable select with only pseudo-elements [forked]](https://codepen.io/smashingmag/pen/pvyqqJR) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvyqqJR">A customizable select with only pseudo-elements [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<ul>
<li><strong>Demo:</strong> Or you can kick it up a notch with this <a href="https://codepen.io/utilitybend/pen/ByoBMBm">Menu selection demo using optgroups</a>.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="myPaaJZ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [An actual Select Menu with optgroups [forked]](https://codepen.io/smashingmag/pen/myPaaJZ) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/myPaaJZ">An actual Select Menu with optgroups [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<p>This feature alone signals a massive shift in how we will build forms, reducing dependencies and technical debt.</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="scroll-markers-and-the-death-of-the-javascript-carousel">Scroll Markers And The Death Of The JavaScript Carousel</h2>

<p>Creating carousels has historically been a friction point between developers and clients. Clients love them, developers dread the JavaScript required to make them accessible and performant. The arrival of <code>::scroll-marker</code> and <code>::scroll-button()</code> pseudo-elements changes this dynamic entirely.</p>

<p>These features allow us to create navigation dots and scroll buttons purely with CSS, linked natively to the scroll container. As I wrote on my blog, this was <a href="https://utilitybend.com/blog/love-at-first-slide-creating-a-carousel-purely-out-of-css">Love at first slide</a>. The ability to create a fully functional, accessible slider without a single line of JavaScript is not just convenient; it is a triumph for performance. There are some accessibility concerns around this feature, and even though these are valid, there might be a way for us developers to make it work. The good thing is, all these UI changes are making it a lot easier than custom DOM manipulation and dragging around aria tags, but I digress…</p>

<p>We can now group markers automatically using <code>scroll-marker-group</code> and style the buttons using anchor positioning to place them exactly where we want.</p>

<div class="break-out">
<pre><code class="language-css">.carousel {
  overflow-x: auto;
  scroll-marker-group: after; /&#42; Creates the container for dots &#42;/

  /&#42; Create the buttons &#42;/
  &::scroll-button(inline-end),
  &::scroll-button(inline-start) {
    content: " ";
    position: absolute;
    /&#42; Use anchor positioning to center them &#42;/
    position-anchor: --carousel;
    top: anchor(center);
  }

  /&#42; Create the markers on the children &#42;/
  div {
    &::scroll-marker {
      content: " ";
      width: 24px;
      border-radius: 50%;
      cursor: pointer;
    }
    /&#42; Highlight the active marker &#42;/
    &::scroll-marker:target-current {
      background: white;
    }
  }
}
</code></pre>
</div>

<ul>
<li><strong>Demo:</strong> My experiment creating a <a href="https://codepen.io/utilitybend/pen/vEBQxNb">carousel purely out of HTML and CSS</a>, using anchor positioning to place the buttons.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ogxJJjQ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Carousel Pure HTML and CSS [forked]](https://codepen.io/smashingmag/pen/ogxJJjQ) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ogxJJjQ">Carousel Pure HTML and CSS [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<ul>
<li><strong>Demo:</strong> A <a href="https://codepen.io/utilitybend/pen/bNbXZWb">Webshop slick slider remake</a> using <code>attr()</code> to pull background images dynamically into the markers.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="gbrZZPY"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Webshop slick slider remake in CSS [forked]](https://codepen.io/smashingmag/pen/gbrZZPY) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/gbrZZPY">Webshop slick slider remake in CSS [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<h2 id="state-queries-sticky-thing-stuck-snappy-thing-snapped">State Queries: Sticky Thing Stuck? Snappy Thing Snapped?</h2>

<p>For a long time, we have lacked the ability to know if a <a href="https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css">“sticky thing is stuck” or if a “snappy item is snapped”</a> without relying on IntersectionObserver hacks. Chrome 133 introduced scroll-state queries, allowing us to query these states declaratively.</p>

<p>By setting <code>container-type: scroll-state</code>, we can now style children based on whether they are stuck, snapped, or overflowing. This is a massive “quality of life” improvement that I have been eagerly waiting for since CSS Day 2023. It has even evolved a lot since we can also see the direction of the scroll, lovely!</p>

<p>For a simple example: we can finally apply a shadow to a header <em>only</em> when it is actually sticking to the top of the viewport:</p>

<pre><code class="language-css">.header-container {
  container-type: scroll-state;
  position: sticky;
  top: 0;

  header {
    transition: box-shadow 0.5s ease-out;
    /&#42; The query checks the state of the container &#42;/
    @container scroll-state(stuck: top) {
      box-shadow: rgba(0, 0, 0, 0.6) 0px 12px 28px 0px;
    }
  }
}
</code></pre>

<ul>
<li><strong>Demo:</strong> A <a href="https://codepen.io/utilitybend/pen/XWLQPOe">sticky header</a> that only applies a shadow when it is actually stuck.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="raeooxY"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Sticky headers with scroll-state query, checking if the sticky element is stuck [forked]](https://codepen.io/smashingmag/pen/raeooxY) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/raeooxY">Sticky headers with scroll-state query, checking if the sticky element is stuck [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<ul>
<li><strong>Demo:</strong> A <a href="https://codepen.io/utilitybend/pen/MWMZoqp">Pokémon-themed list</a> that uses scroll-state queries combined with anchor positioning to move a frame over the currently snapped character.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="vEGvvLM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scroll-state query to check which item is snapped with CSS, Pokemon version [forked]](https://codepen.io/smashingmag/pen/vEGvvLM) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/vEGvvLM">Scroll-state query to check which item is snapped with CSS, Pokemon version [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

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

<h2 id="optimized-ergonomics-logic-in-css">Optimized Ergonomics: Logic In CSS</h2>

<p>The “Optimized Ergonomics” section of CSS Wrapped highlights features that make our workflows more intuitive. Three features stand out as transformative for how we write logic:</p>

<ol>
<li><strong><code>if()</code> Statements</strong><br />
We are finally getting conditionals in CSS. The <code>if()</code> function acts like a ternary operator for stylesheets, allowing us to apply values based on media, support, or style queries inline. This reduces the need for verbose <code>@media</code> blocks for single property changes.</li>
<li><strong><code>@function</code> functions</strong><br />
We can finally move some logic to a different place, resulting in some cleaner files, a real quality of life feature.</li>
<li><strong><code>sibling-index()</code> and <code>sibling-count()</code></strong><br />
These tree-counting functions solve the issue of staggering animations or styling items based on list size. As I explored in <a href="https://utilitybend.com/blog/styling-siblings-with-css-has-never-been-easier-experimenting-with-sibling-count-and-sibling-index">Styling siblings with CSS has never been easier</a>, this eliminates the need to hard-code custom properties (like <code>--index: 1</code>) in our HTML.</li>
</ol>

<h3 id="example-calculating-layouts">Example: Calculating Layouts</h3>

<p>We can now write concise mathematical formulas. For example, staggering an animation for cards entering the screen becomes trivial:</p>

<pre><code class="language-css">.card-container &gt; &#42; {
  animation: reveal 0.6s ease-out forwards;
  /&#42; No more manual --index variables! &#42;/
  animation-delay: calc(sibling-index() &#42; 0.1s);
}
</code></pre>

<p>I even experimented with using these functions along with trigonometry to place items in a perfect circle without any JavaScript.</p>

<ul>
<li><strong>Demo:</strong> <a href="https://codepen.io/utilitybend/pen/wBKQPLr">Staggering card animations dynamically</a>.</li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="RNaEERz"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Stagger cards using sibling-index() [forked]](https://codepen.io/smashingmag/pen/RNaEERz) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/RNaEERz">Stagger cards using sibling-index() [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<ul>
<li><strong>Demo:</strong> Placing items in a <a href="https://codepen.io/utilitybend/pen/VYvVXLN">perfect circle</a> using <code>sibling-index</code>, <code>sibling-count</code>, and the new CSS <code>@function</code> feature.
<br /></li>
</ul>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="XJdoojZ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [The circle using sibling-index, sibling-count and functions [forked]](https://codepen.io/smashingmag/pen/XJdoojZ) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/XJdoojZ">The circle using sibling-index, sibling-count and functions [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<h2 id="my-css-to-do-list-features-i-can-t-wait-to-try">My CSS To-Do List: Features I Can’t Wait To Try</h2>

<p>While I have been busy sculpting selects and transitions, the “CSS Wrapped 2025” report is packed with other goodies that I haven’t had the chance to fire up in CodePen yet. These are high on my list for my next experiments:</p>

<h3 id="anchored-container-queries">Anchored Container Queries</h3>

<p>I used CSS Anchor Positioning for the buttons in my carousel demo, but “CSS Wrapped” highlights an evolution of this: <strong>Anchored Container Queries</strong>. This solves a problem we’ve all had with tooltips: if the browser flips the tooltip from top to bottom because of space constraints, the “arrow” often stays pointing the wrong way. With anchored container queries (<code>@container anchored(fallback: flip-block)</code>), we can style the element based on which fallback position the browser actually chose.</p>

<h3 id="nested-view-transition-groups">Nested View Transition Groups</h3>

<p>View Transitions have been a revolution, but they came with a specific trade-off: they flattened the element tree, which often broke 3D transforms or overflow: clip. I always had a feeling that it was missing something, and this might just be the answer. By using <code>view-transition-group: nearest</code>, we can finally nest transition groups within each other.</p>

<p>This allows us to maintain clipping effects or 3D rotations during a transition &mdash; something that was previously impossible because the elements were hoisted up to the top level.</p>

<pre><code class="language-css">.card img {
  view-transition-name: photo;
  view-transition-group: nearest; /&#42; Keep it nested! &#42;/
}
</code></pre>

<h3 id="typography-and-shapes">Typography and Shapes</h3>

<p>Finally, the ergonomist in me is itching to try <strong>Text Box Trim</strong>, which promises to remove that annoying extra whitespace above and below text content (the leading) to finally achieve perfect vertical alignment. And for the creative side, <code>corner-shape</code> and the <code>shape()</code> function are opening up non-rectangular layouts, allowing for “squaricles” and complex paths that respond to CSS variables. That being said, I can’t wait to have a design full of squircles!</p>

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

<h2 id="a-hopeful-future">A Hopeful Future</h2>

<p>We are witnessing a world where <strong>CSS is becoming capable of handling logic, state, and complex interactions that previously belonged to JavaScript</strong>. Features like <code>moveBefore</code> (preserving DOM state for iframes/videos) and <code>attr()</code> (using types beyond strings for colors and grids) further cement this reality.</p>

<p>While some of these features are currently experimental or specific to Chrome, the momentum is undeniable. We must hope for continued support across all browsers through initiatives like Interop to ensure these capabilities become the baseline. That being said, having browser engines is just as important as having all these awesome features in “Chrome first”. These new features need to be discussed, tinkered with, and tested before ever landing in browsers.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aIt%20is%20a%20fantastic%20moment%20to%20get%20into%20CSS.%20We%20are%20no%20longer%20just%20styling%20documents;%20we%20are%20crafting%20dynamic,%20ergonomic,%20and%20robust%20applications%20with%20a%20native%20toolkit%20that%20is%20more%20powerful%20than%20ever.%0a&url=https://smashingmagazine.com%2f2025%2f12%2fstate-logic-native-power-css-wrapped-2025%2f">
      
It is a fantastic moment to get into CSS. We are no longer just styling documents; we are crafting dynamic, ergonomic, and robust applications with a native toolkit that is more powerful than ever.

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

<p>Let’s get going with this new era and spread the word.</p>

<p>This is <a href="https://chrome.dev/css-wrapped-2025/">CSS Wrapped</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>Mansoor Ahmed Khan</author><title>From Chaos To Clarity: Simplifying Server Management With AI And Automation</title><link>https://www.smashingmagazine.com/2025/11/simplifying-server-management-ai-automation/</link><pubDate>Tue, 18 Nov 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/11/simplifying-server-management-ai-automation/</guid><description>Server chaos doesn’t have to be the norm. AI-ready infrastructure and automation can bring clarity, performance, and focus back to your web work.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/11/simplifying-server-management-ai-automation/" />
              <title>From Chaos To Clarity: Simplifying Server Management With AI And Automation</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>From Chaos To Clarity: Simplifying Server Management With AI And Automation</h1>
                  
                    
                    <address>Mansoor Ahmed Khan</address>
                  
                  <time datetime="2025-11-18T10:00:00&#43;00:00" class="op-published">2025-11-18T10:00:00+00:00</time>
                  <time datetime="2025-11-18T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Cloudways</b></p>
                

<p>If you build or manage websites for a living, you know the feeling. Your day is a constant juggle; one moment you’re fine-tuning a design, the next you’re troubleshooting a slow server or a mysterious error. Daily management of a complex web of plugins, integrations, and performance tools often feels like you’re just reacting to problems—putting out fires instead of building something new.</p>

<p>This reactive cycle is exhausting, and it pulls your focus away from meaningful work and into the technical weeds. A recent industry event, <a href="https://www.cloudways.com/en/bfcm-prepathon.php">Cloudways Prepathon 2025</a>, put a sharp focus on this very challenge. The discussions made it clear: the future of web work demands a better way. It requires an infrastructure that’s ready for AI; one that can actively help you turn this daily chaos into clarity.</p>

<p><em>The stakes for performance are higher than ever.</em></p>

<p>Suhaib Zaheer, SVP of Managed Hosting at DigitalOcean, and Ali Ahmed Khan, Sr. Director of Product Management, shared a telling statistic during their panel: <strong><a href="https://www.thinkwithgoogle.com/consumer-insights/consumer-trends/mobile-site-load-time-statistics/">53% of mobile visitors</a> will leave a site if it takes more than three seconds to load.</strong></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg">
    
    <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/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg"
			
			sizes="100vw"
			alt="Google data showing mobile page speed"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Data from Google underscores the critical importance of mobile page speed for retaining visitors. (Image Source: <a href='https://www.thinkwithgoogle.com/consumer-insights/consumer-trends/mobile-site-load-time-statistics/'>Think with Google</a>) (<a href='https://files.smashing.media/articles/simplifying-server-management-ai-automation/1-google-data-mobile-page-speed.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Think about that for a second, and within half that time, your potential traffic is gone. This isn’t just about a slow website, but about lost trust, abandoned carts, and missed opportunities. Performance is no longer just a feature; it’s the foundation of user experience. And in today’s landscape, automation is the key to maintaining it consistently.</p>

<p>So how do we stop reacting and start preventing?</p>

<h2 id="the-old-way-a-constant-state-of-alert">The Old Way: A Constant State Of Alert</h2>

<p>For too long, server management has worked like this: something breaks, you receive an alert (or worse, a client complaint), and you start digging. You log into your server, check logs, try to correlate different metrics, and eventually (hopefully) find the root cause. Then you manually apply a fix.</p>

<p>This process is fragile and relies on your constant attention while eating up hours that could be spent on development, strategy, or client work. For freelancers and small teams, this time is your most valuable asset. Every minute spent manually diagnosing a disk space issue or a web stack failure is a minute not spent on growing your business.</p>

<p>The problem isn&rsquo;t a lack of tools. It&rsquo;s that most tools just show you the data; they don&rsquo;t help you understand it or act on it. They add to the noise instead of providing clarity.</p>

<h2 id="a-new-approach-from-diagnosis-to-automatic-resolution">A New Approach: From Diagnosis To Automatic Resolution</h2>

<p>This is where a shift towards intelligent automation changes the game. Tools like <a href="https://www.cloudways.com/en/cloudways-ai-copilot.php">Cloudways Copilot</a>, which became generally available earlier this year, are built specifically to simplify this workflow. The goal is straightforward: combine AI-driven diagnostics with automated fixes to predict and resolve performance issues before they affect your users.</p>

<p>Here’s a practical look at how it works.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.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/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png"
			
			sizes="100vw"
			alt="Cloudways Copilot workflow"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Cloudways Copilot workflow: Continuous monitoring leads to instant alerts, AI-powered diagnosis, and actionable recommendations. (Image source: <a href='https://www.cloudways.com/en/cloudways-ai-copilot.php'>Cloudways</a>) (<a href='https://files.smashing.media/articles/simplifying-server-management-ai-automation/2-cloudways-copilot-workflow.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Imagine your site starts running slowly. In the past, you&rsquo;d begin the tedious investigation.</p>

<h3 id="1-the-ai-insights">1. The AI Insights</h3>

<p>Instead of a generic &ldquo;high CPU&rdquo; alert, you get a detailed insight. It tells you what happened (e.g., &ldquo;MySQL process is consuming excessive resources&rdquo;), why it happened (e.g., &ldquo;caused by a poorly optimized query from a recent plugin update&rdquo;), and provides a step-by-step guide to fix it manually. This alone cuts diagnosis time from 30-40 minutes down to about five. You understand the problem, not just the diagnosis.</p>

<h3 id="2-the-smartfix">2. The SmartFix</h3>

<p>This is where it moves from helpful to transformative. For common issues, you don’t just get a manual guide. You get a one-click <em>SmartFix</em> button. After reviewing the actions Copilot will take, you can let it automatically resolve the issue. It applies the necessary steps safely and without you needing to touch a command line. This is the clarity we’re talking about. The system doesn’t just tell you about the problem; it solves it for you.</p>

<p>For developers managing multiple sites, this is a fundamental change. It means you can handle routine server issues at scale. A disk cleanup that would have required logging into ten different servers can now be handled with a few clicks. It frees your brain from repetitive troubleshooting and lets you focus on the work that actually requires your expertise.</p>

<h2 id="building-an-ai-ready-foundation">Building An AI-Ready Foundation</h2>

<p>The principles discussed at Prepathon go beyond any single tool. The theme was about building a resilient foundation. Meeky Hwang, CEO at Ndevr, introduced the <em>&ldquo;3E Framework,&rdquo;</em> which perfectly applies here. A strong platform must balance:</p>

<ul>
<li><strong>Audience Experience</strong><br />
What your visitors see and feel—blazing speed and seamless operation.</li>
<li><strong>Creator Experience</strong><br />
The workflow for you and your team—managing content and marketing without technical friction.</li>
<li><strong>Developer Experience</strong><br />
The backend foundation—server management that is secure, stable, and efficient.</li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.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/simplifying-server-management-ai-automation/3-3e-framework.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.png"
			
			sizes="100vw"
			alt="3E Framework"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A balanced platform is a resilient one. The 3E Framework shows how a strong foundation depends on three connected experiences. (Image source: <a href='https://www.cloudways.com/en/video/event-replays/prepathon-2025/from-fragile-to-ai-ready-websites-prepathon-2025'>Meeky Hwang / Ndevr</a>) (<a href='https://files.smashing.media/articles/simplifying-server-management-ai-automation/3-3e-framework.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>AI-driven server management directly strengthens all three. A faster, more stable server improves the <em>Audience Experience</em>. Fewer emergencies and simpler workflows improve the <em>Creator</em> and <em>Developer Experience</em>. When these are aligned, you can scale with confidence.</p>

<h2 id="this-isn-t-about-replacing-you">This Isn’t About Replacing You</h2>

<p>It’s important to be clear. This isn’t about replacing the developer but about augmenting your capabilities. As Vito Peleg, Co-founder &amp; CEO at Atarim, noted during <a href="https://www.cloudways.com/en/video/event-replays/prepathon-2025/whats-truly-working-in-ai-marketing-and-tech-prepathon-2025">Prepathon</a>:</p>

<blockquote>“We're all becoming prompt engineers in the modern world. Our job is no longer to do the task, but to orchestrate the fleet of AI agents that can do it at a scale we never could alone.”<br /><br />&mdash; Vito Peleg, Co-founder & CEO at Atarim</blockquote>

<p>Think of <a href="https://www.cloudways.com/en/cloudways-ai-copilot.php">Cloudways Copilot</a> as an expert sysadmin on your team. It handles the routine, often tedious, work. It alerts you to what’s important and provides clear, actionable context. This gives you back the mental space and time to focus on architecture, innovation, and client strategy.</p>

<blockquote>“The challenge isn’t managing servers anymore &mdash; it’s managing focus,”<br /><br /><a href="https://www.linkedin.com/in/zaheersuhaib/">Suhaib Zaheer</a> noted.<br /><br />“AI-driven infrastructure should help developers spend less time reacting to issues and more time creating better digital experiences.”</blockquote>

<h2 id="a-practical-path-forward">A Practical Path Forward</h2>

<p>For freelancers, WordPress experts, and small agency developers, this shift offers a tangible way to:</p>

<ul>
<li>Drastically reduce the hours spent manually troubleshooting infrastructure issues.</li>
<li>Implement predictive monitoring that catches slowdowns and bottlenecks early.</li>
<li>Manage your entire stack through clear, plain-English AI insights instead of raw data.</li>
<li>Balance speed, security, and uptime without needing an enterprise-scale budget or team.</li>
</ul>

<p>The goal is to make powerful infrastructure simple, while also giving you back control and your time so you can focus on what you do best: creating exceptional web experiences.</p>

<p><em>You can <a href="https://unified.cloudways.com/signup?coupon=BFCM5050">use promo code BFCM5050</a> to get 50% off for 3 months plus 50 Free Migrations using Cloudways. This offer is valid from November 18th to December 4th, 2025.</em></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>Godstime Aburu</author><title>CSS Gamepad API Visual Debugging With CSS Layers</title><link>https://www.smashingmagazine.com/2025/11/css-gamepad-api-visual-debugging-css-layers/</link><pubDate>Fri, 14 Nov 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/11/css-gamepad-api-visual-debugging-css-layers/</guid><description>Debugging controllers can be a real pain. Here’s a deep dive into how CSS helps clean it up and how to build a reusable visual debugger for your own projects.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/11/css-gamepad-api-visual-debugging-css-layers/" />
              <title>CSS Gamepad API Visual Debugging With CSS Layers</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>CSS Gamepad API Visual Debugging With CSS Layers</h1>
                  
                    
                    <address>Godstime Aburu</address>
                  
                  <time datetime="2025-11-14T13:00:00&#43;00:00" class="op-published">2025-11-14T13:00:00+00:00</time>
                  <time datetime="2025-11-14T13:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>When you plug in a controller, you mash buttons, move the sticks, pull the triggers… and as a developer, you see none of it. The browser’s picking it up, sure, but unless you’re logging numbers in the console, it’s invisible. That’s the headache with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API">Gamepad API</a>.</p>

<p>It’s been around for years, and it’s actually pretty powerful. You can read buttons, sticks, triggers, the works. But most people don’t touch it. Why? Because there’s no feedback. No panel in developer tools. No clear way to know if the controller’s even doing what you think. It feels like flying blind.</p>

<p>That bugged me enough to build a little tool: <strong>Gamepad Cascade Debugger</strong>. Instead of staring at console output, you get a live, interactive view of the controller. Press something and it reacts on the screen. And with <a href="https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/">CSS Cascade Layers</a>, the styles stay organized, so it’s cleaner to debug.</p>

<p>In this post, I’ll show you why debugging controllers is such a pain, how CSS helps clean it up, and how you can build a reusable visual debugger for your own projects.</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="F8fwVDNM0OI"
      
			videotitle="Live Demo of the Gamepad Debugger showing recording, exporting, and ghost replay in action."
		></lite-youtube>
	</div>
	
		<figcaption>Live Demo of the Gamepad Debugger showing recording, exporting, and ghost replay in action.</figcaption>
	
</figure>

<p>By the end, you’ll know how to:</p>

<ul>
<li>Spot the tricky parts of debugging controller input.</li>
<li>Use Cascade Layers to tame messy CSS.</li>
<li>Build a live Gamepad debugger.</li>
<li>Add extra functionalities like recording, replaying, and taking snapshots.</li>
</ul>

<p>Alright, let’s dive in.</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="why-debugging-gamepad-input-is-hard">Why Debugging Gamepad Input Is Hard</h2>

<p>Just the thought of building a game or web app where a player uses a controller instead of a mouse could make you nervous. You need to be able to respond to actions like:</p>

<ul>
<li>Did they press <code>A</code> or <code>B</code>?</li>
<li>Is the joystick tilted halfway or fully?</li>
<li>How hard is the trigger pulled?</li>
</ul>

<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API">Gamepad API</a> exposes and displays all of the information you need, but only as arrays of numbers. Each button has a value (e.g., <code>0</code> for not pressed, <code>1</code> for fully pressed, and decimals for pressure-sensitive triggers), and each joystick reports its position on the X and Y axes.</p>

<p>Here’s what it looks like in raw form:</p>

<pre><code class="language-css">// Example: Reading the first connected gamepad
const gamepad = navigator.getGamepads()[0];
 
console.log(gamepad.buttons.map(b =&gt; b.value));
// [0, 0, 1, 0, 0, 0.5, 0, ...]
 
console.log(gamepad.axes);
// [-0.24, 0.98, -0.02, 0.00]
 </code></pre> 

<p>Is it useful? Technically, yes. Easy to debug? Not at all.</p>

<h3 id="problem-1-invisible-state">Problem 1: Invisible State</h3>

<p>When you press a physical button, you feel the click, right? But in your code, nothing moves on screen unless you manually wire up a display. Unlike keyboard events (which show in browser dev tools) or mouse clicks (which fire visible events), gamepad input has no built-in visual feedback.</p>

<p>To illustrate the difference, here’s how other input methods give you immediate feedback:</p>

<div class="break-out">
<pre><code class="language-css">// Keyboard events are visible and easy to track
document.addEventListener('keydown', (e) =&gt; {
  console.log('Key pressed:', e.key);
  // Outputs: "Key pressed: a"
  // You can see this in DevTools, and many tools show keyboard input
});

// Mouse clicks provide clear event data
document.addEventListener('click', (e) =&gt; {
  console.log('Clicked at:', e.clientX, e.clientY);
  // Outputs: "Clicked at: 245, 389"
  // Visual feedback is immediate
});

// But gamepad input? Silent and invisible.
const gamepad = navigator.getGamepads()[0];
if (gamepad) {
  console.log(gamepad.buttons[0]); 
  // Outputs: GamepadButton {pressed: false, touched: false, value: 0}
  // No events, no DevTools panel, just polling
}
</code></pre>
</div>

<p>The gamepad doesn’t fire events when buttons are pressed. You have to constantly poll it using <code>requestAnimationFrame</code>, checking values manually. There’s no built-in visualization, no dev tools integration, nothing.</p>

<p>This forces you to keep going back and forth between your console and your controller just to keep logging values, interpreting numbers, and mentally mapping them back to physical actions.</p>

<h3 id="problem-2-too-many-inputs">Problem 2: Too Many Inputs</h3>

<p>A modern controller can have up to 15+ buttons and 4+ axes. That’s over a dozen values updating at once.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg">
    
    <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-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg"
			
			sizes="100vw"
			alt="Xbox vs. PlayStation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Both Xbox and PlayStation controllers pack 15+ buttons each, and they’re laid out differently. Debugging across platforms means handling all that variety. (<a href='https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/1-xbox-playstation.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Even if you are able to log them all, you’ll quickly end up with unreadable console spam. For example:</p>

<pre><code class="language-javascript">[0,0,1,0,0,0.5,0,...]
[0,0,0,0,1,0,0,...]
[0,0,1,0,0,0,0,...]
</code></pre>

<p>Can you tell what button was pressed? Maybe, but only after straining your eyes and missing a few inputs. So, no, debugging doesn’t come easily when it comes to reading inputs.</p>

<h3 id="problem-3-lack-of-structure">Problem 3: Lack Of Structure</h3>

<p>Even if you throw together a quick visualizer, styles can quickly get messy. Default, active, and debug states can overlap, and without a clear structure, your CSS becomes brittle and hard to extend.</p>

<p><a href="https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/">CSS Cascade Layers</a> can help. They group styles into “layers” that are ordered by priority, so you stop fighting specificity and guessing, <em>“Why isn’t my debug style showing?”</em> Instead, you maintain separate concerns:</p>

<ul>
<li><strong>Base</strong>: The controller’s standard, initial appearance.</li>
<li><strong>Active</strong>: Highlights for pressed buttons and moved sticks.</li>
<li><strong>Debug</strong>: Overlays for developers (e.g., numeric readouts, guides, and so on).</li>
</ul>

<p>If we were to define layers in CSS according to this, we’d have:</p>

<pre><code class="language-css">/&#42; lowest to highest priority &#42;/
@layer base, active, debug;

@layer base {
  /&#42; ... &#42;/
}

@layer active {
  /&#42; ... &#42;/
}

@layer debug {
  /&#42; ... &#42;/
}
</code></pre>

<p>Because each layer stacks predictably, you always know which rules win. That predictability makes debugging not just easier, but actually manageable.</p>

<p>We’ve covered the problem (invisible, messy input) and the approach (a visual debugger built with Cascade Layers). Now we’ll walk through the step-by-step process to build the debugger.</p>

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

<h2 id="the-debugger-concept">The Debugger Concept</h2>

<p>The easiest way to make hidden input visible is to just draw it on the screen. That’s what this debugger does. Buttons, triggers, and joysticks all get a visual.</p>

<ul>
<li><strong>Press <code>A</code></strong>: A circle lights up.</li>
<li><strong>Nudge the stick</strong>: The circle slides around.</li>
<li><strong>Pull a trigger halfway</strong>: A bar fills halfway.</li>
</ul>

<p>Now you’re not staring at 0s and 1s, but actually watching the controller react live.</p>

<p>Of course, once you start piling on states like default, pressed, debug info, maybe even a recording mode, the CSS starts getting larger and more complex. That’s where cascade layers come in handy. Here’s a stripped-down example:</p>

<pre><code class="language-css">@layer base {
  .button {
    background: &#35;222;
    border-radius: 50%;
    width: 40px;
    height: 40px;
  }
}
 
@layer active {
  .button.pressed {
    background: &#35;0f0; /&#42; bright green &#42;/
  }
}
 
@layer debug {
  .button::after {
    content: attr(data-value);
    font-size: 12px;
    color: &#35;fff;
  }
}
</code></pre>

<p>The layer order matters: <code>base</code> → <code>active</code> → <code>debug</code>.</p>

<ul>
<li><code>base</code> draws the controller.</li>
<li><code>active</code> handles pressed states.</li>
<li><code>debug</code> throws on overlays.</li>
</ul>

<p>Breaking it up like this means you’re not fighting weird specificity wars. Each layer has its place, and you always know what wins.</p>

<h2 id="building-it-out">Building It Out</h2>

<p>Let’s get something on screen first. It doesn’t need to look good &mdash; just needs to exist so we have something to work with.</p>

<div class="break-out">
<pre><code class="language-html">&lt;h1&gt;Gamepad Cascade Debugger&lt;/h1&gt;

&lt;!-- Main controller container --&gt;
&lt;div id="controller"&gt;
  &lt;!-- Action buttons --&gt;
  &lt;div id="btn-a" class="button"&gt;A&lt;/div&gt;
  &lt;div id="btn-b" class="button"&gt;B&lt;/div&gt;
  &lt;div id="btn-x" class="button"&gt;X&lt;/div&gt;
  
  &lt;!-- Pause/menu button (represented as two bars) --&gt;
  &lt;div&gt;
    &lt;div id="pause1" class="pause"&gt;&lt;/div&gt;
    &lt;div id="pause2" class="pause"&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;!-- Toggle button to start/stop the debugger --&gt;
&lt;button id="toggle"&gt;Toggle Debug&lt;/button&gt;

&lt;!-- Status display for showing which buttons are pressed --&gt;
&lt;div id="status"&gt;Debugger inactive&lt;/div&gt;

&lt;script src="script.js"&gt;&lt;/script&gt;
</code></pre>
</div>

<p>That’s literally just boxes. Not exciting yet, but it gives us handles to grab later with CSS and JavaScript.</p>

<p>Okay, I’m using cascade layers here because it keeps stuff organized once you add more states. Here’s a rough pass:</p>

<div class="break-out">
<pre><code class="language-css">/&#42; ===================================
   CASCADE LAYERS SETUP
   Order matters: base → active → debug
   =================================== &#42;/

/&#42; Define layer order upfront &#42;/
@layer base, active, debug;

/&#42; Layer 1: Base styles - default appearance &#42;/
@layer base {
  .button {
    background: &#35;333;
    border-radius: 50%;
    width: 70px;
    height: 70px;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  
  .pause {
    width: 20px;
    height: 70px;
    background: &#35;333;
    display: inline-block;
  }
}

/&#42; Layer 2: Active states - handles pressed buttons &#42;/
@layer active {
  .button.active {
    background: &#35;0f0; /&#42; Bright green when pressed &#42;/
    transform: scale(1.1); /&#42; Slightly enlarges the button &#42;/
  }
  
  .pause.active {
    background: &#35;0f0;
    transform: scaleY(1.1); /&#42; Stretches vertically when pressed &#42;/
  }
}

/&#42; Layer 3: Debug overlays - developer info &#42;/
@layer debug {
  .button::after {
    content: attr(data-value); /&#42; Shows the numeric value &#42;/
    font-size: 12px;
    color: &#35;fff;
  }
}
</code></pre>
</div>

<p>The beauty of this approach is that each layer has a clear purpose. The <code>base</code> layer can never override <code>active,</code> and <code>active</code> can never override <code>debug</code>, regardless of specificity. This eliminates the CSS specificity wars that usually plague debugging tools.</p>

<p>Now it looks like some clusters are sitting on a dark background. Honestly, not too bad.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="402"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png"
			
			sizes="100vw"
			alt="The debugger’s initial state showing the button layout (A, B, X, and pause bars)"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/2-debugger-initial-state.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="adding-the-javascript">Adding the JavaScript</h3>

<p>JavaScript time. This is where the controller actually does something. We’ll build this step by step.</p>

<h4 id="step-1-set-up-state-management">Step 1: Set Up State Management</h4>

<p>First, we need variables to track the debugger’s state:</p>

<pre><code class="language-javascript">// ===================================
// STATE MANAGEMENT
// ===================================

let running = false; // Tracks whether the debugger is active
let rafId; // Stores the requestAnimationFrame ID for cancellation
</code></pre>

<p>These variables control the animation loop that continuously reads gamepad input.</p>

<h4 id="step-2-grab-dom-references">Step 2: Grab DOM References</h4>

<p>Next, we get references to all the HTML elements we’ll be updating:</p>

<pre><code class="language-javascript">// ===================================
// DOM ELEMENT REFERENCES
// ===================================

const btnA = document.getElementById("btn-a");
const btnB = document.getElementById("btn-b");
const btnX = document.getElementById("btn-x");
const pause1 = document.getElementById("pause1");
const pause2 = document.getElementById("pause2");
const status = document.getElementById("status");
</code></pre>

<p>Storing these references up front is more efficient than querying the DOM repeatedly.</p>

<h4 id="step-3-add-keyboard-fallback">Step 3: Add Keyboard Fallback</h4>

<p>For testing without a physical controller, we’ll map keyboard keys to buttons:</p>

<pre><code class="language-javascript">// ===================================
// KEYBOARD FALLBACK (for testing without a controller)
// ===================================

const keyMap = {
  "a": btnA,
  "b": btnB,
  "x": btnX,
  "p": [pause1, pause2] // 'p' key controls both pause bars
};
</code></pre>

<p>This lets us test the UI by pressing keys on a keyboard.</p>

<h4 id="step-4-create-the-main-update-loop">Step 4: Create The Main Update Loop</h4>

<p>Here’s where the magic happens. This function runs continuously and reads gamepad state:</p>

<pre><code class="language-javascript">// ===================================
// MAIN GAMEPAD UPDATE LOOP
// ===================================

function updateGamepad() {
  // Get all connected gamepads
  const gamepads = navigator.getGamepads();
  if (!gamepads) return;

  // Use the first connected gamepad
  const gp = gamepads[0];

  if (gp) {
    // Update button states by toggling the "active" class
    btnA.classList.toggle("active", gp.buttons[0].pressed);
    btnB.classList.toggle("active", gp.buttons[1].pressed);
    btnX.classList.toggle("active", gp.buttons[2].pressed);

    // Handle pause button (button index 9 on most controllers)
    const pausePressed = gp.buttons[9].pressed;
    pause1.classList.toggle("active", pausePressed);
    pause2.classList.toggle("active", pausePressed);

    // Build a list of currently pressed buttons for status display
    let pressed = [];
    gp.buttons.forEach((btn, i) =&gt; {
      if (btn.pressed) pressed.push("Button " + i);
    });

    // Update status text if any buttons are pressed
    if (pressed.length &gt; 0) {
      status.textContent = "Pressed: " + pressed.join(", ");
    }
  }

  // Continue the loop if debugger is running
  if (running) {
    rafId = requestAnimationFrame(updateGamepad);
  }
}
</code></pre>

<p>The <code>classList.toggle()</code> method adds or removes the <code>active</code> class based on whether the button is pressed, which triggers our CSS layer styles.</p>

<h4 id="step-5-handle-keyboard-events">Step 5: Handle Keyboard Events</h4>

<p>These event listeners make the keyboard fallback work:</p>

<pre><code class="language-javascript">// ===================================
// KEYBOARD EVENT HANDLERS
// ===================================

document.addEventListener("keydown", (e) =&gt; {
  if (keyMap[e.key]) {
    // Handle single or multiple elements
    if (Array.isArray(keyMap[e.key])) {
      keyMap[e.key].forEach(el =&gt; el.classList.add("active"));
    } else {
      keyMap[e.key].classList.add("active");
    }
    status.textContent = "Key pressed: " + e.key.toUpperCase();
  }
});

document.addEventListener("keyup", (e) =&gt; {
  if (keyMap[e.key]) {
    // Remove active state when key is released
    if (Array.isArray(keyMap[e.key])) {
      keyMap[e.key].forEach(el =&gt; el.classList.remove("active"));
    } else {
      keyMap[e.key].classList.remove("active");
    }
    status.textContent = "Key released: " + e.key.toUpperCase();
  }
});
</code></pre>

<h4 id="step-6-add-start-stop-control">Step 6: Add Start/Stop Control</h4>

<p>Finally, we need a way to toggle the debugger on and off:</p>

<pre><code class="language-javascript">// ===================================
// TOGGLE DEBUGGER ON/OFF
// ===================================

document.getElementById("toggle").addEventListener("click", () =&gt; {
  running = !running; // Flip the running state

  if (running) {
    status.textContent = "Debugger running...";
    updateGamepad(); // Start the update loop
  } else {
    status.textContent = "Debugger inactive";
    cancelAnimationFrame(rafId); // Stop the loop
  }
});
</code></pre>

<p>So yeah, press a button and it glows. Push the stick and it moves. That’s it.</p>

<p>One more thing: raw values. Sometimes you just want to see numbers, not lights.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="387"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg"
			
			sizes="100vw"
			alt="The Gamepad Cascade Debugger in its idle state with no inputs detected (Pressed buttons: 0)."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Gamepad Cascade Debugger in its idle state with no inputs detected (Pressed buttons: 0). (<a href='https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/3-gamepad-cascade-debugger.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>At this stage, you should see:</p>

<ul>
<li>A simple on-screen controller,</li>
<li>Buttons that react as you interact with them, and</li>
<li>An optional debug readout showing pressed button indices.</li>
</ul>

<p>To make this less abstract, here’s a quick demo of the on-screen controller reacting in real time:</p>


<figure class="video-embed-container break-out">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="gHUKp4Zu-wM"
      
			videotitle="Live demo of the on-screen controller lighting up as buttons are pressed and released."
		></lite-youtube>
	</div>
	
		<figcaption>Live demo of the on-screen controller lighting up as buttons are pressed and released.</figcaption>
	
</figure>

<p>That’s the whole foundation. From here, we can start layering in extra stuff, like record/replay and snapshots.</p>

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

<h2 id="enhancements-from-toy-to-tool">Enhancements: From Toy To Tool</h2>

<p>A static visualizer is helpful, but we as developers often need more than a snapshot of the controller’s state. We want history, analysis, and replay. Let’s add those layers on top of our debugger.</p>

<h3 id="1-recording-stopping-input-logs">1. Recording &amp; Stopping Input Logs</h3>

<p>We can add two buttons:</p>

<div class="break-out">
<pre><code class="language-html">&lt;div class="controls"&gt;
  &lt;button id="start-record" class="btn"&gt;Start Recording&lt;/button&gt;
  &lt;button id="stop-record" class="btn" disabled&gt;Stop Recording&lt;/button&gt;
&lt;/div&gt;
</code></pre>
</div>

<h4 id="step-1-set-up-recording-state">Step 1: Set Up Recording State</h4>

<p>First, let’s set up the variables we need to track recordings:</p>

<pre><code class="language-javascript">// ===================================
// RECORDING STATE
// ===================================

let recording = false; // Tracks if we're currently recording
let frames = []; // Array to store captured input frames

// Get button references
const startBtn = document.getElementById("start-record");
const stopBtn = document.getElementById("stop-record");
</code></pre>

<p>The <code>frames</code> array will store snapshots of the gamepad state at each frame, creating a complete timeline of input.</p>

<h4 id="step-2-handle-start-recording">Step 2: Handle Start Recording</h4>

<p>When the user clicks “Start Recording,” we initialize a new recording session:</p>

<pre><code class="language-javascript">// ===================================
// START RECORDING
// ===================================

startBtn.addEventListener("click", () =&gt; {
  frames = []; // Clear any previous recording
  recording = true;

  // Update UI: disable start, enable stop
  stopBtn.disabled = false;
  startBtn.disabled = true;

  console.log("Recording started...");
});
</code></pre>

<h4 id="step-3-handle-stop-recording">Step 3: Handle Stop Recording</h4>

<p>To stop recording, we flip the state back and re-enable the Start button:</p>

<pre><code class="language-javascript">// ===================================
// STOP RECORDING
// ===================================

stopBtn.addEventListener("click", () =&gt; {
  recording = false;

  // Update UI: enable start, disable stop
  stopBtn.disabled = true;
  startBtn.disabled = false;

  console.log("Recording stopped. Frames captured:", frames.length);
});
</code></pre>

<h4 id="step-4-capture-frames-during-gameplay">Step 4: Capture Frames During Gameplay</h4>

<p>Finally, we need to actually capture frames during the update loop. Add this inside the <code>updateGamepad()</code> function:</p>

<pre><code class="language-javascript">// ===================================
// CAPTURE FRAMES (add this inside updateGamepad loop)
// ===================================

if (recording && gp) {
  // Store a snapshot of the current gamepad state
  frames.push({
    t: performance.now(), // Timestamp for accurate replay
    buttons: gp.buttons.map(b =&gt; ({ 
      pressed: b.pressed, 
      value: b.value 
    })),
    axes: [...gp.axes] // Copy the axes array
  });
}
</code></pre>

<p>Each frame captures the exact state of every button and joystick at that moment in time.</p>

<p>Once wired up, the interface displays a simple recording panel. You get a Start button to begin logging input, while the recording state, frame count, and duration remain at zero until recording begins. The following figure shows the debugger in its initial idle state.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="533"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg"
			
			sizes="100vw"
			alt="Recording panel in its idle state, with only the start button active"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Recording panel in its idle state, with only the start button active. (<a href='https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/4-recording-panel.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Now, pressing <strong>Start Recording</strong> logs everything until you hit <strong>Stop Recording</strong>.</p>

<h3 id="2-exporting-data-to-csv-json">2. Exporting Data to CSV/JSON</h3>

<p>Once we have a log, we’ll want to save it.</p>

<div class="break-out">
<pre><code class="language-html">&lt;div class="controls"&gt;
  &lt;button id="export-json" class="btn"&gt;Export JSON&lt;/button&gt;
  &lt;button id="export-csv" class="btn"&gt;Export CSV&lt;/button&gt;
&lt;/div&gt;
</code></pre>
</div>

<h4 id="step-1-create-the-download-helper">Step 1: Create The Download Helper</h4>

<p>First, we need a helper function that handles file downloads in the browser:</p>

<pre><code class="language-javascript">// ===================================
// FILE DOWNLOAD HELPER
// ===================================

function downloadFile(filename, content, type = "text/plain") {
  // Create a blob from the content
  const blob = new Blob([content], { type });
  const url = URL.createObjectURL(blob);

  // Create a temporary download link and click it
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();

  // Clean up the object URL after download
  setTimeout(() =&gt; URL.revokeObjectURL(url), 100);
}
</code></pre>

<p>This function works by creating a Blob (binary large object) from your data, generating a temporary URL for it, and programmatically clicking a download link. The cleanup ensures we don’t leak memory.</p>

<h4 id="step-2-handle-json-export">Step 2: Handle JSON Export</h4>

<p>JSON is perfect for preserving the complete data structure:</p>

<div class="break-out">
<pre><code class="language-javascript">// ===================================
// EXPORT AS JSON
// ===================================

document.getElementById("export-json").addEventListener("click", () =&gt; {
  // Check if there's anything to export
  if (!frames.length) {
    console.warn("No recording available to export.");
    return;
  }

  // Create a payload with metadata and frames
  const payload = {
    createdAt: new Date().toISOString(),
    frames
  };

  // Download as formatted JSON
  downloadFile(
    "gamepad-log.json", 
    JSON.stringify(payload, null, 2), 
    "application/json"
  );
});
</code></pre>
</div>

<p>The JSON format keeps everything structured and easily parseable, making it ideal for loading back into dev tools or sharing with teammates.</p>

<h4 id="step-3-handle-csv-export">Step 3: Handle CSV Export</h4>

<p>For CSV exports, we need to flatten the hierarchical data into rows and columns:</p>

<div class="break-out">
<pre><code class="language-javascript">// ===================================
// EXPORT AS CSV
// ===================================

document.getElementById("export-csv").addEventListener("click", () =&gt; {
  // Check if there's anything to export
  if (!frames.length) {
    console.warn("No recording available to export.");
    return;
  }

  // Build CSV header row (columns for timestamp, all buttons, all axes)
  const headerButtons = frames[0].buttons.map((&#95;, i) =&gt; `btn${i}`);
  const headerAxes = frames[0].axes.map((&#95;, i) =&gt; `axis${i}`);
  const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";

  // Build CSV data rows
  const rows = frames.map(f =&gt; {
    const btnVals = f.buttons.map(b =&gt; b.value);
    return [f.t, ...btnVals, ...f.axes].join(",");
  }).join("\n");

  // Download as CSV
  downloadFile("gamepad-log.csv", header + rows, "text/csv");
});
</code></pre>
</div>

<p>CSV is brilliant for data analysis because it opens directly in Excel or Google Sheets, letting you create charts, filter data, or spot patterns visually.</p>

<p>Now that the export buttons are in, you’ll see two new options on the panel: <strong>Export JSON</strong> and <strong>Export CSV</strong>. JSON is nice if you want to throw the raw log back into your dev tools or poke around the structure. CSV, on the other hand, opens straight into Excel or Google Sheets so you can chart, filter, or compare inputs. The following figure shows what the panel looks like with those extra controls.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="533"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg"
			
			sizes="100vw"
			alt="Export panel with JSON and CSV buttons for saving logs"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Export panel with JSON and CSV buttons for saving logs. (<a href='https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/5-export-panel.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="3-snapshot-system">3. Snapshot System</h3>

<p>Sometimes you don’t need a full recording, just a quick “screenshot” of input states. That’s where a <strong>Take Snapshot</strong> button helps.</p>

<pre><code class="language-html">&lt;div class="controls"&gt;
  &lt;button id="snapshot" class="btn"&gt;Take Snapshot&lt;/button&gt;
&lt;/div&gt;
</code></pre>

<p>And the JavaScript:</p>

<div class="break-out">
<pre><code class="language-javascript">// ===================================
// TAKE SNAPSHOT
// ===================================

document.getElementById("snapshot").addEventListener("click", () =&gt; {
  // Get all connected gamepads
  const pads = navigator.getGamepads();
  const activePads = [];
  
  // Loop through and capture the state of each connected gamepad
  for (const gp of pads) {
    if (!gp) continue; // Skip empty slots
    
    activePads.push({
      id: gp.id, // Controller name/model
      timestamp: performance.now(),
      buttons: gp.buttons.map(b =&gt; ({ 
        pressed: b.pressed, 
        value: b.value 
      })),
      axes: [...gp.axes]
    });
  }
  
  // Check if any gamepads were found
  if (!activePads.length) {
    console.warn("No gamepads connected for snapshot.");
    alert("No controller detected!");
    return;
  }
  
  // Log and notify user
  console.log("Snapshot:", activePads);
  alert(`Snapshot taken! Captured ${activePads.length} controller(s).`);
});
</code></pre>
</div>

<p>Snapshots freeze the exact state of your controller at one moment in time.</p>

<h3 id="4-ghost-input-replay">4. Ghost Input Replay</h3>

<p>Now for the fun one: ghost input replay. This takes a log and plays it back visually as if a phantom player was using the controller.</p>

<div class="break-out">
<pre><code class="language-html">&lt;div class="controls"&gt;
  &lt;button id="replay" class="btn"&gt;Replay Last Recording&lt;/button&gt;
&lt;/div&gt;
</code></pre>
</div>

<p>JavaScript for replay:</p>

<pre><code class="language-javascript">// ===================================
// GHOST REPLAY
// ===================================

document.getElementById("replay").addEventListener("click", () =&gt; {
  // Ensure we have a recording to replay
  if (!frames.length) {
    alert("No recording to replay!");
    return;
  }
  
  console.log("Starting ghost replay...");
  
  // Track timing for synced playback
  let startTime = performance.now();
  let frameIndex = 0;
  
  // Replay animation loop
  function step() {
    const now = performance.now();
    const elapsed = now - startTime;
    
    // Process all frames that should have occurred by now
    while (frameIndex &lt; frames.length && frames[frameIndex].t &lt;= elapsed) {
      const frame = frames[frameIndex];
      
      // Update UI with the recorded button states
      btnA.classList.toggle("active", frame.buttons[0].pressed);
      btnB.classList.toggle("active", frame.buttons[1].pressed);
      btnX.classList.toggle("active", frame.buttons[2].pressed);
      
      // Update status display
      let pressed = [];
      frame.buttons.forEach((btn, i) =&gt; {
        if (btn.pressed) pressed.push("Button " + i);
      });
      if (pressed.length &gt; 0) {
        status.textContent = "Ghost: " + pressed.join(", ");
      }
      
      frameIndex++;
    }
    
    // Continue loop if there are more frames
    if (frameIndex &lt; frames.length) {
      requestAnimationFrame(step);
    } else {
      console.log("Replay finished.");
      status.textContent = "Replay complete";
    }
  }
  
  // Start the replay
  step();
});
</code></pre>

<p>To make debugging a bit more hands-on, I added a ghost replay. Once you’ve recorded a session, you can hit replay and watch the UI act it out, almost like a phantom player is running the pad. A new <strong>Replay Ghost</strong> button shows up in the panel for this.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="533"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg"
			
			sizes="100vw"
			alt="Ghost replay mode with a session playing back on the debugger."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Ghost replay mode with a session playing back on the debugger. (<a href='https://files.smashing.media/articles/css-gamepad-api-visual-debugging-css-layers/6-ghost-replay-mode.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Hit <strong>Record</strong>, mess around with the controller a bit, stop, then replay. The UI just echoes everything you did, like a ghost following your inputs.</p>

<p>Why bother with these extras?</p>

<ul>
<li><strong>Recording/export</strong> makes it easy for testers to show exactly what happened.</li>
<li><strong>Snapshots</strong> freeze a moment in time, super useful when you’re chasing odd bugs.</li>
<li><strong>Ghost replay</strong> is great for tutorials, accessibility checks, or just comparing control setups side by side.</li>
</ul>

<p>At this point, it’s not just a neat demo anymore, but something you could actually put to work.</p>

<h2 id="real-world-use-cases">Real-World Use Cases</h2>

<p>Now we’ve got this debugger that can do a lot. It shows live input, records logs, exports them, and even replays stuff. But the real question is: who actually cares? Who’s this useful for?</p>

<h3 id="game-developers">Game Developers</h3>

<p>Controllers are part of the job, but debugging them? Usually a pain. Imagine you’re testing a fighting game combo, like <code>↓ →</code> + <code>punch</code>. Instead of praying, you pressed it the same way twice, you record it once, and replay it. Done. Or you swap <code>JSON</code> logs with a teammate to check if your multiplayer code reacts the same on their machine. That’s huge.</p>

<h3 id="accessibility-practitioners">Accessibility Practitioners</h3>

<p>This one’s close to my heart. Not everyone plays with a “standard” controller. Adaptive controllers throw out weird signals sometimes. With this tool, you can see exactly what’s happening. Teachers, researchers, whoever. They can grab logs, compare them, or replay inputs side-by-side. Suddenly, invisible stuff becomes obvious.</p>

<h3 id="quality-assurance-testing">Quality Assurance Testing</h3>

<p>Testers usually write notes like “I mashed buttons here and it broke.” Not very helpful. Now? They can capture the exact presses, export the log, and send it off. No guessing.</p>

<h3 id="educators">Educators</h3>

<p>If you’re making tutorials or YouTube vids, ghost replay is gold. You can literally say, “Here’s what I did with the controller,” while the UI shows it happening. Makes explanations way clearer.</p>

<h3 id="beyond-games">Beyond Games</h3>

<p>And yeah, this isn’t just about games. People have used controllers for robots, art projects, and accessibility interfaces. Same issue every time: what is the browser actually seeing? With this, you don’t have to guess.</p>

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

<p>Debugging a controller input has always felt like flying blind. Unlike the DOM or CSS, there’s no built-in inspector for gamepads; it’s just raw numbers in the console, easily lost in the noise.</p>

<p>With a few hundred lines of HTML, CSS, and JavaScript, we built something different:</p>

<ul>
<li><strong>A visual debugger</strong> that makes invisible inputs visible.</li>
<li><strong>A layered CSS system</strong> that keeps the UI clean and debuggable.</li>
<li><strong>A set of enhancements</strong> (recording, exporting, snapshots, ghost replay) that elevate it from demo to developer tool.</li>
</ul>

<p>This project shows how far you can go by mixing the Web Platform’s power with a little creativity in CSS Cascade Layers.</p>

<p>The tool I just explained in its entirety is open-source. You can <a href="https://github.com/BboyGT/gamepad-cascade-debugger/tree/main/gamepad-cascade-debugger-final">clone the GitHub repo</a> and try it for yourself.</p>

<p>But more importantly, you can make it your own. Add your own layers. Build your own replay logic. Integrate it with your game prototype. Or even use it in ways I haven’t imagined. For teaching, accessibility, or data analysis.</p>

<p>At the end of the day, this isn’t just about debugging gamepads. It’s about <strong>shining a light on hidden inputs</strong>, and giving developers the confidence to work with hardware that the web still doesn’t fully embrace.</p>

<p>So, plug in your controller, open up your editor, and start experimenting. You might be surprised at what your browser and your CSS can truly accomplish.</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>Bryan Rasmussen</author><title>Older Tech In The Browser Stack</title><link>https://www.smashingmagazine.com/2025/11/older-tech-browser-stack/</link><pubDate>Thu, 13 Nov 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/11/older-tech-browser-stack/</guid><description>There are many existing web features and technologies in the wild that you may never touch directly in your day-to-day work. Perhaps you’re fairly new to web development and are simply unaware of them because you’re steeped in the abstraction of a specific framework that doesn’t require you to know it deeply, or even at all. Bryan Rasmussen looks specifically at XPath and demonstrates how it can be used alongside CSS to query elements.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/11/older-tech-browser-stack/" />
              <title>Older Tech In The Browser Stack</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Older Tech In The Browser Stack</h1>
                  
                    
                    <address>Bryan Rasmussen</address>
                  
                  <time datetime="2025-11-13T08:00:00&#43;00:00" class="op-published">2025-11-13T08:00:00+00:00</time>
                  <time datetime="2025-11-13T08:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>I’ve been in front-end development long enough to see a trend over the years: younger developers working with a new paradigm of programming without understanding the historical context of it.</p>

<p>It is, of course, perfectly understandable to <em>not</em> know something. The web is a very big place with a diverse set of skills and specialties, and we don’t always know what we don’t know. Learning in this field is an ongoing journey rather than something that happens once and ends.</p>

<p>Case in point: Someone on my team asked if it was possible to tell if users navigate away from a particular tab in the UI. I pointed out JavaScript’s <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event"><code>beforeunload</code> event</a>. But those who have tackled this before know this is possible because they have been hit with alerts about unsaved data on other sites, for which <code>beforeunload</code> is a typical use case. I also pointed out the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event"><code>pageHide</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event"><code>visibilityChange</code></a> events to my colleague for good measure.</p>

<p>How did I know about that? Because it came up in another project, not because I studied up on it when initially learning JavaScript.</p>

<p>The fact is that modern front-end frameworks are standing on the shoulders of the technology giants that preceded them. They abstract development practices, often for a better developer experience that reduces, or even eliminates, the need to know or touch what have traditionally been essential front-end concepts everyone probably ought to know.</p>

<p>Consider the <a href="https://css-tricks.com/an-introduction-and-guide-to-the-css-object-model-cssom/">CSS Object Model (CSSOM)</a>. You might expect that anyone working in CSS and JavaScript has a bunch of hands-on CSSOM experience, but that’s not always going to be the case.</p>

<p>There was a React project for an e-commerce site I worked on where we needed to load a stylesheet for the currently selected payment provider. The problem was that the stylesheet was loading on every page when it was only really needed on a specific page. The developer tasked with making this happen hadn’t ever loaded a stylesheet dynamically. Again, this is totally understandable when React abstracts away the traditional approach you might have reached for.</p>

<p>The CSSOM is likely not something you need in your everyday work. But it is likely you will need to interact with it at some point, even in a one-off instance.</p>

<p>These experiences inspired me to write this article. There are many existing web features and technologies in the wild that you may never touch directly in your day-to-day work. Perhaps you’re fairly new to web development and are simply unaware of them because you’re steeped in the abstraction of a specific framework that doesn’t require you to know it deeply, or even at all.</p>

<p>I’m speaking specifically about <a href="https://developer.mozilla.org/en-US/docs/Web/XML/Guides/XML_introduction">XML</a>, which many of us know is an ancient language not totally dissimilar from HTML.</p>

<p>I’m bringing this up because of recent WHATWG discussions <a href="https://github.com/whatwg/html/issues/11523">suggesting</a> that a significant chunk of the XML stack known as <a href="https://developer.mozilla.org/en-US/docs/Web/XML/XSLT">XSLT</a> programming should be removed from browsers. This is exactly the sort of older, existing technology we’ve had for years that could be used for something as practical as the CSSOM situation my team was in.</p>

<p>Have you worked with XSLT before? Let’s see if we lean heavily into this older technology and leverage its features outside the context of XML to tackle real-world problems today.</p>

<h2 id="xpath-the-central-api">XPath: The Central API</h2>

<p>The most important XML technology that is perhaps the most useful outside of a straight XML perspective is <strong>XPath</strong>, a query language that allows you to find any node or attribute in a markup tree with one root element. I have a personal affection for XSLT, but that also relies on XPath, and personal affection must be put aside in ranking importance.</p>

<p>The argument for removing XSLT does not make any mention of XPath, so I suppose it is still allowed. That’s good because XPath is the central and most important API in this suite of technologies, especially when trying to find something to use outside normal XML usage. It is important because, while CSS selectors can be used to find most of the elements in your page, they cannot find them all. Furthermore, CSS selectors cannot be used to find an element based on its current position in the DOM.</p>

<p>XPath can.</p>

<p>Now, some of you reading this might know XPath, and some might not. XPath is a pretty big area of technology, and I can’t really teach all the basics and also show you cool things to do with it in a single article like this. I actually tried writing that article, but the average Smashing Magazine publication doesn’t go over 5,000 words. I was already at more than 2,000 words while only halfway through the basics.</p>

<p>So, I’m going to start doing cool stuff with XPath and give you some links that you can use for the basics if you find this stuff interesting.</p>

<h2 id="combining-xpath-css">Combining XPath &amp; CSS</h2>

<p>XPath can do lots of things that CSS selectors can’t when querying elements. But CSS selectors can also do a few things that XPath can’t, namely, query elements by class name.</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>CSS</th>
            <th>XPath</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>.myClass</code></td>
            <td><code>/&#42;[contains(@class, "myClass")]</code></td>
        </tr>
    </tbody>
</table>

<p>In this example, CSS queries elements that contain a <code>.myClass</code> classname. Meanwhile, the XPath example queries elements that contain an attribute class with the string “<code>myClass</code>”. In other words, it selects elements with <code>myClass</code> in any attribute, including elements with the <code>.myClass</code> classname &mdash; as well as elements with “<code>myClass</code>” in the string, such as <code>.myClass2</code>. XPath is broader in that sense.</p>

<p>So, no. I’m not suggesting that we ought to toss out CSS and start selecting all elements via XPath. That’s not the point.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aThe%20point%20is%20that%20XPath%20can%20do%20things%20that%20CSS%20cannot%20and%20could%20still%20be%20very%20useful,%20even%20though%20it%20is%20an%20older%20technology%20in%20the%20browser%20stack%20and%20may%20not%20seem%20obvious%20at%20first%20glance.%0a&url=https://smashingmagazine.com%2f2025%2f11%2folder-tech-browser-stack%2f">
      
The point is that XPath can do things that CSS cannot and could still be very useful, even though it is an older technology in the browser stack and may not seem obvious at first glance.

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

<p>Let’s use the two technologies together not only because we can, but because we’ll learn something about XPath in the process, making it another tool in your stack &mdash; one you might not have known has been there all along!</p>

<p>The problem is that JavaScript’s <code>document.evaluate</code> method and the various query selector methods we use with the CSS APIs for JavaScript are incompatible.</p>

<p>I have made a compatible querying API to get us started, though admittedly, I have not put a lot of thought into it since it’s a departure from what we’re doing here. Here’s a fairly simple working example of a reusable query constructor:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="jEqEyEx"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [queryXPath [forked]](https://codepen.io/smashingmag/pen/jEqEyEx) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/jEqEyEx">queryXPath [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>I’ve added two methods on the document object: <code>queryCSSSelectors</code> (which is essentially <code>querySelectorAll</code>) and <code>queryXPaths</code>. Both of these return a <code>queryResults</code> object:</p>

<div class="break-out">
<pre><code class="language-javascript">{
  queryType: nodes | string | number | boolean,
  results: any[] // html elements, xml elements, strings, numbers, booleans,
  queryCSSSelectors: (query: string, amend: boolean) =&gt; queryResults,
  queryXpaths: (query: string, amend: boolean) =&gt; queryResults
}
</code></pre>
</div>
  

<p>The <code>queryCSSSelectors</code> and <code>queryXpaths</code> functions run the query you give them over the elements in the results array, as long as the results array is of type <code>nodes</code>, of course. Otherwise, it will return a <code>queryResult</code> with an empty array and a type of <code>nodes</code>. If the <code>amend</code> property is set to <code>true</code>, the functions will change their own <code>queryResults</code>.</p>

<p><strong>Under no circumstances should this be used in a production environment.</strong> I am doing it this way purely to demonstrate the various effects of using the two query APIs together.</p>

<h2 id="example-queries">Example Queries</h2>

<p>I want to show a few examples of different XPath queries that demonstrate some of the powerful things they can do and how they can be used in place of other approaches.</p>

<p>The first example is <code>//li/text()</code>. This queries all <code>li</code> elements and returns their text nodes. So, if we were to query the following HTML:</p>

<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;one&lt;/li&gt;
  &lt;li&gt;two&lt;/li&gt;
  &lt;li&gt;three&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
  

<p>…this is what is returned:</p>

<div class="break-out">
<pre><code class="language-json">{"queryType":"xpathEvaluate","results":["one","two","three"],"resultType":"string"}
</code></pre>
</div>
  

<p>In other words, we get the following array: <code>[&quot;one&quot;,&quot;two&quot;,&quot;three&quot;]</code>.</p>

<p>Normally, you would query for the <code>li</code> elements to get that, turn the result of that query into an array, map the array, and return the text node of each element. But we can do that more concisely with XPath:</p>

<pre><code class="language-javascript">document.queryXPaths("//li/text()").results.
</code></pre>

<p>Notice that the way to get a text node is to use <code>text()</code>, which looks like a function signature &mdash; and it is. It returns the text node of an element. In our example, there are three <code>li</code> elements in the markup, each containing text (<code>&quot;one&quot;</code>, <code>&quot;two&quot;</code>, and <code>&quot;three&quot;</code>).</p>

<p>Let’s look at one more example of a <code>text()</code> query. Assume this is our markup:</p>

<pre><code class="language-html">&lt;pa href="/login.html"&gt;Sign In&lt;/a&gt;
</code></pre>
  

<p>Let’s write a query that returns the <code>href</code> attribute value:</p>

<pre><code class="language-javascript">document.queryXPaths("//a[text() = 'Sign In']/@href").results.
</code></pre>

<p>This is an XPath query on the current document, just like the last example, but this time we return the <code>href</code> attribute of a link (<code>a</code> element) that contains the text “Sign In”. The actual returned result is <code>[&quot;/login.html&quot;]</code>.</p>

<h2 id="xpath-functions-overview">XPath Functions Overview</h2>

<p>There are a number of XPath functions, and you’re probably unfamiliar with them. There are several, I think, that are worth knowing about, including the following:</p>

<ul>
<li><strong><code>starts-with</code></strong><br />
If a text starts with a particular other text example, <code>starts-with(@href, 'http:')</code> returns <code>true</code> if an <code>href</code> attribute starts with <code>http:</code>.</li>
<li><strong><code>contains</code></strong><br />
If a text contains a particular other text example, <code>contains(text(), &quot;Smashing Magazine&quot;)</code> returns <code>true</code> if a text node contains the words “Smashing Magazine” in it anywhere.</li>
<li><strong><code>count</code></strong><br />
Returns a count of how many matches there are to a query. For example, <code>count(//*[starts-with(@href, 'http:'])</code> returns a count of how many links in the context node have elements with an <code>href</code> attribute that contains the text beginning with the <code>http:</code>.</li>
<li><strong><code>substring</code></strong><br />
Works like JavaScript <code>substring</code>, except you pass the string as an argument. For example, <code>substring(&quot;my text&quot;, 2, 4)</code> returns <code>&quot;y t&quot;</code>.</li>
<li><strong><code>substring-before</code></strong><br />
Returns the part of a string before another string. For example, <code>substing-before(&quot;my text&quot;, &quot; &quot;)</code> returns <code>&quot;my&quot;</code>. Similarly, <code>substring-before(&quot;hi&quot;,&quot;bye&quot;)</code> returns an empty string.</li>
<li><strong><code>substring-after</code></strong><br />
Returns the part of a string after another string. For example, <code>substing-after(&quot;my text&quot;, &quot; &quot;)</code> returns <code>&quot;text&quot;</code>. Similarly, <code>substring-after(&quot;hi&quot;,&quot;bye&quot;)</code>returns an empty string.</li>
<li><strong><code>normalize-space</code></strong><br />
Returns the argument string with whitespace normalized by stripping leading and trailing whitespace and replacing sequences of whitespace characters by a single space.</li>
<li><strong><code>not</code></strong><br />
Returns a boolean <code>true</code> if the argument is false, otherwise <code>false</code>.</li>
<li><strong><code>true</code></strong><br />
Returns boolean <code>true</code>.</li>
<li><strong><code>false</code></strong><br />
Returns boolean <code>false</code>.</li>
<li><strong><code>concat</code></strong><br />
The same thing as JavaScript <code>concat</code>, except you do not run it as a method on a string. Instead, you put in all the strings you want to concatenate.</li>
<li><strong><code>string-length</code></strong><br />
This is not the same as JavaScript <code>string-length</code>, but rather returns the length of the string it is given as an argument.</li>
<li><strong><code>translate</code></strong><br />
This takes a string and changes the second argument to the third argument. For example, <code>translate(&quot;abcdef&quot;, &quot;abc&quot;, &quot;XYZ&quot;)</code> outputs <code>XYZdef</code>.</li>
</ul>

<p>Aside from these particular XPath functions, there are a number of other functions that work just the same as their JavaScript counterparts &mdash; or counterparts in basically any programming language &mdash; that you would probably also find useful, such as <code>floor</code>, <code>ceiling</code>, <code>round</code>, <code>sum</code>, and so on.</p>

<p>The following demo illustrates each of these functions:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="emZmgzX"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [XPath Numerical functions [forked]](https://codepen.io/smashingmag/pen/emZmgzX) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/emZmgzX">XPath Numerical functions [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>Note that, like most of the string manipulation functions, many of the numerical ones take a <strong>single input</strong>. This is, of course, because they are supposed to be used for querying, as in the last XPath example:</p>

<pre><code class="language-html">//li[floor(text()) &gt; 250]/@val
</code></pre>

<p>If you use them, as most of the examples do, you will end up running it on the first node that matches the path.</p>

<p>There are also some type conversion functions you should probably avoid because JavaScript already has its own type conversion problems. But there can be times when you want to convert a string to a number in order to check it against some other number.</p>

<p>Functions that set the type of something are boolean, number, string, and node. These are the important XPath datatypes.</p>

<p>And as you might imagine, most of these functions can be used on datatypes that are not DOM nodes. For example, <code>substring-after</code> takes a string as we’ve already covered, but it could be the string from an <code>href</code> attribute. It can also just be a string:</p>

<div class="break-out">
<pre><code class="language-javascript">const testSubstringAfter = document.queryXPaths("substring-after('hello world',' ')");
</code></pre>
</div>

<p>Obviously, this example will give us back the results array as <code>[&quot;world&quot;]</code>. To show this in action, I have made a demo page using functions against things that are not DOM nodes:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="qEZERqd"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [queryXPath [forked]](https://codepen.io/smashingmag/pen/qEZERqd) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/qEZERqd">queryXPath [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>You should note the surprising aspect of the <code>translate</code> function, which is that if you have a character in the second argument (i.e., the list of characters you want translated) and no matching character to translate to, that character gets removed from the output.</p>

<p>Thus, this:</p>

<div class="break-out">
<pre><code class="language-javascript">translate('Hello, My Name is Inigo Montoya, you killed my father, prepare to die','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','&#42;')
</code></pre>
</div>

<p>…results in the string, including spaces:</p>

<pre><code class="language-json">[" &#42; &#42;  &#42;&#42; "]
</code></pre>

<p>This means that the letter “a” is being translated to an asterisk (<code>*</code>), but every other character that does not have a translation given the target string is completely removed. The whitespace is all we have left between the translated “a” characters.</p>

<p>Then again, this query:</p>

<div class="break-out">
<pre><code class="language-javascript">translate('Hello, My Name is Inigo Montoya, you killed my father, prepare to die','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,','&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;')")
</code></pre>
</div>

<p>…does not have the problem and outputs a result that looks like this:</p>

<div class="break-out">
<pre><code class="language-javascript">"&#42;&#42;&#42;&#42;&#42; &#42;&#42; &#42;&#42;&#42;&#42; &#42;&#42; &#42;&#42;&#42;&#42;&#42; &#42;&#42;&#42;&#42;&#42;&#42;&#42; &#42;&#42;&#42; &#42;&#42;&#42;&#42;&#42;&#42; &#42;&#42; &#42;&#42;&#42;&#42;&#42;&#42; &#42;&#42;&#42;&#42;&#42;&#42;&#42; &#42;&#42; &#42;&#42;&#42;"
</code></pre>
</div>
  

<p>It might strike you that there is no easy way in JavaScript to do exactly what the XPath <code>translate</code> function does, although for many use cases, <code>replaceAll</code> with regular expressions can handle it.</p>

<p>You could use the same approach I have demonstrated, but that is suboptimal if all you want is to translate the strings. The following demo wraps XPath’s <code>translate</code> function to provide a JavaScript version:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ZYWYLyZ"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [translate function [forked]](https://codepen.io/smashingmag/pen/ZYWYLyZ) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ZYWYLyZ">translate function [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>Where might you use something like this? Consider <a href="https://en.wikipedia.org/wiki/Caesar_cipher">Caesar Cipher</a> encryption with a three-place offset (e.g., top-of-the-line encryption from 48 B.C.):</p>

<div class="break-out">
<pre><code class="language-javascript">translate("Caesar is planning to cross the Rubicon!", 
 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
  "XYZABCDEFGHIJKLMNOPQRSTUVWxyzabcdefghijklmnopqrstuvw")
</code></pre>
</div>

<p>The input text “Caesar is planning to cross the Rubicon!” results in “Zxbpxo fp mixkkfkd ql zolpp qeb Oryfzlk!”</p>

<p>To give another quick example of different possibilities, I made a <code>metal</code> function that takes a string input and uses a <code>translate</code> function to return the text, including all characters that take umlauts.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="YPqPNrN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [metal function [forked]](https://codepen.io/smashingmag/pen/YPqPNrN) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/YPqPNrN">metal function [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<div class="break-out">
<pre><code class="language-javascript">const metal = (str) =&gt; {
  return translate(str, "AOUaou","ÄÖÜäöü");
}
</code></pre>
</div>
  

<p>And, if given the text “Motley Crue rules, rock on dudes!”, returns “Mötley Crüe rüles, röck ön düdes!”</p>

<p>Obviously, one might have all sorts of parody uses of this function. If that’s you, then this <a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/HeavyMetalUmlaut">TVTropes article</a> ought to provide you with plenty of inspiration.</p>

<h2 id="using-css-with-xpath">Using CSS With XPath</h2>

<p>Remember our main reason for using CSS selectors together with XPath: CSS pretty much understands what a class is, whereas the best you can do with XPath is string comparisons of the class attribute. That will work in most cases.</p>

<p>But if you were to ever run into a situation where, say, someone created classes named <code>.primaryLinks</code> and <code>.primaryLinks2</code> and you were using XPath to get the <code>.primaryLinks</code> class, then you would likely run into problems. As long as there’s nothing silly like that, you would probably use XPath. But I am sad to report that I have worked at places where people do those types of silly things.</p>

<p>Here’s another demo using CSS and XPath together. It shows what happens when we use the code to run an XPath on a context node that is not the document’s node.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ogxgBpz"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [css and xpath together [forked]](https://codepen.io/smashingmag/pen/ogxgBpz) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ogxgBpz">css and xpath together [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>The CSS query is <code>.relatedarticles a</code>, which fetches the two <code>a</code> elements in a <code>div</code> assigned a <code>.relatedarticles</code> class.</p>

<p>After that are three “bad” queries, that is to say, queries that do not do what we want them to do when running with these elements as the context node.</p>

<p>I can explain why they are behaving differently than you might expect. The three bad queries in question are:</p>

<ul>
<li><code>//text()</code>: Returns all the text in the document.</li>
<li><code>//a/text()</code>: Returns all the text inside of links in the document.</li>
<li><code>./a/text()</code>: Returns no results.</li>
</ul>

<p>The reason for these results is that while your context is <code>a</code> elements returned from the CSS query, <code>//</code> goes against the whole document. This is the strength of XPath; CSS cannot go from a node up to an ancestor and then to a sibling of that ancestor, and walk down to a descendant of that sibling. But XPath can.</p>

<p>Meanwhile, <code>./</code> queries the children of the current node, where the dot (<code>.</code>) represents the current node, and the forward slash (<code>/</code>) represents going to some child node &mdash; whether it is an attribute, element, or text is determined by the next part of the path. But there is no child <code>a</code> element selected by the CSS query, thus that query also returns nothing.</p>

<p>There are three good queries in that last demo:</p>

<ul>
<li><code>.//text()</code>,</li>
<li><code>./text()</code>,</li>
<li><code>normalize-space(./text())</code>.</li>
</ul>

<p>The <code>normalize-space</code> query demonstrates XPath function usage, but also fixes a problem included in the other queries. The HTML is structured like this:</p>

<div class="break-out">
<pre><code class="language-html">&lt;a href="https://www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/"&gt;
  Automating Your Feature Testing With Selenium WebDriver
&lt;/a&gt;
</code></pre>
</div>
  

<p>The query returns a line feed at the beginning and end of the text node, and <code>normalize-space</code> removes this.</p>

<p>Using any XPath function that returns something other than a boolean with an input XPath applies to other functions. The following demo shows a number of examples:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="JoXYGeN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [xpath functions examples [forked]](https://codepen.io/smashingmag/pen/JoXYGeN) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/JoXYGeN">xpath functions examples [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>The first example shows a problem you should watch out for. Specifically, the following code:</p>

<div class="break-out">
<pre><code class="language-javascript">document.queryXPaths("substring-after(//a/@href,'https://')");
</code></pre>
</div>
  

<p>…returns one string:</p>

<div class="break-out">
<pre><code class="language-html">"www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/"
</code></pre>
</div>

<p>It makes sense, right? These functions do not return arrays but rather single strings or single numbers. Running the function anywhere with multiple results only returns the first result.</p>

<p>The second result shows what we really want:</p>

<div class="break-out">
<pre><code class="language-javascript">document.queryCSSSelectors("a").queryXPaths("substring-after(./@href,'https://')");
</code></pre>
</div>

<p>Which returns an array of two strings:</p>

<div class="break-out">
<pre><code class="language-json">["www.smashingmagazine.com/2018/04/feature-testing-selenium-webdriver/","www.smashingmagazine.com/2022/11/automated-test-results-improve-accessibility/"]
</code></pre>
</div>

<p>XPath functions can be nested just like functions in JavaScript. So, if we know the Smashing Magazine URL structure, we could do the following (using template literals is recommended):</p>

<pre><code class="language-javascript">`translate(
    substring(
      substring-after(./@href, ‘www.smashingmagazine.com/')
    ,9),
 '/','')`
</code></pre>
  

<p>This is getting a bit too complex to the extent that it needs comments describing what it does: take all of the URL from the <code>href</code> attribute after <code>www.smashingmagazine.com/</code>, remove the first nine characters, then translate the forward slash (<code>/</code>) character to nothing so as to get rid of the ending forward slash.</p>

<p>The resulting array:</p>

<div class="break-out">
<pre><code class="language-json">["feature-testing-selenium-webdriver","automated-test-results-improve-accessibility"]
</code></pre>
</div>
  

<h2 id="more-xpath-use-cases">More XPath Use Cases</h2>

<p>XPath can really shine in <strong>testing</strong>. The reason is not difficult to see, as XPath can be used to get every element in the DOM, from any position in the DOM, whereas CSS cannot.</p>

<p>You cannot count on CSS classes remaining consistent in many modern build systems, but with XPath, we are able to make more robust matches as to what the text content of an element is, regardless of a changing DOM structure.</p>

<p>There has been <a href="https://ieeexplore.ieee.org/document/6983884">research on techniques</a> that allow you to make resilient XPath tests. Nothing is worse than having tests flake out and fail just because a CSS selector no longer works because something has been renamed or removed.</p>

<p>XPath is also really great at <strong>multiple locator extraction</strong>. There is more than one way to use XPath queries to match an element. The same is true with CSS. But XPath queries can drill into things in a more targeted way that limits what gets returned, allowing you to find a specific match where there may be several possible matches.</p>

<p>For example, we can use XPath to return a specific <code>h2</code> element that is contained inside a <code>div</code> that immediately follows a sibling <code>div</code> that, in turn, contains a child image element with a <code>data-testID=&quot;leader&quot;</code> attribute on it:</p>

<pre><code class="language-html">&lt;div&gt;
  &lt;div&gt;
    &lt;h1&gt;don't get this headline&lt;/h1&gt;
  &lt;/div&gt;
  
  &lt;div&gt;
    &lt;h2&gt;Don't get this headline either&lt;/h2&gt;
  &lt;/div&gt;
  
  &lt;div&gt;
    &lt;h2&gt;The header for the leader image&lt;/h2&gt;
  &lt;/div&gt;
  
  &lt;div&gt;
    &lt;img data-testID="leader" src="image.jpg"/&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
  

<p>This is the query:</p>

<pre><code class="language-javascript">document.queryXPaths(`
  //div[
    following-sibling::div[1]
    /img[@data-testID='leader']
  ]
  /h2/
  text()
`);
</code></pre>
  

<p>Let’s drop in a demo to see how that all comes together:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="zxqxNev"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Complex H2 Query [forked]](https://codepen.io/smashingmag/pen/zxqxNev) by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/zxqxNev">Complex H2 Query [forked]</a> by <a href="https://codepen.io/bryanrasmussen">Bryan Rasmussen</a>.</figcaption>
</figure>

<p>So, yes. There are lots of possible paths to any element in a test using XPath.</p>

<h2 id="xslt-1-0-deprecation">XSLT 1.0 Deprecation</h2>

<p>I mentioned early on that <a href="https://xslt.rip/">the Chrome team plans on removing XSLT 1.0 support from the browser</a>. That’s important because XSLT 1.0 uses XML-focused programming for document transformation that, in turn, relies on XPath 1.0, which is what is found in most browsers.</p>

<p>When that happens, we’ll lose a key component of XPath. But given the fact that XPath is really great for writing tests, I find it unlikely that XPath as a whole will disappear anytime soon.</p>

<p>That said, I’ve noticed that people get interested in a feature when it’s taken away. And that’s certainly true in the case of XSLT 1.0 being deprecated. <a href="https://news.ycombinator.com/item?id=45006098">There’s an entire discussion happening over at Hacker News</a> filled with arguments against the deprecation. The post itself is a great example of creating a blogging framework with XSLT. You can read the discussion for yourself, but it gets into how JavaScript might be used as a shim for XLST to handle those sorts of cases.</p>

<p>I have also <a href="https://www.saxonica.com/saxonjs/documentation3/index.html#!browser">seen suggestions</a> that browsers should use SaxonJS, which is a port to JavaScript’s Saxon XSLT, XQUERY, and XPath engines. That’s an interesting idea, especially as Saxon-JS implements the current version of these specifications, whereas there is no browser that implements any version of XPath or XSLT beyond 1.0, and none that implements XQuery.</p>

<p>I reached out to <a href="https://norm.tovey-walsh.com">Norm Tovey-Walsh</a> at Saxonica, the company behind SaxonJS and other versions of the Saxon engine. He said:</p>

<blockquote>“If any browser vendor was interested in taking SaxonJS as a starting point for integrating modern XML technologies into the browser, we’d be thrilled to discuss it with them.”<br /><br />&mdash; <a href="https://norm.tovey-walsh.com">Norm Tovey-Walsh</a></blockquote>

<p>But also added:</p>

<blockquote>“I would be very surprised if anyone thought that taking SaxonJS in its current form and dropping it into the browser build unchanged would be the ideal approach. A browser vendor, by nature of the fact that they build the browser, could approach the integration at a much deeper level than we can ‘from the outside’.”<br /><br />&mdash; <a href="https://norm.tovey-walsh.com">Norm Tovey-Walsh</a></blockquote>

<p>It’s worth noting that Tovey-Walsh’s comments came about a week before the XSLT deprecation announcement.</p>

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

<p>I could go on and on. But I hope this has demonstrated the <strong>power of XPath</strong> and given you plenty of examples demonstrating how to use it for achieving great things. It’s a perfect example of older technology in the browser stack that still has plenty of <strong>utility</strong> today, even if you’ve never known it existed or never considered reaching for it.</p>

<h3 id="further-reading">Further Reading</h3>

<ul>
<li>“<a href="https://dl.acm.org/doi/full/10.1145/3700523.3700536">Enhancing the Resiliency of Automated Web Tests with Natural Language</a>” (ACM Digital Library) by Maroun Ayli, Youssef Bakouny, Nader Jalloul, and Rima Kilany<br />
<em>This article provides many XPath examples for writing resilient tests.</em></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/XML/XPath">XPath</a> (MDN)<br />
<em>This is an excellent place to start if you want a technical explanation detailing how XPath works.</em></li>
<li><a href="http://www.zvon.org/xxl/XPathTutorial/General/examples.html">XPath Tutorial</a> (ZVON)<br />
<em>I’ve found this tutorial to be the most helpful in my own learning, thanks to a wealth of examples and clear explanations.</em></li>
<li><a href="https://xpather.com">XPather</a><br />
<em>This interactive tool lets you work directly with the code.</em></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>Matt Zeunert</author><title>Effectively Monitoring Web Performance</title><link>https://www.smashingmagazine.com/2025/11/effectively-monitoring-web-performance/</link><pubDate>Tue, 11 Nov 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/11/effectively-monitoring-web-performance/</guid><description>There are lots of tips for &lt;a href="https://www.debugbear.com/blog/improve-website-performance?utm_campaign=sm-10">improving your website performance&lt;/a>. But even if you follow all of the advice, are you able to maintain an optimized site? And are you targeting the right pages? Matt Zeunert outlines an effective strategy for web performance optimization and explains the roles that different types of data play in it.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/11/effectively-monitoring-web-performance/" />
              <title>Effectively Monitoring Web Performance</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Effectively Monitoring Web Performance</h1>
                  
                    
                    <address>Matt Zeunert</address>
                  
                  <time datetime="2025-11-11T10:00:00&#43;00:00" class="op-published">2025-11-11T10:00:00+00:00</time>
                  <time datetime="2025-11-11T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>DebugBear</b></p>
                

<p><a href="https://www.smashingmagazine.com/2023/08/running-page-speed-test-monitoring-versus-measuring/">There’s no single way to measure website performance.</a> That said, the <a href="https://www.smashingmagazine.com/2024/04/monitor-optimize-google-core-web-vitals/">Core Web Vitals</a> metrics that Google <a href="https://www.debugbear.com/docs/page-speed-seo?utm_campaign=sm-10">uses as a ranking factor</a> are a great starting point, as they cover different aspects of visitor experience:</p>

<ul>
<li><strong>Largest Contentful Paint (LCP):</strong> Measures the initial page load time.</li>
<li><strong>Cumulative Layout Shift (CLS)</strong>: Measures if content is stable after rendering.</li>
<li><strong>Interaction to Next Paint (INP)</strong>: Measures how quickly the page responds to user input.</li>
</ul>

<p>There are also <a href="https://www.debugbear.com/docs/web-performance-metrics?utm_campaign=sm-10">many other web performance metrics</a> that you can use to track technical aspects, like page weight or server response time. While these often don’t matter directly to the end user, they provide you with insight into what’s slowing down your pages.</p>

<p>You can also use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/User_timing">User Timing API</a> to track page load milestones that are important on your website specifically.</p>

<h2 id="synthetic-and-real-user-data">Synthetic And Real User Data</h2>

<p>There are <a href="https://www.debugbear.com/blog/synthetic-vs-rum?utm_campaing=sm-10">two different types</a> of web performance data:</p>

<ul>
<li><strong>Synthetic tests</strong> are run in a controlled test environment.</li>
<li><strong>Real user data</strong> is collected from actual website visitors.</li>
</ul>

<p>Synthetic monitoring can provide super-detailed reports to help you identify page speed issues. You can configure exactly how you want to collect the data, picking a specific network speed, device size, or test location.</p>

<p>Get a hands-on feel for synthetic monitoring by using the free <a href="https://www.debugbear.com/test/website-speed?utm_campaign=sm-10">DebugBear website speed test</a> to check on your website.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="672"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png"
			
			sizes="100vw"
			alt="DebugBear website speed report"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/1-debugbear-page-speed-report.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>That said, your synthetic test settings might not match what’s typical for your real visitors, and you can’t script all of the possible ways that people might interact with your website.</p>

<p>That’s why you also need real user monitoring (RUM). Instead of looking at one experience, you see different load times and how specific visitor segments are impacted. You can review specific page views to identify what caused poor performance for a particular visitor.</p>

<p>At the same time, real user data isn’t quite as detailed as synthetic test reports, due to web API limitations and performance concerns.</p>

<p>DebugBear offers both <a href="https://www.debugbear.com/synthetic-website-monitoring?utm_campaign=sm-10">synthetic monitoring</a> and <a href="https://www.debugbear.com/real-user-monitoring?utm_campaign=sm-10">real user monitoring</a>:</p>

<ul>
<li>To set up synthetic tests, you just need to enter a website URL, and</li>
<li>To collect real user metrics, you need to install an analytics snippet on your website.</li>
</ul>

<h2 id="three-steps-to-a-fast-website">Three Steps To A Fast Website</h2>

<p>Collecting data helps you throughout the lifecycle of your web performance optimizations. You can follow this three-step process:</p>

<ol>
<li><strong>Identify</strong>: Collect data across your website and identify slow visitor experiences.</li>
<li><strong>Diagnose</strong>: Dive deep into technical analysis to find optimizations.</li>
<li><strong>Monitor</strong>: Check that optimizations are working and get alerted to performance regressions.</li>
</ol>

<p>Let’s take a look at each step in detail.</p>

<h2 id="step-1-identify-slow-visitor-experiences">Step 1: Identify Slow Visitor Experiences</h2>

<p>What’s prompting you to look into website performance issues in the first place? You likely already have some specific issues in mind, whether that’s from customer reports or because of poor scores in the <a href="https://www.debugbear.com/blog/search-console-core-web-vitals?utm_campaign=sm-10">Core Web Vitals section of Google Search Console</a>.</p>

<p>Real user data is the best place to check for slow pages. It tells you whether the technical issues on your site actually result in poor user experience. It’s easy to collect across your whole website (while synthetic tests need to be set up for each URL). And, you can often get a view count along with the performance metrics. A moderately slow page that gets two visitors a month isn’t as important as a moderately fast page that gets thousands of visits a day.</p>

<p>The Web Vitals dashboard in DebugBear’s RUM product checks your site’s performance health and surfaces the most-visited pages and URLs where many visitors have a poor experience.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="644"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png"
			
			sizes="100vw"
			alt="Web Vitals dashboard in DebugBear’s RUM product"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/2-web-vitals-dashboard-debugbear-rum-product.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can also run a <a href="https://www.debugbear.com/docs/website-scan?utm_campaign=sm-10">website scan</a> to get a list of URLs from your sitemap and then check each of these pages against real user data from Google’s <a href="https://developer.chrome.com/docs/crux">Chrome User Experience Report (CrUX)</a>. However, this will only work for pages that meet a minimum traffic threshold to be included in the CrUX dataset.</p>

<p>The scan result highlights pages with poor web vitals scores where you might want to investigate further.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="632"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png"
			
			sizes="100vw"
			alt="Website scan result for ahrefs.com"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/3-website-scan-result-debugbear.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If no real-user data is available, then there is a scanning tool called <a href="https://www.debugbear.com/software/unlighthouse-website-scan">Unlighthouse</a>, which is based on Google’s Lighthouse tool. It runs synthetic tests for each page, allowing you to filter through the results in order to identify pages that need to be optimized.</p>

<h2 id="step-2-diagnose-web-performance-issues">Step 2: Diagnose Web Performance Issues</h2>

<p>Once you’ve identified slow pages on your website, you need to look at what’s actually happening on your page that is causing delays.</p>

<h3 id="debugging-page-load-time">Debugging Page Load Time</h3>

<p>If there are issues with page load time metrics &mdash; like the <a href="https://www.debugbear.com/docs/metrics/largest-contentful-paint?utm_campaign=sm-10">Largest Contentful Paint (LCP)</a> &mdash; synthetic test results can provide a detailed analysis. You can also run <a href="https://www.debugbear.com/docs/experiments?utm_campaign=sm-10">page speed experiments</a> to try out and measure the impact of certain optimizations.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="652"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png"
			
			sizes="100vw"
			alt="Page speed recommendations in synthetic data"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/4-page-speed-recommendations.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Real user data can still be important when debugging page speed, as load time depends on many user- and device-specific factors. For example, depending on the size of the user’s device, the page element that’s responsible for the LCP can vary. RUM data can provide a breakdown of possible influencing factors, like CSS selectors and image URLs, across all visitors, helping you zero in on what exactly needs to be fixed.</p>

<h3 id="debugging-slow-interactions">Debugging Slow Interactions</h3>

<p>RUM data is also generally needed to properly diagnose issues related to the <a href="https://debugbear.com/docs/rum/fix-inp-issues?utm_campaign=sm-10">Interaction to Next Paint (INP)</a> metric. Specifically, real user data can provide insight into what causes slow interactions, which helps you answer questions like:</p>

<ul>
<li>What page elements are responsible?</li>
<li>Is time spent processing already-active background tasks or handling the interaction itself?</li>
<li>What scripts contribute the most to overall CPU processing time?</li>
</ul>

<p>You can view this data at a high level to identify trends, as well as review specific page views to see what impacted a specific visitor experience.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="642"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png"
			
			sizes="100vw"
			alt="Interaction to Next Paint metric, which reviews specific page views"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/5-inp-interaction-element.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="step-3-monitor-performance-respond-to-regressions">Step 3: Monitor Performance &amp; Respond To Regressions</h2>

<p>Continuous monitoring of your website performance lets you track whether the performance is improving after making a change, and alerts you when scores decline.</p>

<p>How you respond to performance regressions depends on whether you’re looking at lab-based synthetic tests or real user analytics.</p>

<h3 id="synthetic-data">Synthetic Data</h3>

<p>Test settings for synthetic tests are standardized between runs. While infrastructure changes, like browser upgrades, occasionally cause changes, performance is more generally determined by resources loaded by the website and the code it runs.</p>

<p>When a metric changes, DebugBear lets you view a before-and-after comparison between the two test results. For example, the next screenshot displays a regression in the First Contentful Paint (FCP) metric. The comparison reveals that new images were added to the page, <a href="https://www.debugbear.com/blog/bandwidth-competition-page-speed?utm_campaign=sm-10">competing for bandwidth with other page resources</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="720"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png"
			
			sizes="100vw"
			alt="Before-and-after comparison between the two synthetic test results"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/6-synthetic-tests.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>From the report, it’s clear that a CSS file that previously took 255 milliseconds to load now takes 915 milliseconds. Since stylesheets are required to render page content, this means the page now loads more slowly, giving you better insight into what needs optimization.</p>

<h3 id="real-user-data">Real User Data</h3>

<p>When you see a change in real user metrics, there can be two causes:</p>

<ol>
<li>A shift in visitor characteristics or behavior, or</li>
<li>A technical change on your website.</li>
</ol>

<p>Launching an ad campaign, for example, often increases redirects, reduces cache hits, and shifts visitor demographics. When you see a regression in RUM data, the first step is to find out if the change was on your website or in your visitor’s browser. Check for view count changes in ad campaigns, referrer domains, or network speed to get a clearer picture.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="370"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png"
			
			sizes="100vw"
			alt="LCP by UTM campaign"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/7-lcp-utm-campaign.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If those visits have different performance compared to your typical visitors, then that suggests the repression is not due to a change on your website. However, you may still need to make changes on your website to better serve these visitor cohorts and deliver a good experience for them.</p>

<p>To identify the cause of a technical change, take a look at component breakdown metrics, such as <a href="https://www.smashingmagazine.com/2025/03/how-to-fix-largest-contentful-issues-with-subpart-analysis/">LCP subparts</a>. This helps you narrow down the cause of a regression, whether it is due to changes in server response time, new render-blocking resources, or the LCP image.</p>

<p>You can also check for shifts in page view properties, like different LCP element selectors or specific scripts that cause poor performance.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.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/effectively-monitoring-web-performance/8-lcp-subparts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.png"
			
			sizes="100vw"
			alt="INP subparts"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/8-lcp-subparts.png'>Large preview</a>)
    </figcaption>
  
</figure>

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

<p>One-off page speed tests are a great starting point for optimizing performance. However, a monitoring tool like DebugBear can form the basis for a more comprehensive web <strong>performance strategy</strong> that helps you stay fast for the long term.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="477"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png"
			
			sizes="100vw"
			alt="Summary of performance metrics on DebugBear"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/effectively-monitoring-web-performance/9-debugbear-web-performance.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Get <a href="https://www.debugbear.com/?utm_campaign=sm-10">a free DebugBear trial</a> on our website!</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>Preethi Sam</author><title>SerpApi: A Complete API For Fetching Search Engine Data</title><link>https://www.smashingmagazine.com/2025/09/serpapi-complete-api-fetching-search-engine-data/</link><pubDate>Tue, 16 Sep 2025 17:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/09/serpapi-complete-api-fetching-search-engine-data/</guid><description>From competitive SEO research and monitoring prices to training AI and parsing local geographic data, real-time search results power smarter apps. Tools like SerpApi make it easy to pull, customize, and integrate this data directly into your app or website.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/09/serpapi-complete-api-fetching-search-engine-data/" />
              <title>SerpApi: A Complete API For Fetching Search Engine Data</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>SerpApi: A Complete API For Fetching Search Engine Data</h1>
                  
                    
                    <address>Preethi Sam</address>
                  
                  <time datetime="2025-09-16T17:00:00&#43;00:00" class="op-published">2025-09-16T17:00:00+00:00</time>
                  <time datetime="2025-09-16T17:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>SerpApi</b></p>
                

<p>SerpApi leverages the power of search engine giants, like Google, DuckDuckGo, Baidu, and more, to put together the most pertinent and accurate search result data for your users from the comfort of your app or website. It’s customizable, adaptable, and offers an easy integration into any project.</p>

<p>What do you want to put together?</p>

<ul>
<li>Search information on a brand or business for <a href="https://serpapi.com/use-cases/seo?utm_source=smashingmagazine">SEO purposes</a>;</li>
<li>Input data to <a href="https://serpapi.com/use-cases/machine-learning-and-artificial-intelligence?utm_source=smashingmagazine">train AI models</a>, such as the Large Language Model, for a customer service chatbot;</li>
<li>Top <a href="https://serpapi.com/use-cases/news-monitoring?utm_source=smashingmagazine">news</a> and websites to pick from for a subscriber newsletter;</li>
<li><a href="https://serpapi.com/google-flights-api?utm_source=smashingmagazine">Google Flights API</a>: collect flight information for your travel app;</li>
<li><a href="https://serpapi.com/use-cases/price-monitoring?utm_source=smashingmagazine">Price</a> comparisons for the same product across different platforms;</li>
<li>Extra definitions and examples for words that can be offered along a language learning app.</li>
</ul>

<p>The list goes on.</p>

<p>In other words, you get to leverage the most comprehensive source of data on the internet for any number of needs, from <a href="https://serpapi.com/use-cases/seo?utm_source=smashingmagazine">competitive SEO research</a> and <a href="https://serpapi.com/use-cases/news-monitoring?utm_source=smashingmagazine">tracking news</a> to <a href="https://serpapi.com/use-cases/local-seo?utm_source=smashingmagazine">parsing local geographic data</a> and even <a href="https://serpapi.com/use-cases/background-check-automation?utm_source=smashingmagazine">completing personal background checks</a> for employment.</p>

<h2 id="start-with-a-simple-get-request">Start With A Simple GET Request</h2>

<p>The results from the <a href="https://serpapi.com?utm_source=smashingmagazine/#integrationsMountPoint">search API</a> are <strong>only a URL request away</strong> for those who want a super quick start. Just add your search details in the URL parameters. Say you need the search result for “Stone Henge” from the location “Westminster, England, United Kingdom” in language “en-GB”, and country of search origin “uk” from the domain “google.co.uk”. Here’s how simple it is to put the GET request together:</p>

<div class="break-out">
<pre><code class="language-json">https://serpapi.com/search.json?q=Stone+Henge&location=Westminster,+England,+United+Kingdom&hl=en-GB&gl=uk&google_domain=google.co.uk&api_key=your_api_key
</code></pre>
</div>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.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/serpapi-complete-api-fetching-search-engine-data/1-get-request.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.png"
			
			sizes="100vw"
			alt=""
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/1-get-request.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Then there’s the impressive list of libraries that seamlessly integrate the APIs into mainstream programming languages and frameworks such as JavaScript, Ruby, .NET, and more.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.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/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png"
			
			sizes="100vw"
			alt="JavaScript integration code for SerpApi"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      JavaScript integration code for SerpApi. (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/2-javascript-integration-code-serpapi.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.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/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png"
			
			sizes="100vw"
			alt="Table of SerpApi libraries showing information about seven libraries."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/3-table-serpapi-libraries.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="give-it-a-quick-try">Give It A Quick Try</h2>

<p>Want to give it a spin? <a href="https://serpapi.com/users/sign_up?utm_source=smashingmagazine">Sign up and start for free</a>, or tinker with the SerpApi’s <a href="https://serpapi.com/playground?utm_source=smashingmagazine">live playground</a> without signing up. The <strong>playground</strong> allows you to choose which search engine to target, and you can fill in the values for all the basic parameters available in the chosen API to customize your search. On clicking “Search”, you get the search result page and its extracted JSON data.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.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/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png"
			
			sizes="100vw"
			alt="Playground search for flights from LGW to MLA airport using SerpApi’s Google Flights API."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Playground search for flights from LGW to MLA airport using SerpApi’s Google Flights API. (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/4-serpapi-google-flights-api.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If you need to get a feel for the full API first, you can explore their easy-to-grasp <a href="https://serpapi.com/search-api?utm_source=smashingmagazine">web documentation</a> before making any decision. You have the chance to work with all of the APIs to your satisfaction before committing to it, and when that time comes, SerpApi’s multiple <a href="https://serpapi.com/pricing?utm_source=smashingmagazine">price plans</a> tackle anywhere between an economic few hundred searches a month and bulk queries fit for large corporations.</p>

<h2 id="what-data-do-you-need">What Data Do You Need?</h2>

<p>Beyond the rudimentary search scraping, SerpApi provides a range of configurations, features, and additional APIs worth considering.</p>

<h3 id="geolocation">Geolocation</h3>

<p>Capture the global trends, or refine down to more localized particulars by names of locations or Google’s place identifiers. SerpApi’s optimized routing of requests ensures <strong>accurate retrieval of search result</strong> data from any location worldwide. If locations themselves are the answers to your queries &mdash; say, a cycle trail to be suggested in a fitness app &mdash; those can be extracted and presented as maps using SerpApi’s <a href="https://serpapi.com/google-maps-api?utm_source=smashingmagazine">Google Maps API</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.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/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png"
			
			sizes="100vw"
			alt="SerpApi’s cycle route"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/5-serpapi-geolocation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="structured-json">Structured JSON</h3>

<p>Although search engines reveal results in a tidy user interface, deriving data into your application could cause you to end up with a large data dump to be sifted through &mdash; but not if you’re using SerpApi.</p>

<p>SerpApi pulls data in a <strong>well-structured JSON format</strong>, even for the popular kinds of <em>enriched search results</em>, such as knowledge graphs, review snippets, sports league stats, ratings, product listings, AI overview, and more.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.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/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png"
			
			sizes="100vw"
			alt="Example of SerpApi returning data in a JSON format."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      SerpApi returns data in a JSON format, making it easy to integrate into your application. (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/6-serpapi-data-json-format.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.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/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png"
			
			sizes="100vw"
			alt="Various types of search engine results, such as meta related to video, audio, geolocation, questions, and recipes."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Various types of search engine results, such as meta related to video, audio, geolocation, questions, and recipes. (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/7-types-search-engine-results.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="speedy-results">Speedy Results</h3>

<p>SerpApi’s baseline performance can take care of timely search data for real-time requirements. But what if you need more? SerpApi’s <a href="https://serpapi.com/ludicrous-speed"><strong>Ludicrous Speed</strong></a> option, easily enabled from the dashboard with an upgrade, provides a super-fast response time. More than twice as fast as usual, thanks to twice the server power.</p>

<p>There’s also <a href="https://serpapi.com/ludicrous-speed-max"><strong>Ludicrous Speed Max</strong></a>, which allocates four times more server resources for your data retrieval. Data that is time-sensitive and for monitoring things in real-time, such as sports scores and tracking product prices, will lose its value if it is not handled in a timely manner. Ludicrous Speed Max guarantees no delays, even for a large-scale enterprise haul.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.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/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png"
			
			sizes="100vw"
			alt="A list of flight prices from Google Flights"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A list of flight prices from Google Flights. (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/8-list-flight-prices.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can also use a relevant SerpApi API to hone in on your <strong>relevant category</strong>, like <a href="https://serpapi.com/google-flights-api?utm_source=smashingmagazine">Google Flights API</a>, <a href="https://serpapi.com/amazon-search-api?utm_source=smashingmagazine">Amazon API</a>, <a href="https://serpapi.com/google-news-api?utm_source=smashingmagazine">Google News API</a>, etc., to get fresh and apt results.</p>

<p>If you don’t need the full depth of the <a href="https://serpapi.com?utm_source=smashingmagazine/#integrationsMountPoint">search</a> <a href="https://serpapi.com?utm_source=smashingmagazine/#integrationsMountPoint">API</a>, there’s a <strong>Light version</strong> available for Google Search, Google Images, Google Videos, Google News, and DuckDuckGo Search APIs.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.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/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png"
			
			sizes="100vw"
			alt="Three-column list of 45 Search APIs that are supported by SerpApi"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/9-serpapi-api-list.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="search-controls-privacy">Search Controls &amp; Privacy</h3>

<p>Need the results asynchronously picked up? Want a refined output using advanced <a href="https://serpapi.com?utm_source=smashingmagazine/#integrationsMountPoint">search</a> <a href="https://serpapi.com?utm_source=smashingmagazine/#integrationsMountPoint">API</a> parameters and a JSON Restrictor? Looking for search outcomes for specific devices? Don’t want auto-corrected query results? <strong>There’s no shortage of ways to configure SerpApi to get exactly what you need.</strong></p>

<p>Additionally, if you prefer not to have your search metadata on their servers, simply turn on the <a href="https://serpapi.com/zero-trace-mode?utm_source=smashingmagazine"><strong>“ZeroTrace” mode</strong></a> that’s available for selected plans.</p>

<h3 id="the-x-ray">The X-Ray</h3>

<p>Save yourself a headache, literally, trying to play match between what you see on a search result page and its extracted data in JSON. SerpApi’s <a href="https://serpapi.com/xray?utm_source=smashingmagazine"><strong>X-Ray tool</strong></a> <strong>shows you where what comes from</strong>. It’s available and free in all plans.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.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/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png"
			
			sizes="100vw"
			alt="SerpApi’s X-Ray tool"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/serpapi-complete-api-fetching-search-engine-data/10-serpapi-x-ray.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="inclusive-support">Inclusive Support</h3>

<p>If you don’t have the expertise or resources for tackling the validity of scraping search results, here’s what SerpApi says:</p>

<blockquote>“SerpApi, LLC assumes scraping and parsing liabilities for both domestic and foreign companies unless your usage is otherwise illegal”.</blockquote>

<p>You can reach out and have a conversation with them regarding the legal protections they offer, as well as inquire about anything else you might want to know about, including SerpApi in your project, such as pricing, performance expected, on-demand options, and technical support.  Just drop a message at their <a href="https://serpapi.com/#contact">contact page</a>.</p>

<p>In other words, the SerpApi team has your back with the support and expertise to get the most from your fetched data.</p>

<h3 id="try-serpapi-free">Try SerpApi Free</h3>

<p>That’s right, you can get your hands on SerpApi today and start fetching data with absolutely no commitment, thanks to a free starter plan that gives you up to 250 free search queries. Give it a try and then bump up to one of the reasonably-priced monthly subscription plans with generous search limits.</p>

<ul>
<li><a href="https://serpapi.com/users/sign_up?utm_source=smashingmagazine">Try SerpApi</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>Marius Sarca</author><title>Creating Elastic And Bounce Effects With Expressive Animator</title><link>https://www.smashingmagazine.com/2025/09/creating-elastic-bounce-effects-expressive-animator/</link><pubDate>Mon, 15 Sep 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/09/creating-elastic-bounce-effects-expressive-animator/</guid><description>Elastic and bounce effects have long been among the most desirable but time-consuming techniques in motion design. Expressive Animator streamlines the process, making it possible to produce lively animations in seconds, bypassing the tedious work of manual keyframe editing.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/09/creating-elastic-bounce-effects-expressive-animator/" />
              <title>Creating Elastic And Bounce Effects With Expressive Animator</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Creating Elastic And Bounce Effects With Expressive Animator</h1>
                  
                    
                    <address>Marius Sarca</address>
                  
                  <time datetime="2025-09-15T10:00:00&#43;00:00" class="op-published">2025-09-15T10:00:00+00:00</time>
                  <time datetime="2025-09-15T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Expressive</b></p>
                

<p>In the world of modern web design, SVG images are used everywhere, from illustrations to icons to background effects, and are universally prized for their crispness and lightweight size. While static SVG images play an important role in web design, most of the time their true potential is unlocked only when they are combined with motion.</p>

<p>Few things add more life and personality to a website than a well-executed SVG animation. But not all animations have the same impact in terms of digital experience. For example, <strong>elastic and bounce effects</strong> have a unique appeal in motion design because they bring a <strong>sense of realism into movement</strong>, making animations more engaging and memorable.</p>

<figure><a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg.gif"><img src="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg-800.gif" width="800" height="800" alt="Grumpy Egg" /></a><figcaption>(<a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg.gif">Large preview</a>)</figcaption></figure>

<p>However, anyone who has dived into animating SVGs knows <a href="https://www.smashingmagazine.com/2023/02/putting-gears-motion-animating-cars-with-html-svg/">the technical hurdles involved</a>. Creating a convincing elastic or bounce effect traditionally requires handling complex CSS keyframes or wrestling with JavaScript animation libraries. Even when using an SVG animation editor, it will most likely require you to manually add the keyframes and adjust the easing functions between them, which can become a time-consuming process of trial and error, no matter the level of experience you have.</p>

<p>This is where Expressive Animator shines. It allows creators to apply elastic and bounce effects <strong>in seconds</strong>, bypassing the tedious work of manual keyframe editing. And the result is always exceptional: animations that feel <em>alive</em>, produced with a fraction of the effort.</p>

<h2 id="using-expressive-animator-to-create-an-elastic-effect">Using Expressive Animator To Create An Elastic Effect</h2>

<p>Creating an elastic effect in Expressive Animator is remarkably simple, fast, and intuitive, since the effect is built right into the software as an easing function. This means you only need two keyframes (start and end) to make the effect, and the software will automatically handle the springy motion in between. Even better, the elastic easing can be applied to <strong>any animatable property</strong> (e.g., position, scale, rotation, opacity, morph, etc.), giving you a consistent way to add it to your animations.</p>

<p>Before we dive into the tutorial, take a look at the video below to see what you will learn to create and the entire process from start to finish.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1116135653"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>First things first, let’s set the scene. For this, we’ll <a href="https://expressive.app/expressive-animator/docs/v1/projects/create/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">create a new project</a> by pressing <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>P</kbd> and configuring it in the “Create New Project” dialog that pops up. For frame size, we’ll choose 1080×1080, for a duration of 00:01:30, and we’ll let the frame rate remain unchanged at 60 frames per second (fps).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png"
			
			sizes="100vw"
			alt="“Create New Project” dialog"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/01-create-dialog.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once you hit the “Create project” button, you can use the <a href="https://expressive.app/expressive-animator/docs/v1/tools/pen-tool/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Pen</a> and <a href="https://expressive.app/expressive-animator/docs/v1/tools/ellipse-tool/">Ellipse</a> tools to create the artwork that will be animated, or you can simply copy and paste the artwork below.</p>

<figure class="break-out">
	<p data-height="600"
	data-theme-id="light"
	data-slug-hash="pvjmwxv"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Effects With Expressive Animator - Artwork for Animation](https://codepen.io/smashingmag/pen/pvjmwxv).</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvjmwxv">Effects With Expressive Animator - Artwork for Animation</a>.</figcaption>
</figure>

<p>Now that everything has been set up, let’s create the animation. Make sure that snapping and auto-record are enabled, then move the playhead to 01:00f. By <a href="https://expressive.app/expressive-animator/docs/v1/canvas/snapping/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">enabling snapping</a>, you will be able to perfectly align nodes and graphic objects on the canvas. On the other hand, as the name suggests, auto-record tracks every change you make to the artwork and adds the appropriate keyframes on the timeline.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png"
			
			sizes="100vw"
			alt="Screenshot with snapping and auto-record are enabled and the playhead moved to 01:00f"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/02-prepare-scene.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Press the <kbd>A</kbd> key on your keyboard to switch to the <a href="https://expressive.app/expressive-animator/docs/v1/tools/node-tool/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Node tool</a>, then select the String object and move its handle to the center-right point of the artboard. Don’t worry about precision, as the snapping will do all the heavy lifting for you. This will bend the shape and add keyframes for the Morph animator.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png"
			
			sizes="100vw"
			alt="Screenshot with the String object and its handle moved to the center-right point of the artboard"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/03-string.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Next, press the <kbd>V</kbd> key on your keyboard to switch to the <a href="https://expressive.app/expressive-animator/docs/v1/tools/selection-tool/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Selection tool</a>. With this tool enabled, select the Ball, move it to the right, and place it in the middle of the string. Once again, snapping will do all the hard work, allowing you to position the ball exactly where you want to, while auto-recording automatically adds the appropriate keyframes.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png"
			
			sizes="100vw"
			alt="Screenshot with the Ball selected and moved to the middle of the string"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/04-ball.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can now replay the animation and disable auto-recording by clicking on the Auto-Record button again.</p>

<p>As you can see when replaying, the direction in which the String and Ball objects are moving is wrong. Fortunately, we can fix this extremely easily just by reversing the keyframes. To do this, select the keyframes in the timeline and right-click to open the context menu and choose Reverse. This will reverse the keyframes, and if you replay the animation, you will see that the direction is now correct.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png"
			
			sizes="100vw"
			alt="Screenshot with the context menu where you can choose Reverse"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/05-reverse.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>With this out of the way, we can finally add the elastic effect. Select all the keyframes in the timeline and click on the Custom easing button to open a dialog with easing options. From the dialog, choose Elastic and set the oscillations to 4 and the stiffness to 2.5.</p>

<p>That’s it! Click anywhere outside the easing dialog to close it and replay the animation to see the result.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png"
			
			sizes="100vw"
			alt="Selected custom easing button that opened a dialog with easing options"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/06-effect.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><a href="https://expressive.app/expressive-animator/docs/v1/export/svg/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">The animation can be exported as well.</a> Press <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>E</kbd> on your keyboard to open the export dialog and choose from various export options, ranging from vectorized formats, such as <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">SVG</a> and <a href="https://expressive.app/expressive-animator/docs/v1/export/lottie/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Lottie</a>, to rasterized formats, such as <a href="https://expressive.app/expressive-animator/docs/v1/export/image/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">GIF</a> and <a href="https://expressive.app/expressive-animator/docs/v1/export/video/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">video</a>.</p>

<p>For this specific animation, we’re going to choose the SVG export format. Expressive Animator allows you to choose between three different types of SVG, depending on the technology used for animation: <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/smil/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">SMIL</a>, <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/css/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">CSS</a>, or <a href="https://expressive.app/expressive-animator/docs/v1/export/svg/js/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">JavaScript</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="467"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png"
			
			sizes="100vw"
			alt="Export settings in the Expressive Animator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/07-export.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Each of these technologies has different strengths and weaknesses, but for this tutorial, we are going to choose SMIL. This is because SMIL-based animations are widely supported, even on Safari browsers, and can be used as background images or embedded in HTML pages using the <code>&lt;img&gt;</code>  tag. In fact, <a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Andy Clarke recently wrote all about SMIL animations here at Smashing Magazine</a> if you want a full explanation of how it works.</p>

<p>You can visualize the exported SVG in the following CodePen demo:</p>

<figure class="break-out">
	<p data-height="600"
	data-theme-id="light"
	data-slug-hash="GgpaEyG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Expressive Animator - Exported SVG](https://codepen.io/smashingmag/pen/GgpaEyG).</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/GgpaEyG">Expressive Animator - Exported SVG</a>.</figcaption>
</figure>

<h2 id="expressive-animator-for-bounce-and-other-effects">Expressive Animator For Bounce And Other Effects</h2>

<p>Adding a bounce effect to an animation is very similar to the process we just covered for creating an elastic effect, since both are built into Expressive Animator as easing functions. Just like elastic, bounce easing can be applied to any animatable property, giving you quick ways to create realistic motion.</p>

<p>Beyond these two effects, Expressive Animator also offers other easing options that can shape the personality of your animation, like Back, Steps, Sinc, just to name a few.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="757"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png"
			
			sizes="100vw"
			alt="Easing functions in the Expressive Animator"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/08-easing-functions.png'>Large preview</a>)
    </figcaption>
  
</figure>

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

<p>Elastic and bounce effects have long been among the most desirable but time-consuming techniques in motion design. By integrating them directly into its easing functions, Expressive Animator removes the complexity of manual keyframe manipulation and transforms what used to be a technical challenge into a creative opportunity.</p>

<p>The best part is that getting started with Expressive Animator comes with zero risk. The software offers a full 7&ndash;day <strong>free trial without requiring an account</strong>, so you can download it instantly and begin experimenting with your own designs right away. After the trial ends, you can buy Expressive Animator with a one-time payment, <strong>no subscription required</strong>. This will give you a perpetual license covering both Windows and macOS.</p>

<p>To help you get started even faster, I’ve prepared some extra resources for you. You’ll find the source files for the animations created in this tutorial, along with a curated list of useful links that will guide you further in exploring Expressive Animator and SVG animation. These materials are meant to give you a solid starting point so you can learn, experiment, and build on your own with confidence.</p>

<ul>
<li>Grumpy Egg: The <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/grumpy-egg.eaf" download><code>.eaf</code></a> source file for the sample animation presented at the beginning of this article.</li>
<li>Elastic Effect: Another <a href="https://files.smashing.media/articles/creating-elastic-bounce-effects-expressive-animator/elastic-effect.eaf" download><code>.eaf</code></a> file, this time for the animation we made in this tutorial.</li>
<li><a href="https://expressive.app/expressive-animator/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Get started with Expressive Animator</a></li>
<li>Expressive Animator <a href="https://expressive.app/expressive-animator/docs/v1/?utm_source=smashingmagazine&amp;utm_medium=blog&amp;utm_campaign=elastic_effect">Documentation</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>Joas Pambou</author><title>Automating Design Systems: Tips And Resources For Getting Started</title><link>https://www.smashingmagazine.com/2025/08/automating-design-systems-tips-resources/</link><pubDate>Wed, 06 Aug 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/08/automating-design-systems-tips-resources/</guid><description>Design systems are more than style guides: they’re made up of workflows, tokens, components, and documentation &amp;mdash; all the stuff teams rely on to build consistent products. As projects grow, keeping everything in sync gets tricky fast. In this article, we’ll look at how smart tooling, combined with automation where it makes sense, can speed things up, reduce errors, and help your team focus on design over maintenance.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/08/automating-design-systems-tips-resources/" />
              <title>Automating Design Systems: Tips And Resources For Getting Started</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Automating Design Systems: Tips And Resources For Getting Started</h1>
                  
                    
                    <address>Joas Pambou</address>
                  
                  <time datetime="2025-08-06T10:00:00&#43;00:00" class="op-published">2025-08-06T10:00:00+00:00</time>
                  <time datetime="2025-08-06T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>A design system is more than just a set of colors and buttons. It’s a shared language that helps designers and developers build good products together. At its core, a design system includes <a href="https://www.smashingmagazine.com/2024/05/naming-best-practices/">tokens</a> (like colors, spacing, fonts), <a href="https://www.smashingmagazine.com/2022/12/anatomy-themed-design-system-components/">components</a> (such as buttons, forms, navigation), plus the <a href="https://www.smashingmagazine.com/2023/11/designing-web-design-documentation/">rules and documentation</a> that tie all together across projects.</p>

<p>If you’ve ever used systems like <a href="https://m3.material.io/">Google Material Design</a> or <a href="https://polaris-react.shopify.com/">Shopify Polaris</a>, for example, then you’ve seen how design systems set <strong>clear expectations for structure and behavior</strong>, making teamwork smoother and faster. But while design systems promote consistency, keeping everything in sync is the hard part. Update a token in Figma, like a color or spacing value, and that change has to show up in the code, the documentation, and everywhere else it’s used.</p>

<p>The same thing goes for components: when a button’s behavior changes, it needs to update across the whole system. That’s where the right tools and a bit of automation can make the difference. They help reduce repetitive work and keep the system easier to manage as it grows.</p>

<p>In this article, we’ll cover a variety of <strong>tools and techniques for syncing tokens, updating components, and keeping docs up to date</strong>, showing how automation can make all of it easier.</p>

<h2 id="the-building-blocks-of-automation">The Building Blocks Of Automation</h2>

<p>Let’s start with the basics. Color, typography, spacing, radii, shadows, and all the tiny values that make up your visual language are known as <strong>design tokens</strong>, and they’re meant to be the single source of truth for the UI. You’ll see them in design software like Figma, in code, in style guides, and in documentation. <a href="https://www.smashingmagazine.com/2024/05/naming-best-practices/">Smashing Magazine has covered them</a> before in great detail.</p>

<p>The problem is that they <strong>often go out of sync</strong>, such as when a color or component changes in design but doesn’t get updated in the code. The more your team grows or changes, the more these mismatches show up; not because people aren’t paying attention, but because <strong>manual syncing just doesn’t scale</strong>. That’s why <strong>automating tokens</strong> is usually the first thing teams should consider doing when they start building a design system. That way, instead of writing the same color value in Figma and then again in a configuration file, you pull from a shared token source and let that drive both design and development.</p>

<p>There are a few tools that are designed to help make this easier.</p>

<h3 id="token-studio">Token Studio</h3>

<p><a href="https://tokens.studio/">Token Studio</a> is a Figma plugin that lets you manage design tokens directly in your file, export them to different formats, and sync them to code.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="503"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png"
			
			sizes="100vw"
			alt="Token Studio"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/1-token-studio-figma.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="specify">Specify</h3>

<p><a href="https://specifyapp.com/">Specify</a> lets you collect tokens from Figma and push them to different targets, including GitHub repositories, continuous integration pipelines, documentation, and more.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1107014014"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<h3 id="design-tokens-dev">Design-tokens.dev</h3>

<p><a href="https://design-tokens.dev/">Design-tokens.dev</a> is a helpful reference if you want tips for things like how to structure tokens, format them (e.g., JSON, YAML, and so on), and think about token types.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="370"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png"
			
			sizes="100vw"
			alt="Design-tokens.dev screen showing the output of named design tokens generated by the system."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/2-design-tokens-dev-screen.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="namedesigntokens-guide">NameDesignTokens.guide</h3>

<p><a href="https://namedesigntokens.guide/">NamedDesignTokens.guide</a> helps with naming conventions, which is honestly a common pain point, especially when you’re working with a large number of tokens.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="644"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png"
			
			sizes="100vw"
			alt="Token configuration"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/3-token-configuration.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once your tokens are set and connected, you’ll spend way less time fixing inconsistencies. It also gives you a solid base to scale, whether that’s adding themes, switching brands, or even building systems for multiple products.</p>

<p>That’s also when naming really starts to count. If your tokens or components aren’t clearly named, things can get confusing quickly.</p>

<p><strong>Note</strong>: <em>Vitaly Friedman’s “<a href="https://www.linkedin.com/posts/vitalyfriedman_how-to-name-things-httpslnkdineirqgv9a-activity-7338149568607363073-j0">How to Name Things</a>” is worth checking out if you’re working with larger systems.</em></p>

<p>From there, it’s all about components. Tokens define the values, but components are what people actually use, e.g., buttons, inputs, cards, dropdowns &mdash; you name it. In a perfect setup, you build a component once and reuse it everywhere. But without structure, it’s easy for things to “drift” out of scope. It’s easy to end up with five versions of the same button, and what’s in code doesn’t match what’s in Figma, for example.</p>

<blockquote>Automation doesn’t replace design, but rather, it connects everything to one source.</blockquote>

<p>The Figma component matches the one in production, the documentation updates when the component changes, and the whole team is pulling from the same library instead of rebuilding their own version. This is where real collaboration happens.</p>

<p>Here are a few tools that help make that happen:</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>Tool</th>
            <th>What It Does</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><a href="https://www.uxpin.com/merge">UXPin Merge</a></td>
            <td>Lets you design using real code components. What you prototype is what gets built.</td>
        </tr>
        <tr>
            <td><a href="https://www.supernova.io/">Supernova</a></td>
            <td>Helps you publish a design system, sync design and code sources, and keep documentation up-to-date.</td>
        </tr>
        <tr>
            <td><a href="https://zeroheight.com/">Zeroheight</a></td>
            <td>Turns your Figma components into a central, browsable, and documented system for your whole team.</td>
        </tr>
    </tbody>
</table>

<h2 id="how-does-everything-connect">How Does Everything Connect?</h2>

<p>A lot of the work starts right inside your design application. Once your tokens and components are in place, tools like Supernova help you take it further by extracting design data, syncing it across platforms, and generating production-ready code. You don’t need to write custom scripts or use the Figma API to get value from automation; these tools handle most of it for you.</p>

<p>But for teams that want full control, <a href="https://www.figma.com/developers/api">Figma does offer an API</a>. It lets you do things like the following:</p>

<ul>
<li>Pull token values (like colors, spacing, typography) directly from Figma files,</li>
<li>Track changes to components and variants,</li>
<li>Tead metadata (like style names, structure, or usage patterns), and</li>
<li>Map which components are used where in the design.</li>
</ul>

<p>The Figma API is <strong>REST-based</strong>, so it works well with custom scripts and automations. You don’t need a huge setup, just the right pieces. On the development side, teams usually use Node.js or Python to handle automation. For example:</p>

<ul>
<li>Fetch styles from Figma.</li>
<li>Convert them into JSON.</li>
<li>Push the values to a design token repo or directly into the codebase.</li>
</ul>

<p>You won’t need that level of setup for most use cases, but it’s helpful to know it’s there if your team outgrows no-code tools.</p>

<ul>
<li>Where do your tokens and components come from?</li>
<li>How do updates happen?</li>
<li>What tools keep everything connected?</li>
</ul>

<p>The workflow becomes easier to manage once that’s clear, and you spend less time trying to fix changes or mismatches. When tokens, components, and documentation stay in sync, your team moves faster and spends less time fixing the same issues.</p>

<h2 id="extracting-design-data">Extracting Design Data</h2>

<p><strong>Figma</strong> is a collaborative design tool used to create UIs: buttons, layouts, styles, components, everything that makes up the visual language of the product. It’s also where all your design data lives, which includes the tokens we talked about earlier. This data is what we’ll extract and eventually connect to your codebase. But first, you’ll need a setup.</p>

<p>To follow along:</p>

<ol>
<li>Go to <a href="https://figma.com">figma.com</a> and create a free account.</li>
<li>Download the Figma desktop app if you prefer working locally, but keep an eye on system requirements if you’re on an older device.</li>
</ol>

<p>Once you’re in, you’ll see a home screen that looks something like the following:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.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/automating-design-systems-tips-resources/4-figma-dashboard.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.png"
			
			sizes="100vw"
			alt="Figma dashboard showing a left sidebar navigation for exploring design files and a grid of thumbnail images on the right for previewing specific files."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/4-figma-dashboard.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>From here, it’s time to set up your design tokens. You can either create everything from scratch or <a href="https://www.figma.com/templates/">use a template from the Figma community</a> to save time. Templates are a great option if you don’t want to build everything yourself. But if you prefer full control, creating your setup totally works too.</p>

<p>There are other ways to get tokens as well. For example, a site like <a href="https://namedesigntokens.guide/">namedesigntokens.guide</a> lets you generate and download tokens in formats like JSON. The only catch is that Figma doesn’t let you import JSON directly, so if you go that route, you’ll need to bring in a middle tool like Specify to bridge that gap. It helps sync tokens between Figma, GitHub, and other places.</p>

<p>For this article, though, we’ll keep it simple and stick with Figma. Pick any design system template from the Figma community to get started; there are plenty to choose from.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="480"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png"
			
			sizes="100vw"
			alt="Showing a collection of Figma templates contributed by community members."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/5-collection-figma-templates.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Depending on the template you choose, you’ll get a pre-defined set of tokens that includes colors, typography, spacing, components, and more. These templates come in all types: website, e-commerce, portfolio, app UI kits, you name it. For this article, we’ll be using the <a href="https://www.figma.com/community/file/1055785285964148921"><strong>/Design-System-Template&ndash;Community</strong></a> because it includes most of the tokens you’ll need right out of the box. But feel free to pick a different one if you want to try something else.</p>

<p>Once you’ve picked your template, it’s time to download the tokens. We’ll use <strong>Supernova</strong>, a tool that connects directly to your Figma file and pulls out design tokens, styles, and components. It makes the design-to-code process a lot smoother.</p>

<h3 id="step-1-sign-up-on-supernova">Step 1: Sign Up on Supernova</h3>

<p>Go to <a href="https://supernova.io">supernova.io</a> and create an account. Once you’re in, you’ll land on a dashboard that looks like this:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="373"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png"
			
			sizes="100vw"
			alt="Supernova dashboard in an empty state. There is navigation in the left sidebar and a summary of activity in the main content showing no design tokens, components, assets, or documentation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/6-supernova-dashboard.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="step-2-connect-your-figma-file">Step 2: Connect Your Figma File</h3>

<p>To pull in the tokens, head over to the <strong>Data Sources</strong> section in Supernova and choose <strong>Figma</strong> from the list of available sources. (You’ll also see other options like Storybook or Figma variables, but we’re focusing on Figma.) Next, click on <strong>Connect a new file,</strong> paste the link to your Figma template, and click <strong>Import</strong>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png">
    
    <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/automating-design-systems-tips-resources/7-supernova-figma.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png"
			
			sizes="100vw"
			alt="Supernova dashboard to connect Figma files"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/7-supernova-figma.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Supernova will load the full design system from your template. From your dashboard, you’ll now be able to see all the tokens.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="380"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png"
			
			sizes="100vw"
			alt="Supernova dashboard with tokens"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/8-supernova-figma.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="turning-tokens-into-code">Turning Tokens Into Code</h2>

<p>Design tokens are great inside Figma, but the real value shows when you turn them into code. That’s how the developers on your team actually get to use them.</p>

<p><strong>Here’s the problem</strong>: Many teams default to copying values manually for things like color, spacing, and typography. But when you make a change to them in Figma, the code is instantly out of sync. That’s why automating this process is such a big win.</p>

<p>Instead of rewriting the same theme setup for every project, you generate it, constantly translating designs into dev-ready assets, and keep everything in sync from one source of truth.</p>

<p>Now that we’ve got all our tokens in Supernova, let’s turn them into code. First, go to the <strong>Code Automation</strong> tab, then click <strong>New Pipeline</strong>. You’ll see different options depending on what you want to generate: React Native, CSS-in-JS, Flutter, Godot, and a few others.</p>

<p>Let’s go with the <strong>CSS-in-JS</strong> option for the sake of demonstration:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="383"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png"
			
			sizes="100vw"
			alt="Supernova Code Automation screen showing options for creating a new pipeline that pulls information from other services to produce code documentation."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/9-supernova-code-automation-screen.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>After that, you’ll land on a setup screen with three sections: <strong>Data</strong>, <strong>Configuration</strong>, and <strong>Delivery</strong>.</p>

<h3 id="data">Data</h3>

<p>Here, you can pick a theme. At first, it might only give you “Black” as the option; you can select that or leave it empty. It really doesn’t matter for the time being.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="377"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png"
			
			sizes="100vw"
			alt="Supernova Code Automation screen"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/10-supernova-code-automation-screen.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="configuration">Configuration</h3>

<p>This is where you control how the code is structured. I picked <strong>PascalCase</strong> for how token names are formatted. You can also update how things like spacing, colors, or font styles are grouped and saved.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="380"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png"
			
			sizes="100vw"
			alt="Supernova Code Automation screen showing configuration of tokens"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/11-supernova-code-automation-screen-configuration.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="delivery">Delivery</h3>

<p>This is where you choose how you want the output delivered. I chose <strong>“Build Only”</strong>, which builds the code for you to download.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="375"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png"
			
			sizes="100vw"
			alt="Supernova Code Automation screen where you choose how you want the output delivered"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/12-supernova-code-automation-screen-delivery.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once you’re done, click <strong>Save</strong>. The pipeline is created, and you’ll see it listed in your dashboard. From here, you can download your token code, which is already generated.</p>

<h2 id="automating-documentation">Automating Documentation</h2>

<p>So, what’s the point of documentation in a design system?</p>

<p>You can think of it as the <strong>instruction manual</strong> for your team. It explains <em>what</em> each token or component is, <em>why</em> it exists, and <em>how</em> to use it. Designers, developers, and anyone else on your team can stay on the same page &mdash; no guessing, no back-and-forth. Just clear context.</p>

<p>Let’s continue from where we stopped. Supernova is capable of handling your documentation. Head over to the <strong>Documentation</strong> tab. This is where you can start editing everything about your design system docs, all from the same place.</p>

<p>You can:</p>

<ul>
<li>Add descriptions to your tokens,</li>
<li>Define what each base token is for (as well as what it’s <em>not</em> for),</li>
<li>Organize sections by colors, typography, spacing, or components, and</li>
<li>Drop in images, code snippets, or examples.</li>
</ul>

<p>You’re building the documentation inside the same tool where your tokens live. In other words, there’s no jumping between tools and no additional setup. That’s where the automation kicks in. You edit once, and your docs stay synced with your design source. It all stays in one environment.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.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/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png"
			
			sizes="100vw"
			alt="Supernova Code Automation screen where you automate documentation"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/automating-design-systems-tips-resources/13-supernova-code-automation-screen-documentation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Once you’re done, click <strong>Publish</strong> and you will be presented with a new window asking you to sign in. After that, you’re able to access your live documentation site.</p>

<h2 id="practical-tips-for-automations">Practical Tips For Automations</h2>

<p>Automation is great. It saves hours of manual work and keeps your design system tight across design and code. The trick is knowing when to automate and how to make sure it keeps working over time. You don’t need to automate everything right away. But if you’re doing the same thing over and over again, that’s a kind of red flag.</p>

<p>A few signs that it’s time to consider using automation:</p>

<ul>
<li>You’re using <strong>the same styles across multiple platforms</strong> (like web and mobile).</li>
<li>You have a <strong>shared design system</strong> used by more than one team.</li>
<li><strong>Design tokens change often</strong>, and you want updates to flow into code automatically.</li>
<li>You’re <strong>tired of manual updates</strong> every time the brand team tweaks a color.</li>
</ul>

<p>There are three steps you need to consider. Let’s look at each one.</p>

<h3 id="step-1-keep-an-eye-on-tools-and-api-updates">Step 1: Keep An Eye On Tools And API Updates</h3>

<p>If your pipeline depends on design tools, like Figma, or platforms, like Supernova, you’ll want to know when changes are made and evaluate how they impact your work, because even small updates can quietly affect your exports.</p>

<p>It’s a good idea to check <a href="https://www.figma.com/developers/api#changelog">Figma’s API changelog</a> now and then, especially if something feels off with your token syncing. They often update how variables and components are structured, and that can impact your pipeline. There’s also an <a href="https://www.figma.com/release-notes/">RSS feed for product updates</a>.</p>

<p>The same goes for <a href="https://updates.supernova.io">Supernova’s product updates</a>. They regularly roll out improvements that might tweak how your tokens are handled or exported. If you’re using open-source tools like <a href="https://v4.styledictionary.com">Style Dictionary</a>, keeping an eye on the GitHub repo (particularly the Issues tab) can save you from debugging weird token name changes later.</p>

<p>All of this isn’t about staying glued to release notes, but having a system to check if something suddenly stops working. That way, you’ll catch things before they reach production.</p>

<h3 id="step-2-break-your-pipeline-into-smaller-steps">Step 2: Break Your Pipeline Into Smaller Steps</h3>

<p>A common trap teams fall into is trying to automate <em>everything</em> in one big run: colors, spacing, themes, components, and docs, all processed in a single click. It sounds convenient, but it’s hard to maintain, and even harder to debug.</p>

<p>It’s much more manageable to split your automation into pieces. For example, having a single workflow that handles your core design tokens (e.g., colors, spacing, and font sizes), another for theme variations (e.g., light and dark themes), and one more for component mapping (e.g., buttons, inputs, and cards). This way, if your team changes how spacing tokens are named in Figma, you only need to update one part of the workflow, not the entire system. It’s also <strong>easier to test and reuse smaller steps</strong>.</p>

<h3 id="step-3-test-the-output-every-time">Step 3: Test The Output Every Time</h3>

<p>Even if everything runs fine, always take a moment to check the exported output. It doesn’t need to be complicated. A few key things:</p>

<ul>
<li><strong>Are the token names clean and readable?</strong><br />
If you see something like <code>PrimaryColorColorText</code>, that’s a red flag.</li>
<li><strong>Did anything disappear or get renamed unexpectedly?</strong><br />
It happens more often than you think, especially with typography or spacing tokens after design changes.</li>
<li><strong>Does the UI still work?</strong><br />
If you’re using something like Tailwind, CSS variables, or custom themes, double-check that the new token values aren’t breaking anything in the design or build process.</li>
</ul>

<p>To catch issues early, it helps to run tools like <a href="https://eslint.org">ESLint</a> or <a href="https://stylelint.io">Stylelint</a> right after the pipeline completes. They’ll flag odd syntax or naming problems before things get shipped.</p>

<h2 id="how-ai-can-help">How AI Can Help</h2>

<p>Once your automation is stable, there’s a next layer that can boost your workflow: AI. It’s not just for writing code or generating mockups, but for helping with the small, repetitive things that eat up time in design systems. When used right, AI can assist without replacing your control over the system.</p>

<p>Here’s where it might fit into your workflow:</p>

<h3 id="naming-suggestions">Naming Suggestions</h3>

<p>When you’re dealing with hundreds of tokens, naming them clearly and consistently is a real challenge. Some AI tools can help by suggesting clean, readable names for your tokens or components based on patterns in your design. It’s not perfect, but it’s a good way to kickstart naming, especially for large teams.</p>

<h3 id="pattern-recognition">Pattern Recognition</h3>

<p>AI can also spot repeated styles or usage patterns across your design files. If multiple buttons or cards share similar spacing, shadows, or typography, tools powered by AI can group or suggest components for systemization even before a human notices.</p>

<h3 id="automated-documentation">Automated Documentation</h3>

<p>Instead of writing everything from scratch, AI can generate first drafts of documentation based on your tokens, styles, and usage. You still need to review and refine, but it takes away the blank-page problem and saves hours.</p>

<p>Here are a few tools that already bring AI into the design and development space in practical ways:</p>

<ul>
<li><a href="https://uizard.io/"><strong>Uizard</strong></a>: Uizard uses AI to turn wireframes into designs automatically. You can sketch something by hand, and it transforms that into a usable mockup.</li>
<li><a href="https://www.animaapp.com/"><strong>Anima</strong></a>: Anima can convert Figma designs into responsive React code. It also helps fill in real content or layout structures, making it a powerful bridge between design and development, with some AI assistance under the hood.</li>
<li><a href="https://www.builder.io/"><strong>Builder.io</strong></a>: Builder uses AI to help generate and edit components visually. It&rsquo;s especially useful for marketers or non-developers who need to build pages fast. AI helps streamline layout, content blocks, and design rules.</li>
</ul>

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

<p>This article is not about achieving complete automation in the technical sense, but more about using <strong>smart tools to streamline the menial and manual aspects of working with design systems</strong>. Exporting tokens, generating docs, and syncing design with code can be automated, making your process quicker and more reliable with the right setup.</p>

<p>Instead of rebuilding everything from scratch every time, you now have a way to keep things consistent, stay organized, and save time.</p>

<h3 id="further-reading">Further Reading</h3>

<ul>
<li>“<a href="https://thedesignsystem.guide/">Design System Guide</a>” by Romina Kavcic</li>
<li>“<a href="https://www.smashingmagazine.com/2025/05/design-system-in-90-days/">Design System In 90 Days</a>” by Vitaly Friedman</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>Zareen Tasnim</author><title>Droip: The Modern Website Builder WordPress Needed</title><link>https://www.smashingmagazine.com/2025/07/modern-website-builder-wordpress-droip/</link><pubDate>Tue, 08 Jul 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/07/modern-website-builder-wordpress-droip/</guid><description>Traditional page builders have shaped how we build WordPress sites for years. Let’s take a closer look at &lt;a href="https://droip.com/">Droip&lt;/a>, a modern, no-code visual builder, and explore how it redefines the experience with cleaner performance, full design freedom, and zero plugin dependency.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/07/modern-website-builder-wordpress-droip/" />
              <title>Droip: The Modern Website Builder WordPress Needed</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Droip: The Modern Website Builder WordPress Needed</h1>
                  
                    
                    <address>Zareen Tasnim</address>
                  
                  <time datetime="2025-07-08T10:00:00&#43;00:00" class="op-published">2025-07-08T10:00:00+00:00</time>
                  <time datetime="2025-07-08T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Droip</b></p>
                

<p>Traditional WordPress page builders had their moment. Builders like Elementor, Divi, and Oxygen have been around for years. So long, in fact, that many of us just accepted their limitations as the cost of using WordPress.</p>

<p>But Droip, a relatively new no-code website builder, steps in with a completely different philosophy. It is built to provide Webflow and Framer-level power in WordPress, complete design freedom, built-in performance, and no reliance on third-party plugins.</p>

<p>In this review, we’re putting Droip head-to-head with traditional builders according to all the things that matter when choosing a website builder:</p>

<ul>
<li>Price,</li>
<li>Affect on website performance,</li>
<li>User-friendliness vs flexibility,</li>
<li>Features,</li>
<li>Theme and layout options.</li>
</ul>

<h2 id="what-is-droip">What Is Droip?</h2>

<p><a href="https://droip.com/">Droip</a> is a no-code visual website builder for WordPress, designed to bridge the gap where other page builders fall short.</p>

<p>Unlike other page builders, Droip is an all-in-one solution that aims to provide everything you need to build websites without any third-party dependencies, shifting from the norm in WordPress!</p>

<p>And the best part? It’s all included in your subscription, so you won’t be hit with surprise upgrades.</p>

<h2 id="pricing-a-smarter-investment-with-all-features-included">Pricing: A Smarter Investment with All Features Included</h2>

<p>While most page builders upsell critical features or require multiple add-ons, Droip keeps it simple: one platform, all features, no hidden costs.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg"
			
			sizes="100vw"
			alt="Screenshot displaying Droip’s pricing plans and features."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Overview of Droip’s affordable and transparent pricing plans. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/1-droip-pricing-plans.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It’s surprisingly affordable for the value it delivers. The Starter plan is just $34.50/year (currently at 50% off) for one site and includes all premium features.</p>

<p>If you compare it with Elementor, that’s almost half the cost of Elementor Pro’s Essential plan, which starts at $60/year and still keeps several essentials behind paywalls.</p>

<p>Droip also has a Lifetime plan. For a one-time payment of $299.50, you get unlimited use, forever. No renewals, no upcharges.</p>

<p>All Droip Pro plans are fully featured from the start. You don’t need to stack plugins or pay extra to unlock dynamic content support, pop-up builders, or submission forms. You also get access to the entire growing template library from day one.</p>

<p><strong>Note</strong>: <em>Explore <a href="https://droip.com/pricing/">Droip pricing</a>.</em></p>

<h2 id="website-performance-comparison">Website Performance Comparison</h2>

<p>Performance directly impacts user experience, SEO, and conversion rates. So, to get a clear picture of how different page builders impact performance, we put Droip and Elementor to the test under identical conditions to see how each builder stacks up.</p>

<p>We installed both on a clean WordPress setup using the default Twenty Twenty-Five theme to ensure a fair comparison. Then, we created identical layouts using comparable design elements and ran Lighthouse performance audits to measure load time, responsiveness, and Core Web Vitals.</p>

<p><strong>Test Conditions:</strong></p>

<ul>
<li>Clean WordPress installation.</li>
<li>Same theme: Twenty Twenty-Five.</li>
<li>Same layout structure and design elements.</li>
<li>Lighthouse is used for performance scoring.</li>
</ul>

<p><strong>Sample Layout</strong></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg"
			
			sizes="100vw"
			alt="Image of a website layout built for performance testing comparison."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Sample website layout used for benchmarking performance tests. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/2-website-layout-built-pperformance-testing-comparison.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Droip’s Performance</strong></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg"
			
			sizes="100vw"
			alt="Performance test scores showing Droip’s website speed and responsiveness."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Performance results showing Droip’s fast loading times and metrics. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/3-droip-performance-test-scores.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Elementor’s Performance</strong></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg"
			
			sizes="100vw"
			alt="Performance test scores showing Elementor’s website speed and responsiveness."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Performance results illustrating Elementor’s load times and metrics. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/4-elementor-performance.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Droip’s Code Output</strong></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="469"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png"
			
			sizes="100vw"
			alt="Screenshot of Droip’s clean semantic HTML code structure."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Clean and minimal code output generated by Droip for optimized websites. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/5-droip-code-output.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Elementor’s Code Output</strong></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png"
			
			sizes="100vw"
			alt="Screenshot of Elementor’s complex and heavily nested HTML code."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Nested and bulky code output produced by Elementor, affecting the performance. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/6-elementor-code-output.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The difference was immediately clear. Droip generated a much cleaner DOM with significantly fewer <code>&lt;div&gt;</code>s and no unnecessary wrappers, resulting in faster load times and higher scores across all boards.</p>

<p>Elementor, on the other hand, added heavily nested markup and extra scripts, even on this simple layout, which dragged down its performance.</p>

<p>If clean code, fast loading, and technical efficiency are priorities for you, Droip clearly comes out ahead.</p>

<h2 id="exploring-the-features">Exploring The Features</h2>

<p>Now that we’ve seen how Droip outperforms the competition and does it at a highly competitive price, let’s dive into the features to see what makes it such a powerful all-in-one builder.</p>

<h3 id="freeform-visual-canvas-for-true-design-freedom">Freeform Visual Canvas For True Design Freedom</h3>

<p>What makes Droip different from the existing page builders is its <a href="https://droip.com/editor/">freeform visual canvas</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg"
			
			sizes="100vw"
			alt="Interface screenshot of Droip’s drag-and-drop freeform visual canvas."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Droip’s flexible freeform canvas enables pixel-perfect design freedom. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/7-freeform-visual-canvas.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>With Droip, you finally get the layout flexibility modern design demands and no longer need to place elements into rigid structures.</p>

<p>The editor is powerful, modern, and feels more like designing in a modern interface tool like Figma.</p>

<p>You can place elements exactly where you want, overlap sections, layer backgrounds, and create complex animations &amp; interactions all visually. Every element’s layout behavior is editable on canvas, giving you pixel-level control without touching code.</p>

<p>The editor supports both light and dark modes for a more comfortable, focused workspace.</p>

<p>If you&rsquo;ve used Figma or Webflow, you&rsquo;ll feel instantly at home. If you haven&rsquo;t, this is the most natural way to design websites you&rsquo;ve ever tried.</p>

<h3 id="instant-figma-to-droip-handoff">Instant Figma to Droip Handoff</h3>

<p>Talking about Figma, if you have a design ready in Figma, you can instantly import it into Droip to a functional website with no need to rebuild from scratch.</p>

<figure><a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/8-instant-figma-droip-handoff.gif"><img src="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/8-instant-figma-droip-handoff-800px.gif" width="800" height="387" alt="Screencast showing Figma design being imported into Droip builder." /></a><figcaption>Seamless import of Figma designs directly into Droip for fast development. (<a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/8-instant-figma-droip-handoff.gif">Large preview</a>)</figcaption></figure>

<p>Your imported design comes in fully responsive by default, adapting to all screen sizes, including any custom breakpoints you define.</p>

<p>And it supports unlimited breakpoints, too. You can define layout behavior exactly how you want it, and styles will cascade intelligently across smaller screens.</p>

<h3 id="no-third-party-plugins-needed-for-dynamic-content">No Third-Party Plugins Needed For Dynamic Content</h3>

<p>In traditional WordPress, handling dynamic content means installing the ACF or other third-party plugins.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg"
			
			sizes="100vw"
			alt="Image showing Droip’s dynamic content management capabilities."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Droip’s native dynamic content management without third-party plugins. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/9-droip-native-dynamic-content-management.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>But with Droip, all of that is natively integrated. It comes with a powerful <a href="https://droip.com/content-manager/">Dynamic Content Manager</a> that lets you:</p>

<ul>
<li>Create custom content types and fields.</li>
<li>Use reference and multi-reference relationships.</li>
<li>Build dynamic templates visually.</li>
<li>Add dynamic SEO to template pages.</li>
<li>Apply advanced filtering to Collection elements.</li>
</ul>

<p>All without writing a single line of code or relying on external plugins.</p>

<h3 id="reusable-styling-with-class-based-editing">Reusable Styling With Class-Based Editing</h3>

<p>Droip also has an efficient way to manage design at scale without repetitive work.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg"
			
			sizes="100vw"
			alt="Screenshot of Droip’s CSS class management panel."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Efficient class-based styling system for reusable and scalable designs. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/10-droip-css-class-management-panel.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It uses a class-based styling system that brings structure and scalability to your design process. When you style an element, those styles are automatically saved as reusable CSS classes.</p>

<p>Here’s what that means for you:</p>

<ul>
<li>You can create global classes for common components like buttons, cards, or headings.</li>
<li>Reuse those styles across pages and projects with consistency.</li>
<li>Update a class once, and every instance updates instantly.</li>
<li>You can also create subclasses to make slight variations, like secondary buttons, while still inheriting styles from the parent.</li>
</ul>

<h3 id="css-variables-for-global-styling">CSS Variables For Global Styling</h3>

<p>Droip takes styling even further with <a href="https://droip.com/docs/variables/">Global Variables</a>, allowing you to define design tokens like colors, fonts, spacing, and sizing that can be reused across your entire site.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg"
			
			sizes="100vw"
			alt="Interface displaying CSS variables used for colors, fonts, and spacing in Droip."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Global CSS variables powering consistent and easy-to-update site styling. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/11-global-css-variables.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can pair these global variables with your class-based structure to:</p>

<ul>
<li>Maintain visual consistency;</li>
<li>Update values globally with a single change;</li>
<li>Easily manage themes like switching between light and dark modes with one click.</li>
</ul>

<p>And while Droip offers a fully visual experience, it doesn’t limit advanced users. You can write custom CSS for any class or element, and even inject JavaScript at the page or element level when needed.</p>

<h3 id="build-complex-interactions-and-animations-visually">Build Complex Interactions and Animations Visually</h3>

<p>When it comes to modern animations and interactive design, Droip leaves traditional WordPress page builders far behind.</p>

<p>Its fully <a href="https://droip.com/interactions/">visual interaction builder</a> lets you create dynamic, immersive experiences.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg"
			
			sizes="100vw"
			alt="Image of Droip’s timeline-based animation and interaction editor."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Visual interaction builder allowing advanced animations without coding. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/12-visual-interaction-builder.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can build scroll-based animations, hover and click effects, interactive sections that respond across devices, and control visibility, motion, and behavior all within a visual interface.</p>

<p>For advanced users, Droip includes a <strong>timeline-based editor</strong> where you can:</p>

<ul>
<li>Create multi-step animations;</li>
<li>Fine-tune transitions with precise timing, easing, delays, and sequencing.</li>
</ul>

<p>Even text animations get special attention.</p>

<p>You can animate text by character, word, or full element. Choose custom triggers (scroll, hover, load, and so on) and select from various transition styles or create your own.</p>

<p>Droip&rsquo;s no-code website builder truly helps you move past generic and create unique animations and complex interactions.</p>

<h3 id="seamless-integration-management-with-droip-apps">Seamless Integration Management With Droip Apps</h3>

<p>Droip takes the hassle out of connecting third-party tools with its intuitive <strong>Droip Apps</strong> system. You can install and manage essential integrations such as analytics, CRMs, email marketing platforms, support widgets, and more, all from within the Droip editor itself.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg"
			
			sizes="100vw"
			alt="Droip Apps interface showing available integrations for analytics, CRM, and more."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Centralized integrations management with Droip Apps. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/13-integration-management-droip-apps.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>This centralized approach means you never have to leave your workspace. The clean, user-friendly interface guides you through the connection process visually, making setup fast and straightforward even if you’re not a technical expert.</p>

<h3 id="accessibility-is-core-to-the-experience">Accessibility Is Core To The Experience</h3>

<p>One of Droip’s standout features is its built-in focus on accessibility from day one.</p>

<p>Unlike many platforms that rely on third-party plugins for accessibility, Droip integrates it directly into the core experience.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg">
    
    <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/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg"
			
			sizes="100vw"
			alt="Interface highlighting accessibility settings like magnifier, contrast, etc."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Accessibility features built into Droip’s core for inclusive web design. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/14-accessibility-settings.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Whether you need to enlarge editor text, reduce motion effects, use a larger cursor, or work with color-blind–friendly palettes, Droip ensures an <a href="https://droip.com/accessibility/">inclusive editing environment</a>.</p>

<p>But it doesn’t stop at editor settings. Droip actively helps you follow best accessibility practices, enforcing semantic HTML, prompting for proper alt text, and supporting ARIA labels. Plus, its built-in contrast checker ensures your designs aren’t just visually appealing, they’re easy to read and use for everyone.</p>

<h3 id="team-collaboration-made-easy">Team Collaboration Made Easy</h3>

<p>Collaboration is also a core part of the experience, thoughtfully designed to support teams, clients, and developers alike. With Droip’s Role Manager, you can define exactly what each role can view, edit, or manage within the builder.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg"
			
			sizes="100vw"
			alt="Screenshot of Droip’s role management and collaboration tools."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Role Manager and view-only links simplifying team workflows and client reviews. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/15-droip-role-management-collaboration-tools.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can assign custom roles to team members based on their responsibilities, like designers, developers, content editors, clients, and so on.</p>

<p>For handling client reviews, it also generates a shareable view-only link that gives clients access to preview the site without giving them edit permissions or exposing the backend. Perfect for gathering feedback and approvals while maintaining full control.</p>

<h3 id="built-in-quality-control">Built-in Quality Control</h3>

<p>Before you publish your site, Droip helps ensure your site is technically sound with its built-in Page Audit tool.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg"
			
			sizes="100vw"
			alt="Interface showing Droip’s quality control, including alt text and broken links."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Page Audit tool ensuring SEO, accessibility, and technical quality before publishing. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/16-droip-quality-control.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It automatically scans your layout for:</p>

<ul>
<li>Missing alt text on images,</li>
<li>Broken links,</li>
<li>Unassigned or duplicate classes,</li>
<li>Accessibility issues,</li>
<li>And more.</li>
</ul>

<p>So you’re not just building beautiful pages, you’re shipping fast, accessible, SEO-ready websites with confidence.</p>

<h2 id="theme-layout-options">Theme &amp; Layout Options</h2>

<p>Droip has a growing library of high-quality templates and modular layout options, so you’re never out of options.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg"
			
			sizes="100vw"
			alt="Preview of Droip’s template library and layout options."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Extensive library of templates, pre-designed pages, and modular sections. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/17-droip-template-library.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="template-kits-full-website-packs">Template Kits: Full Website Packs</h3>

<p>Droip’s Template Kits include complete multi-page website designs for every industry. Pick a template, update the content, and you’re ready to launch.</p>

<p>New template kits are added regularly, so you&rsquo;re always equipped with the latest design trends. And the best part? At no additional cost. You get access to the finest designs without ever paying extra.</p>

<h3 id="pre-designed-pages">Pre-Designed Pages</h3>

<p>Do you need just a landing page or a pricing page? Droip also offers standalone pre-designed pages you can drop into your project and customize instantly.</p>

<h3 id="pre-made-sections">Pre-Made Sections</h3>

<p>Prefer to build from scratch but don’t want to start with a blank canvas? It also has ready-made sections like hero banners, testimonials, pricing blocks, and FAQs. You can visually assemble your layout in minutes using these.</p>

<h3 id="wireframes">Wireframes</h3>

<p>You can also map out your layout using wireframes before applying any styling. It’s a great way to get your content and structure right without distractions, perfect for planning UX and content flow.</p>

<h2 id="how-easy-is-droip-to-use">How Easy Is Droip to Use?</h2>

<p>If you want something dead simple and just need to build a basic site fast, there are other options like Elementor that can do that, but at the cost of power, performance, and flexibility.</p>

<p>Droip, on the other hand, has a bit of a learning curve. That’s because it’s way more powerful and is built for those who care about design control, clean output, and scalability.</p>

<p>If you’re someone who wants to fine-tune every pixel, build advanced layouts, and doesn’t mind a learning curve, you’ll appreciate the level of control it offers.</p>

<p>Having said that, it’s not hard to use once you understand how it works.</p>

<p>The learning curve, especially for complete beginners, mostly comes from understanding its powerful features like dynamic content, reusable components (called Symbols), styling logic using classes, global variables, and breakpoints, advanced interactions using custom animation timelines, etc.</p>

<p>But to help you get up to speed quickly, Droip includes:</p>

<ul>
<li>Guided onboarding to walk you through the essentials.</li>
<li>A growing <a href="https://droip.com/themes/">library of templates</a>, pages, UI components, and wireframes to kickstart your projects.</li>
<li>An AI Generator that can scaffold entire pages and layouts in seconds.</li>
<li>Detailed <a href="https://droip.com/docs/system-requirements/">documentation</a> and <a href="https://www.youtube.com/@DroipNoCode">video tutorials</a> (with more added regularly).</li>
</ul>

<h2 id="what-users-are-saying">What Users Are Saying</h2>

<p>For many users, Droip is more than just a builder. It’s the all-in-one tool WordPress has been waiting for. They are calling it the future of WordPress, a truly great alternative to tools like Framer and Webflow.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="512"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg"
			
			sizes="100vw"
			alt="Image showing customer reviews and feedback on Droip’s website builder."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      User testimonials praising Droip’s power, performance, and design freedom. (<a href='https://files.smashing.media/articles/creating-more-than-wordpress-page-builders-droip/18-droip-customer-reviews-feedback.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="tl-dr-why-droip-outshines-traditional-builders">TL;DR: Why Droip Outshines Traditional Builders</h2>

<ul>
<li>All-in-one builder with no third-party bloat.</li>
<li>Clean, performance-optimized code output.</li>
<li>Figma integration + modern visual canvas.</li>
<li>Dynamic content, advanced interactions, and global styling.</li>
<li>One price, all features, no hidden costs.</li>
</ul>

<h2 id="overall-verdict-is-droip-really-better-than-alternatives">Overall Verdict: Is Droip Really Better Than Alternatives?</h2>

<p>After putting Droip through its paces, the answer is a clear <strong>yes</strong>. Droip not only matches traditional WordPress page builders where it counts, but it surpasses them in nearly every critical area.</p>

<p>From its cleaner, faster code output and outstanding performance to its unparalleled design freedom and powerful built-in features, Droip solves many of the pain points that users have accepted for years. Its all-in-one approach eliminates the need for multiple plugins, saving time, money, and technical headaches.</p>

<p>While there is a learning curve for beginners, the payoff is huge for those who want full control, scalability, and a truly modern web design experience inside WordPress.</p>

<p>If you’re serious about building high-quality, scalable, and visually stunning websites, Droip isn’t just an alternative; it’s the future of WordPress site building.</p>

<p>Ready to experience the difference yourself? Try <a href="https://droip.com/">Droip</a> today and start building faster, cleaner, and smarter.</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>(il, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Vitaly Friedman</author><title>Design System In 90 Days</title><link>https://www.smashingmagazine.com/2025/05/design-system-in-90-days/</link><pubDate>Mon, 19 May 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/design-system-in-90-days/</guid><description>Helpful PDF worksheets and tools to get the design system effort up and running — and adopted! Kindly powered by &lt;a href="https://measure-ux.com">How To Measure UX and Design Impact&lt;/a>, a friendly course on how to show the impact of your incredible UX work on business.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/design-system-in-90-days/" />
              <title>Design System In 90 Days</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Design System In 90 Days</h1>
                  
                    
                    <address>Vitaly Friedman</address>
                  
                  <time datetime="2025-05-19T10:00:00&#43;00:00" class="op-published">2025-05-19T10:00:00+00:00</time>
                  <time datetime="2025-05-19T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>So we want to set up a new design system for your product. How do we get it up and running from scratch? Do we start with key stakeholders, UI audits, or naming conventions? And what are some of the <strong>critical conversations</strong> we need to have early to avoid problems down the line?</p>

<p>Fortunately, there are a few <strong>useful little helpers</strong> to get started &mdash; and they are the ones I tend to rely on quite a bit when initiating any design system projects.</p>

<h2 id="design-system-in-90-days-canvas">Design System In 90 Days Canvas</h2>

<p><a href="https://www.figma.com/community/file/1275210165201477676"><strong>Design System in 90 Days Canvas (FigJam template)</strong></a> is a handy set of <strong>useful questions</strong> to start a design system effort. Essentially, it’s a roadmap to discuss everything from the value of a design system to stakeholders, teams involved, and components to start with.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.figma.com/community/file/1275210165201477676/design-system-in-90-days-canvas">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="1002"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mailing-91-design-system-90-days/1-mailing-91-design-system-90-days.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mailing-91-design-system-90-days/1-mailing-91-design-system-90-days.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mailing-91-design-system-90-days/1-mailing-91-design-system-90-days.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mailing-91-design-system-90-days/1-mailing-91-design-system-90-days.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mailing-91-design-system-90-days/1-mailing-91-design-system-90-days.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mailing-91-design-system-90-days/1-mailing-91-design-system-90-days.jpg"
			
			sizes="100vw"
			alt="Design System in 90 Days Canvas"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Very comprehensive and helpful: <a href='https://www.figma.com/community/file/1275210165201477676/design-system-in-90-days-canvas'>Design System in 90 Days Canvas</a> by Dan Mall.
    </figcaption>
  
</figure>

<p>A neat little helper to get a design system up and running &mdash; and adopted! &mdash; in 90 days. Created for small and large companies that are building a design system or plan to set up one. Kindly shared by Dan Mall.</p>

<h2 id="practical-design-system-tactics">Practical Design System Tactics</h2>

<p><a href="https://redesigningdesign.systems/tactics/all-tactics"><strong>Design System Tactics</strong></a> is a practical overview of tactics to help designers <strong>make progress with a design system at every stage</strong> &mdash; from crafting system principles to component discovery to design system office hours to cross-brand consolidation. Wonderful work by the one-and-only Ness Grixti.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://redesigningdesign.systems/tactics/all-tactics">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="968"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mailing-91-design-system-90-days/2-mailing-91-design-system-90-days.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mailing-91-design-system-90-days/2-mailing-91-design-system-90-days.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mailing-91-design-system-90-days/2-mailing-91-design-system-90-days.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mailing-91-design-system-90-days/2-mailing-91-design-system-90-days.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mailing-91-design-system-90-days/2-mailing-91-design-system-90-days.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mailing-91-design-system-90-days/2-mailing-91-design-system-90-days.jpg"
			
			sizes="100vw"
			alt="An overview of practical design system tactics displayed as cards"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://redesigningdesign.systems/tactics/all-tactics'>Design System Tactics</a>, a practical overview by Ness Grixti.
    </figcaption>
  
</figure>

<h2 id="design-system-worksheet-pdf">Design System Worksheet (PDF)</h2>

<p><a href="https://medium.com/eightshapes-llc/picking-parts-products-people-a06721e81742"><strong>Design System Checklist</strong></a> by Nathan Curtis (<a href="https://drive.google.com/file/d/1qXMUXKHaEXnLDOu99GCzTMY2XW6NnPe_/view">download the PDF</a>) is a <strong>practical 2-page worksheet</strong> for a 60-minute team activity, designed to choose the right parts, products, and people for your design system.</p>














<figure class="
  
  
  ">
  
    <a href="https://medium.com/eightshapes-llc/picking-parts-products-people-a06721e81742">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="1011"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mailing-91-design-system-90-days/3-mailing-91-design-system-90-days.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/mailing-91-design-system-90-days/3-mailing-91-design-system-90-days.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/mailing-91-design-system-90-days/3-mailing-91-design-system-90-days.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/mailing-91-design-system-90-days/3-mailing-91-design-system-90-days.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/mailing-91-design-system-90-days/3-mailing-91-design-system-90-days.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/mailing-91-design-system-90-days/3-mailing-91-design-system-90-days.jpg"
			
			sizes="100vw"
			alt="Design System Parts Worksheet"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://medium.com/eightshapes-llc/picking-parts-products-people-a06721e81742'>Design System Worksheet</a>, by Nathan Curtis.
    </figcaption>
  
</figure>

<p>Of course, the point of a design system is not to be fully comprehensive or cover every possible component you might ever need. It’s all about <strong>being useful enough</strong> to help designers produce quality work faster and being flexible enough to help designers make decisions rather than make decisions for them.</p>

<h2 id="useful-questions-to-get-started-with">Useful Questions To Get Started With</h2>

<p>The value of a design system lies in it <strong>being useful and applicable</strong> &mdash; for a large group of people in the organization. And <a href="https://www.linkedin.com/pulse/26-design-system-questions-answer-design-system-university/">according to Dan</a>, a good start is to identify where exactly that value would be most helpful to tackle the company’s <strong>critical challenges and goals</strong>:</p>

<ol>
<li>What is <strong>important to our organization</strong> at the highest level?</li>
<li>Who is important to our design system effort?</li>
<li>What unofficial systems already exist in design and code?</li>
<li>Which teams have <strong>upcoming needs</strong> that a system could solve?</li>
<li>Which teams have <strong>immediate needs</strong> that can grow our system?</li>
<li>Which teams should we and have we talked to?</li>
<li>Which <strong>stakeholders</strong> should we and have we talked to?</li>
<li>What <strong>needs, desires, and concerns</strong> do our stakeholders have?</li>
<li>What components do product or feature teams need now or soon?</li>
<li>What <strong>end-user problems/opportunities</strong> could a system address?</li>
<li>What did we learn about using other design systems?</li>
<li>What is our <strong>repeatable process</strong> for working on products?</li>
<li>What components will we start with?</li>
<li>What needs, desires, and concerns do our stakeholders share?</li>
<li>Where are <strong>our components</strong> currently being used or planned for?</li>
</ol>

<h2 id="useful-resources">Useful Resources</h2>

<p>Here are a few other useful little helpers that might help you in your design system efforts:</p>

<ul>
<li><a href="https://www.linkedin.com/pulse/26-design-system-questions-answer-design-system-university/">Design System Questions To Answer In First 90 Days</a>, by Dan Mall</li>
<li><a href="https://designsystemcanvas.com/">Design System Canvas (PDF / Figjam)</a>, by Paavan Buddhdev</li>
<li><a href="https://www.figma.com/community/file/1360306476090347707/leands-framework">LeanDS Framework (Figma)</a>, by Marianne Ashton-Booth</li>
<li><a href="https://www.linkedin.com/posts/vitalyfriedman_ux-design-figma-activity-7262738783358304257-uooc/">Useful UX Templates For Designers (Figma Kits)</a>, by yours truly, Vitaly Friedman</li>
<li><a href="https://thedesignsystem.guide/">Design System Guide</a>, by Romina Kavcic</li>
</ul>

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

<p>A canvas often acts as a <strong>great conversation starter</strong>. It’s rarely complete, but it brings up topics and issues that one wouldn’t have discovered on the spot. We won’t have answers to all questions right away, but we can start <strong>moving in the right direction</strong> to turn a design system effort into a success.</p>

<p>Happy crossing off the right tick boxes!</p>

<h2 id="how-to-measure-ux-and-design-impact">How To Measure UX And Design Impact</h2>

<p>Meet <a href="https://measure-ux.com/">Measure UX &amp; Design Impact</a> (8h), a <strong>practical guide for designers and UX leads</strong> to shape, measure, and explain your incredible UX impact on business. Recorded and updated by Vitaly Friedman. Use the friendly code 🎟 <strong><code>IMPACT</code></strong> to <strong>save 20% off</strong> today. <a href="https://measure-ux.com">Jump to the details</a>.</p>

<figure style="margin-bottom:0;padding-bottom:0" class="break-out article__image">
    <a href="https://measure-ux.com/" title="How To Measure UX and Design Impact, with Vitaly Friedman">
    <img width="900" height="466" style="border-radius: 11px" src="https://files.smashing.media/articles/ux-metrics-video-course-release/measure-ux-and-design-impact-course.png" alt="How to Measure UX and Design Impact, with Vitaly Friedman.">
    </a>
</figure>

<div class="book-cta__inverted"><div class="book-cta" data-handler="ContentTabs" data-mq="(max-width: 480px)"><nav class="content-tabs content-tabs--books"><ul><li class="content-tab"><a href="#"><button class="btn btn--small btn--white btn--white--bordered">
Video + UX Training</button></a></li><li class="content-tab"><a href="#"><button class="btn btn--small btn--white btn--white--bordered">Video only</button></a></li></ul></nav><div class="book-cta__col book-cta__hardcover content-tab--content"><h3 class="book-cta__title"><span>Video + UX Training</span></h3><span class="book-cta__price"><span><span class=""><span class="currency-sign">$</span>&nbsp;<span>495<span class="sup">.00</span></span></span> <span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>799<span class="sup">.00</span></span></span></span></span>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3081832?price_id=3951439" class="btn btn--full btn--medium btn--text-shadow">
Get Video + UX Training<div></div></a><p class="book-cta__desc">25 video lessons (8h) + <a href="https://smashingconf.com/online-workshops/workshops/vitaly-friedman-impact-design/">Live UX Training</a>.<br>100 days money-back-guarantee.</p></div><div class="book-cta__col book-cta__ebook content-tab--content"><h3 class="book-cta__title"><span>Video only</span></h3><div data-audience="anonymous free supporter" data-remove="true"><span class="book-cta__price" data-handler="PriceTag"><span><span class=""><span class="currency-sign">$</span>&nbsp;<span>250<span class="sup">.00</span></span></span><span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>395<span class="sup">.00</span></span></span></span></div>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3081832?price_id=3950630" class="btn btn--full btn--medium btn--text-shadow">
Get the video course<div></div></a><p class="book-cta__desc" data-audience="anonymous free supporter" data-remove="true">25 video lessons (8h). Updated yearly.<br>Also available as a <a href="https://smart-interface-design-patterns.thinkific.com/enroll/3082557?price_id=3951421">UX Bundle with 2 video courses.</a></p></div><span></span></div></div>

<h3 id="further-reading-on-smashing-magazine">Further Reading on Smashing Magazine</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2024/07/build-design-systems-penpot-components/">Build Design Systems With Penpot Components</a>,” Mikołaj Dobrucki</li>
<li>“<a href="https://www.smashingmagazine.com/2025/04/anima-playground-figma-designs-live-apps/">How To Turn Your Figma Designs Into Live Apps With Anima Playground</a>,” Anima Team</li>
<li>“<a href="https://www.smashingmagazine.com/2025/04/ux-design-files-organization-template/">UX And Design Files Organization Template</a>,” Vitaly Friedman</li>
<li>“<a href="https://www.smashingmagazine.com/2025/01/digital-playbook-crucial-counterpart-design-system/">The Digital Playbook: A Crucial Counterpart To Your Design System</a>,” Paul Boag</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>(mrn, yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Anima Team</author><title>How To Turn Your Figma Designs Into Live Apps With Anima Playground</title><link>https://www.smashingmagazine.com/2025/04/anima-playground-figma-designs-live-apps/</link><pubDate>Tue, 29 Apr 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/04/anima-playground-figma-designs-live-apps/</guid><description>As designers, it’s important to be able to transform visual ideas into concepts within minutes and into fully functional products within hours. Well, today we’re bringing you closer to AnimaApp, an app designed to make your life easier &amp;mdash; whether you’re a designer, developer, product team member or entrepreneur.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/04/anima-playground-figma-designs-live-apps/" />
              <title>How To Turn Your Figma Designs Into Live Apps With Anima Playground</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>How To Turn Your Figma Designs Into Live Apps With Anima Playground</h1>
                  
                    
                    <address>Anima Team</address>
                  
                  <time datetime="2025-04-29T10:00:00&#43;00:00" class="op-published">2025-04-29T10:00:00+00:00</time>
                  <time datetime="2025-04-29T10:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Anima App</b></p>
                

<p>For years, designers and developers have been stuck in a frustrating loop. Designers create stunning UIs in Figma, only for developers to spend hours &mdash; or days &mdash; coding them from scratch. Along the way, details get lost, tweaks pile up, and before you know it, the whole process turns into a never-ending back-and-forth.</p>

<p>It’s a tale as old as modern product teams: pixel-perfect designs turned into imperfect realities, timelines stretched by repetitive tasks, and collaboration slowed by tool mismatches. Designers work in one world, developers in another &mdash; and the bridge between them has always been shaky at best.</p>

<p>But what if you could just… skip the painful part?</p>

<p>That’s where <a href="https://dev.animaapp.com/">Anima Playground</a> comes in. It’s a tool that transforms your Figma designs into fully functional web apps automatically. No more pixel-matching marathons, no more manual UI rebuilding. Just a smoother, faster way to go from a design to a live product &mdash; with AI doing the heavy lifting.</p>

<h2 id="what-is-anima-playground">What Is Anima Playground?</h2>

<p>Anima Playground is an AI-powered development environment that makes the jump from design to code seamless. It turns your Figma designs into clean, editable, and production-ready React components &mdash; instantly. And unlike static design-to-code tools of the past, this one goes further: it lets you add business logic, connect to APIs, and preview real-time changes right inside the playground.</p>

<p>In short: it&rsquo;s not just a handoff tool. It&rsquo;s where design becomes a working app.</p>

<p><strong>Here’s what you can do with Anima Playground:</strong></p>

<ul>
<li>Import Figma designs exactly as they were created &mdash; layouts, styles, responsiveness, and all.</li>
<li>Generate React components instantly, with support for libraries like MUI and shadcn/ui.</li>
<li>Use AI prompts to add logic &mdash; from button clicks to dynamic lists and form validation.</li>
<li>Customize everything, with full code access and live previews.</li>
</ul>

<h2 id="how-it-works">How It Works</h2>

<p>Easily sync your Figma designs with Anima Playground. All it takes is four quick steps.</p>

<h3 id="1-import-your-figma-designs">1. Import Your Figma Designs</h3>

<p>No clunky exports, no third-party converters. Just paste your Figma link, and Anima syncs it directly. It preserves layout, typography, responsiveness, and component structure, exactly as designed.</p>

<p>This step sets the foundation: Anima translates your Figma layers into React code, respecting design fidelity down to the pixel. Designers can rest easy knowing their UI won’t get “lost in translation.”</p>

<h3 id="2-convert-designs-into-react-components">2. Convert Designs Into React Components</h3>

<p>Once imported, your Figma designs are instantly transformed into React components. This includes:</p>

<ul>
<li>Clean JSX structure</li>
<li>Tailwind, MUI, or shadcn/ui styling (you choose!)</li>
<li>Nested component trees</li>
<li>Auto-handling of responsive layouts</li>
</ul>

<p>You can switch between UI libraries with a simple prompt or setting change &mdash; no need to rewrite everything manually. Whether you&rsquo;re building a startup landing page or a complex dashboard, the output is dev-ready and easy to extend.</p>

<h3 id="3-add-logic-with-ai-powered-prompts">3. Add Logic With AI-Powered Prompts</h3>

<p>Want a button to open a modal? Or a form that sends data to an API? You don’t need to write all that boilerplate yourself.</p>

<p>Just describe what you want using natural language &mdash; for example:</p>

<blockquote><p>“Make this button open a signup modal.”</p></blockquote>

<p>Anima’s AI will generate the underlying code for you &mdash; complete with state management, handlers, and reusable logic. You can always dive in and tweak the output to fit your specific app structure.</p>

<p>This turns design into functional UI with a level of speed that traditional front-end workflows just can’t match.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.animaapp.com/">
    
    <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/anima-playground-figma-designs-live-apps/use-ai-promts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/anima-playground-figma-designs-live-apps/use-ai-promts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/anima-playground-figma-designs-live-apps/use-ai-promts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/anima-playground-figma-designs-live-apps/use-ai-promts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/anima-playground-figma-designs-live-apps/use-ai-promts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/anima-playground-figma-designs-live-apps/use-ai-promts.png"
			
			sizes="100vw"
			alt="Use AI prompts to add interactivity and logic effortlessly. "
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Use AI prompts to add interactivity and logic effortlessly. (<a href='https://files.smashing.media/articles/anima-playground-figma-designs-live-apps/use-ai-promts.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="4-see-live-changes-instantly">4. See Live Changes Instantly</h3>

<p>As you make changes &mdash; whether through prompts or direct code edits &mdash; you see them reflected in real-time. Anima Playground acts as a visual IDE, combining the flexibility of code with the immediacy of design tools.</p>

<p>This live feedback loop means less context-switching and faster iterations. Whether you’re testing animations, layout tweaks, or new features, you get to <em>see it</em> before you commit to anything.</p>

<h2 id="more-than-just-design-to-code">More Than Just Design-to-Code</h2>

<p>While many tools promise “Figma to code,” <strong>Anima Playground goes beyond static conversion</strong>. It’s a fully interactive environment where real apps are born &mdash; with logic, data, and interactivity.</p>

<p><strong>Some powerful features include:</strong></p>

<ul>
<li><strong>One-click AI suggestions</strong> to enhance your UI with logic.</li>
<li><strong>Custom component support</strong>, allowing teams to inject their own building blocks.</li>
<li><strong>Component reuse</strong>, letting you structure apps in a scalable way.</li>
<li><strong>Flexible framework support</strong>, starting with React and planning to support more in the future.</li>
</ul>

<p>It’s not just for prototyping &mdash; it’s for building.</p>

<h2 id="why-it-matters">Why It Matters</h2>

<p>The design-to-code handoff has been broken for too long. Anima Playground isn’t just another tool. It’s a game-changer. Here’s why:</p>

<ul>
<li>🚀 Speed<br />
What used to take days now takes minutes. You skip the repetitive coding, layout guesswork, and context switching.</li>
<li>🎯 Accuracy<br />
Your designs stay true to the original. No more pixel-matching or guessing which font size the designer used.</li>
<li>🧩 Flexibility<br />
Developers get full access to the code. It&rsquo;s not a black box &mdash; it&rsquo;s fully transparent and editable.</li>
<li>🤝 Collaboration<br />
Designers and developers finally share the same playground &mdash; literally. This tightens feedback loops and shortens build cycles.</li>
</ul>

<p>By making the workflow smarter, <strong>Anima Playground helps teams build better products, faster</strong>, and with fewer headaches.</p>

<h2 id="who-is-it-for">Who Is It For?</h2>

<p>Whether you’re a <strong>designer</strong>, <strong>developer</strong>, <strong>startup founder</strong>, or <strong>PM</strong>, Anima Playground removes the barriers between your ideas and real products.</p>

<ul>
<li><strong>Designers</strong> can see their visions come to life, exactly as imagined.</li>
<li><strong>Developers</strong> can skip the grunt work and focus on logic, architecture, and business needs.</li>
<li><strong>Teams</strong> can work together in a unified environment &mdash; no more waiting for the “handoff.”</li>
</ul>

<p>It’s perfect for building landing pages, dashboards, internal tools, MVPs, and more.</p>

<h3 id="are-you-ready-to-try-it">Are You Ready To Try It?</h3>

<p>Anima Playground and the Anima API are redefining the connection between design and development in the era of AI-powered coding. Whether you&rsquo;re a designer, developer, product team member, marketer, or entrepreneur, Anima empowers you to transform visual ideas into concepts within minutes—and into fully functional products within hours.</p>

<p>If you’re tired of the endless design-to-development grind, <a href="https://projects.animaapp.com/signup">it’s time to give Anima Playground a spin</a>. Whether you’re a designer who wants to bring your vision to life or a developer looking to speed up the build process, this tool has your back.</p>

<p>Let your designs do more than look good &mdash; let them <em>work</em>!</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>(il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Joas Pambou</author><title>Using Manim For Making UI Animations</title><link>https://www.smashingmagazine.com/2025/04/using-manim-making-ui-animations/</link><pubDate>Tue, 08 Apr 2025 15:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/04/using-manim-making-ui-animations/</guid><description>Animation makes things clearer, especially for designers and front-end developers working on UI, prototypes, or interactive visuals. Manim is a tool that lets you create smooth and dynamic animations, not just for the design field but also in math, coding, and beyond, to explain complex ideas or simply make everything a little bit more interactive.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/04/using-manim-making-ui-animations/" />
              <title>Using Manim For Making UI Animations</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Using Manim For Making UI Animations</h1>
                  
                    
                    <address>Joas Pambou</address>
                  
                  <time datetime="2025-04-08T15:00:00&#43;00:00" class="op-published">2025-04-08T15:00:00+00:00</time>
                  <time datetime="2025-04-08T15:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>Say you are learning to code for the first time, in Python, for example, which is a great starting point for getting into development. You are likely to come across some information like <strong>“a variable stores a value.”</strong> That sounds straightforward, but if you are a beginner just starting, then it can also be a bit confusing. <em>How</em> does a variable store or hold something? <em>What</em> happens when we assign a new value to it?</p>

<p>To figure things out, you could read a bunch and watch tutorials, but sometimes, resources like these don’t help the concept fully click. That’s where animation helps. It has the power to take complex programming concepts and turn them into something visual, dynamic, and easy to grasp.</p>

<p>Let’s break it down with an example: Say we have a box labeled X, first empty, then fill with a value 5, for this example, then update to 12, then 8, then 20, then 3.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073634054"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>This animation shows how a variable stores values and updates over time, step by step. <a href='https://github.com/pontonkid/Manim-Manim/blob/main/Variable'>Source Code</a></figcaption>
	
</figure>

<p>Even if you are unfamiliar with Python, an animation like this makes the concept more obvious, helping you understand how variables work with visual cues. You can now visualize the variables as containers that hold and update values dynamically. It’s way easier to <em>see</em> that than it is to just read about variables.</p>

<p>Well, <strong>Manim isn’t just limited to programming</strong>; it works for math, physics, UI/UX, and more. In trigonometry, you can take something like a “Sine Wave” as an example, which is a smooth, continuous curve that moves up and down in a repeating pattern, and it is found everywhere from sound waves to electrical signals to the motion of a pendulum.</p>

<p>Sounds simple, right? Or maybe a bit confusing, especially if you’re not a math person, but let me help with this:</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073634919"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>A smooth sine wave moving across the screen, illustrating oscillation and periodic motion. <a href='https://github.com/pontonkid/Manim-Manim/blob/main/Sine-Wave'>Source Code</a></figcaption>
	
</figure>

<p>Now, with this, you can see how the wave moves. Instead of just numbers and formulas, you’re watching it happen. And that’s pretty much the idea here! In this article, we’ll explore Manim and <em>how</em> it makes concepts easier to understand through animation.</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="manim-manim-what-is-it">Manim, Manim! What Is It?</h2>

<p>By now, you may have a rough idea of what Manim can do, but let’s break it down a little more. What exactly is Manim? Well, it’s two things.</p>

<blockquote>First, Manim is an open-source Python library for creating high-quality mathematical animations.</blockquote>

<p>If you’ve ever watched a <a href="https://www.3blue1brown.com/"><strong>3Blue1Brown</strong></a> video, you’ve seen Manim in action because <strong>Grant Sanderson</strong> originally developed it for his YouTube channel.</p>

<blockquote>Second, Manim is a script-driven animation engine, meaning you write Python code to generate animations instead of dragging and dropping elements like in typical video editing software.</blockquote>

<p>This gives you <strong>precise control over every detail</strong>, including text, color, shape, transformations, timing &mdash; you name it. Whether you’re explaining math, physics, or programming concepts, Manim makes it fairly easy to create clear and dynamic visuals with just a few lines of code. Plus, it works seamlessly with <a href="https://www.latex-project.org/about/">LaTeX</a>, so you can render mathematical equations beautifully without extra effort. That’s why it’s popular among educators, researchers, and content creators.</p>

<p>Of course, Manim isn’t the only tool you can use. If it doesn’t quite fit your needs or the programming language you are most comfortable with, here are some alternatives worth checking out:</p>

<ul>
<li><a href="https://processing.org/"><strong>Processing</strong></a><br />
This is a Java-based coding framework, great for generative art and interactive visuals. If you enjoy experimenting with visual design through code, in Java, to be exact, then Processing gives you a solid foundation.</li>
<li><a href="https://p5js.org/"><strong>p5.js</strong></a><br />
This is a JavaScript library, an alternative for web animations. If you’re a front-end developer working with HTML and CSS, p5.js makes it easy for you to create graphics directly in the browser.</li>
<li><strong>Desmos</strong><br />
This focuses on math visualization. Desmos lets you create interactive graphs and scripted animations directly in the browser. You can use it through <a href="https://www.desmos.com/calculator/cq6zs8fxhp">Desmos Graphs</a>, <a href="https://www.desmos.com/scientific">Desmos Calculator,</a> or the <a href="https://www.desmos.com/api/v1.10/docs/index.html#document-quickstart">Desmos API</a>.</li>
<li><a href="https://docs.blender.org/api/current/info_quickstart.html"><strong>Blender (with Python Scripting)</strong></a><br />
This is mostly known for 3D animation, but with its Python API, you can script animations, including math and physics-based simulations.</li>
</ul>

<p>Now, let’s compare them:</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>Tool</th>
            <th>Language</th>
      <th>Best For</th>
      <th>Strengths</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>Manim</strong></td>
            <td>Python</td>
      <td>Math, physics, programming animations</td>
      <td>High precision, script-driven, LaTeX support</td>
        </tr>
        <tr>
            <td><strong>Processing</strong></td>
            <td>Java</td>
      <td>Generative art, interactive visuals</td>
      <td>Great for creative coding</td>
        </tr>
        <tr>
            <td><strong>p5.js</strong></td>
            <td>JavaScript</td>
      <td>Web-based animations</td>
      <td>Works well with HTML & CSS</td>
        </tr>
    <tr>
            <td><strong>Blender (Python API)</strong></td>
            <td>Python</td>
      <td>3D & math-based animations</td>
      <td>Powerful 3D capabilities, physics simulations</td>
        </tr>
    <tr>
            <td><strong>Desmos</strong></td>
            <td>JavaScript</td>
      <td>Math visualizations</td>
      <td>Browser-based, great for interactive graphs</td>
        </tr>
    </tbody>
</table>

<h2 id="how-to-get-started">How To Get Started</h2>

<p>There are multiple ways to install the library. You can set it up locally, use Conda or Docker, or run it inside Jupyter Notebooks. But if you don’t want to deal with installations, Replit is a great alternative, as it’s a real-time live editor that lets you start coding animations instantly.</p>

<h3 id="1-create-an-account-on-replit-using-github-or-email">1. Create An Account On Replit Using GitHub or Email.</h3>

<p>Once you’re in, your dashboard should look something like this:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="381"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png"
			
			sizes="100vw"
			alt="Replit dashboard"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/using-manim-making-ui-animations/1-create-account-replit.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="2-click-create-app">2. Click “Create App”</h3>

<p>You’ll see three options:</p>

<ol>
<li>“Create With Replit Agent”,</li>
<li>“Choose a Template”,</li>
<li>“Import from GitHub”.</li>
</ol>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="378"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png"
			
			sizes="100vw"
			alt="A screenshot showing three options how to create an new App on Replit"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/using-manim-making-ui-animations/2-replit-create-app.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="3-select-choose-a-template">3. Select “Choose a Template”</h3>

<p>Then, search for Manim and create your app. At this point, you don’t have to do anything else because this sets up everything for you (including the <code>main.py</code> file, a media folder, and all of the required dependencies).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="380"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png"
			
			sizes="100vw"
			alt="A screenshot showing how to choose a template Manim on Replit"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/using-manim-making-ui-animations/3-replit-choose-template.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><em>Voilà!</em> Now you can start coding your animations right away!</p>

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

<h2 id="using-manim-for-math-code-and-ui-ux-visuals">Using Manim For Math, Code, And UI/UX Visuals</h2>

<p>Okay, you know Manim. Whether it’s for math, programming, physics, or even prototyping UI concepts, it’s all about making complex concepts easier to grasp through animation. But how does that work in practice? Let’s go through some ways Manim makes things clearer and more engaging.</p>

<h3 id="1-math-geometry-visuals">1. Math &amp; Geometry Visuals</h3>

<p>Sometimes, math can feel a bit like a puzzle with missing pieces. But with Manim, numbers, shapes, and graphs move, making patterns and relationships easier to grasp. Take graphs, for example. When you tweak a parameter, Manim instantly updates the visualization so you can watch how a function changes over time. And that’s a game-changer for understanding concepts like <strong>derivatives</strong> or <strong>transformations</strong>.</p>

<figure><a href="https://files.smashing.media/articles/using-manim-making-ui-animations/4-manim-graphs.gif"><img src="https://files.smashing.media/articles/using-manim-making-ui-animations/4-manim-graphs-800px.gif" width="800" height="450" alt="Manim graphs" /></a><figcaption>(<a href="https://files.smashing.media/articles/using-manim-making-ui-animations/4-manim-graphs.gif">Large preview</a>)</figcaption></figure>

<p>Geometry concepts also come easier and become even more fun when you can see those shapes move, giving you a clear understanding of rotation or reflection. If you’re drawing a triangle with a compass and straightedge, for example, Manim can animate each step, making it easier to follow along and understand the idea.</p>

<figure><a href="https://files.smashing.media/articles/using-manim-making-ui-animations/5-manim-triangle.gif"><img src="https://files.smashing.media/articles/using-manim-making-ui-animations/5-manim-triangle.gif" width="640" height="360" alt="Manim for drawing triangles" /></a></figure>

<h3 id="2-coding-algorithms">2. Coding &amp; Algorithms</h3>

<p>As you may already know, coding is a process that runs step by step, and Manim makes that easy to see. Whether you are working on the front end or the back end, logic flows in a way that’s not always clear from just reading or writing code. With Manim, you can, for example, watch how a sorting algorithm moves numbers around or simply how a loop runs.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073635454"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption><a href='https://github.com/pontonkid/Manim-Manim/blob/main/Sorting-Algo'>Source Code</a></figcaption>
	
</figure>

<p>The same goes for data structures like linked lists, trees, and more. A binary tree makes more sense when you can see it grow and balance itself. Even complex algorithms like <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Dijkstra’s shortest path</a> become clearer when you watch the path being calculated in real time, even if you may not have a background in math.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073635907"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Watch as the tree is explored node by node, showing how data is structured and accessed. <a href='https://github.com/pontonkid/Manim-Manim/blob/main/Binary_Tree.py'>Source Code</a></figcaption>
	
</figure>

<h3 id="3-ui-ux-concepts-motion-design">3. UI/UX Concepts &amp; Motion Design</h3>

<p>Although Manim is not a UI/UX design tool, it can be useful for <strong>demonstrating designs</strong>. Static images can’t always show the full picture, but with Manim, before-and-after comparisons become more dynamic, and of course, it makes it easier to highlight why a new navigation menu, for example, is more intuitive or how a checkout flow reduces friction.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073636288"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption><a href='https://github.com/pontonkid/Manim-Manim/blob/main/UI_Comparison.py'>Source Code</a></figcaption>
	
</figure>

<p>Animated heatmaps can show click patterns over time, helping to spot trends more easily. Conversion funnels become clearer when each stage is animated, revealing exactly where users drop off.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073636879"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption><a href='https://github.com/pontonkid/Manim-Manim/blob/main/user_heatmap.py'>Source Code</a></figcaption>
	
</figure>

<h2 id="let-s-manim">Let’s Manim!</h2>

<p>Well, that’s a lot we covered! By now, you should have Manim installed in whatever way works best for you. But before we jump into the coding part, let’s quickly go over Manim’s core building blocks. Manim’s animations are made of three main concepts:</p>

<ul>
<li>Mobjects,</li>
<li>Animations,</li>
<li>Scenes.</li>
</ul>

<h3 id="1-mobjects-mathematical-objects">1. Mobjects (Mathematical Objects)</h3>

<p>Everything you display in Manim is a Mobject (short for “mathematical object”). There are different types:</p>

<ul>
<li>Basic shapes like  <strong><code>Circle()</code></strong>, <strong><code>Rectangle()</code></strong>, and <strong><code>Arrow()</code></strong>,</li>
<li>Text elements for adding labels, and</li>
<li>Advanced structures like graphs, axes, and bar charts.</li>
</ul>

<p>A mobject is more like a blueprint, and it won’t show up unless you add it to a scene. Here’s a brief example:</p>

<pre><code class="language-python">from manim import *

class MobjectExample(Scene):
  def construct(self):
    circle = Circle()  &#35; Create a circle
    circle.set&#95;fill(BLUE, opacity=0.5)  &#35; Set color and transparency
    self.add(circle)  &#35; Add to the scene
    self.wait(2)
</code></pre>

<p>A blue circle will appear for about two seconds when you run this:</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073637351"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<h3 id="2-animations">2. Animations</h3>

<p>Animations in Manim, on the other hand, are all about changing these objects over time. Rather than just displaying a sharp edge, we can make it move, rotate, fade, or transform into something else. Really, we do have this much control through the <strong><code>Animation</code></strong> <code>class</code>.</p>

<p>If we use the same circle example from earlier, we can add animations to see how it works and compare the visual differences:</p>

<pre><code class="language-python">from manim import &#42;

class AnimationExample(Scene):
  def construct(self):
    circle = Circle()
    circle.set&#95;fill(BLUE, opacity=0.5) 

    self.play(FadeIn(circle))
    self.play(circle.animate.shift(RIGHT &#42; 2))
    self.play(circle.animate.scale(1.5)) 
    self.play(Rotate(circle, angle=PI/4))  
    self.wait(2)
</code></pre>

<p>Here, we are making a move, scaling up, and rotating. The <code>play()</code> method is what makes animations run. For example, <code>FadeIn(circle)</code> makes the circle gradually appear, and <code>circle.animate.shift(RIGHT * 2)</code> moves it two units to the right. If you want to slow things down, you can add <code>run_time</code> to control the duration, like the following:</p>

<pre><code class="language-python">self.play(circle.animate.scale(2), run&#95;time=3),
</code></pre>

<p>This makes the scaling take three more seconds instead of the default amount of time:</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073637655"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<h3 id="3-scenes">3. Scenes</h3>

<p>Scenes are what hold everything together. A scene defines what appears, how it animates, and in what order. Every Manim script has a class that is inherited from a <code>Scene</code>, and it contains a <code>construct()</code> method. This is where we write our animation logic. For example,</p>

<pre><code class="language-python">class SimpleScene(Scene):
  def construct(self):
    text = Text("Hello, Manim!")
    self.play(Write(text))
    self.wait(2)
</code></pre>

<p>This creates a simple text animation where the words appear as if being written.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073637982"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

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

<h2 id="bringing-manim-to-design">Bringing Manim To Design</h2>

<p>As we discussed earlier, Manim is a great tool for UI/UX designers and front-end developers to <strong>visualize user interactions</strong> or to <strong>explain UI concepts</strong>. Think about how users navigate through a website or an app: they click buttons, move between pages, and interact with elements. With Manim, we can animate these interactions and see them play out step by step.</p>

<p>With this in mind, let’s create a simple flow where a user clicks a button, leading to a new page:</p>

<div class="break-out">
<pre><code class="language-python">from manim import &#42;

class UIInteraction(Scene):
  def construct(self):
    &#35; Create a homepage screen
    homepage = Rectangle(width=6, height=3, color=BLUE)
    homepage&#95;label = Text("Home Page").scale(0.8)
    homepage&#95;group = VGroup(homepage, homepage&#95;label)

    &#35; Create a button
    button = RoundedRectangle(width=1.5, height=0.6, color=RED).shift(DOWN &#42; 1)
    button&#95;label = Text("Click Me").scale(0.5).move&#95;to(button)
    button&#95;group = VGroup(button, button&#95;label)

    &#35; Add homepage and button
    self.add(homepage&#95;group, button&#95;group)

    &#35; Simulating a button click
    self.play(button.animate.set&#95;fill(RED, opacity=0.5))  &#35; Button press effect
    self.wait(0.5)  &#35; Pause to simulate user interaction

    &#35; Create a new page (simulating navigation)
    new_page = Rectangle(width=6, height=3, color=GREEN)
    new&#95;page&#95;label = Text("New Page").scale(0.8)
    new&#95;page&#95;group = VGroup(new&#95;page, new&#95;page&#95;label)

    &#35; Animate transition to new page
    self.play(FadeOut(homepage&#95;group, shift=UP),  &#35; Move old page up
      FadeOut(button&#95;group, shift=UP),  &#35; Move button up
      FadeIn(new&#95;page&#95;group, shift=DOWN))  &#35; Bring new page from top
    self.wait(2)
</code></pre>
</div>

<p>The code creates a simple UI animation for a homepage displaying a button. When the button is clicked, it fades slightly to simulate pressing, and then the homepage and button fade out while a new page fades in, creating a transition effect.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073638330"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>If you think of it, scrolling is one of the most natural interactions in modern web and app design. Whether moving between sections on a landing page or smoothly revealing content, well-designed scroll animations make the experience feel fluid. Let me show you:</p>

<div class="break-out">
<pre><code class="language-python">from manim import &#42;

class ScrollEffect(Scene):
  def construct(self):
    &#35; Create three sections to simulate a webpage
    section1 = Rectangle(width=6, height=3, color=BLUE).shift(UP&#42;3)
    section2 = Rectangle(width=6, height=3, color=GREEN)
    section3 = Rectangle(width=6, height=3, color=RED).shift(DOWN&#42;3)

    &#35; Add text to each section
    text1 = Text("Welcome", font&#95;size=32).move&#95;to(section1)
    text2 = Text("About Us", font&#95;size=32).move&#95;to(section2)
    text3 = Text("Contact", font&#95;size=32).move&#95;to(section3)

    self.add(section1, section2, section3, text1, text2, text3)
    self.wait(1)

    &#35; Simulate scrolling down
    self.play(
      section1.animate.shift(DOWN&#42;6),
      section2.animate.shift(DOWN&#42;6),
      section3.animate.shift(DOWN&#42;6),
      text1.animate.shift(DOWN&#42;6),
      text2.animate.shift(DOWN&#42;6),
      text3.animate.shift(DOWN&#42;6),
      run&#95;time=3
    )
    self.wait(1)
</code></pre>
</div>

<p>This animation shows a scrolling effect by moving sections of a webpage upward, simulating how content shifts as a user scrolls. It is a simple way to visualize transitions that make the UI feel smooth and engaging.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1073638729"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

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

<p>Manim makes it easier to show how users interact with a design. You can animate navigations, interactions, and user behaviors to understand better how design works in action. Is there more to explore? Definitely! You can take these simple examples and build on them by adding more complex features.</p>

<p>But what I hope you take away from all of this is that <strong>subtle animations can help communicate and clarify concepts</strong> and that Manim is a library for making those sorts of animations. Traditionally, it’s used to help explain mathematical and scientific concepts, but you can see just how useful it can be to working in front-end development, particularly when it comes to highlighting and visualizing UI changes.</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>Edoardo Dusi</author><title>Building A Drupal To Storyblok Migration Tool: An Engineering Perspective</title><link>https://www.smashingmagazine.com/2025/04/building-drupal-storyblok-migration-tool-engineering-perspective/</link><pubDate>Wed, 02 Apr 2025 12:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/04/building-drupal-storyblok-migration-tool-engineering-perspective/</guid><description>In this article, Edoardo Dusi shares the engineering and architectural choices made by the team at Storyblok and how real-world migration challenges were addressed using modern PHP practices.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/04/building-drupal-storyblok-migration-tool-engineering-perspective/" />
              <title>Building A Drupal To Storyblok Migration Tool: An Engineering Perspective</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Building A Drupal To Storyblok Migration Tool: An Engineering Perspective</h1>
                  
                    
                    <address>Edoardo Dusi</address>
                  
                  <time datetime="2025-04-02T12:00:00&#43;00:00" class="op-published">2025-04-02T12:00:00+00:00</time>
                  <time datetime="2025-04-02T12:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Storyblok</b></p>
                

<p>Content management is evolving. The traditional monolithic CMS approach is giving way to headless architectures, where content management and presentation are decoupled. This shift brings new challenges, particularly when organizations need to migrate from legacy systems to modern headless platforms.</p>

<p>Our team encountered this scenario when creating a migration path from Drupal to Storyblok. These systems handle content architecture quite differently &mdash; Drupal uses an entity-field model integrated with PHP, while Storyblok employs a flexible Stories and Blocks structure designed for headless delivery.</p>

<p>If you just need to use a script to do a simple &mdash; yet extensible &mdash; content migration from Drupal to Storyblok, I already shared <a href="https://www.storyblok.com/tp/migrating-drupal-articles-to-storyblok">step-by-step instructions</a> on how to download and use it. If you’re interested in the process of creating such a script so that you can write your own (possibly) better version, stay here!</p>

<p>We observed that developers sometimes struggle with manual content transfers and custom scripts when migrating between CMSs. This led us to develop and share our migration approach, which we implemented as an open-source tool that others could use as a reference for their migration needs.</p>

<p>Our solution combines two main components: a custom Drush command that handles content mapping and transformation and a new PHP client for Storyblok’s Management API that leverages modern language features for improved developer experience.</p>

<p>We’ll explore the engineering decisions behind this tool’s development, examining our architectural choices and how we addressed real-world migration challenges using modern PHP practices.</p>

<blockquote><p><strong>Note</strong>: You can find the complete source code of the migration tool <a href="https://github.com/storyblok/drupal-exporter">in the Drupal exporter repo</a>.</p></blockquote>

<h2 id="planning-the-migration-architecture">Planning The Migration Architecture</h2>

<p>The journey from Drupal to Storyblok presents unique architectural challenges. The fundamental difference lies in how these systems conceptualize content: Drupal structures content as entities with fields, while Storyblok uses a component-based approach with Stories and Blocks.</p>

<h3 id="initial-requirements-analysis">Initial Requirements Analysis</h3>

<p>A successful migration tool needs to understand both systems intimately. Drupal’s content model relies heavily on its Entity API, storing content as structured field collections within entities. A typical Drupal article might contain fields for the title, body content, images, and taxonomies. <a href="https://www.storyblok.com/?utm_source=smashing&amp;utm_medium=sponsor&amp;utm_campaign=DGM_DEV_SMA_TRA&amp;utm_content=smashing-OSS">Storyblok</a>, on the other hand, structures content as stories that contain blocks, reusable components that can be nested and arranged in a flexible way. It’s a subtle difference that shaped our technical requirements, particularly around content mapping and data transformation, but ultimately, it’s easy to see the relationships between the two content models.</p>

<h3 id="technical-constraints">Technical Constraints</h3>

<p>Early in development, we identified several key constraints. <a href="https://www.storyblok.com/docs/api/management/getting-started/?utm_source=smashing&amp;utm_medium=sponsor&amp;utm_campaign=DGM_DEV_SMA_TRA&amp;utm_content=smashing-OSS">Storyblok’s Management API</a> enforces rate limits that affect how quickly we can transfer content. Media assets must first be uploaded and then linked. Error recovery becomes essential when migrating hundreds of pieces of content.</p>

<p>The brand-new<a href="https://github.com/storyblok/php-management-api-client"> Management API PHP client</a> handles these constraints through built-in retry mechanisms and response validation, so in writing a migration script, we don’t need to worry about them.</p>

<h3 id="tool-selection">Tool Selection</h3>

<p>We chose Drush as our command-line interface for several reasons. First, it’s deeply integrated with Drupal’s bootstrap process, providing direct access to the Entity API and field data. Second, Drupal developers are already familiar with its conventions, making our tool more accessible.</p>

<p>The decision to develop a new <strong>Management API client</strong> came from our experience with the evolution of PHP since we developed the first PHP client, and our goal to provide developers with a dedicated tool for this specific API that offered an improved DX and a tailored set of features.</p>

<p>This groundwork shaped how we approached the migration workflow.</p>

<h2 id="the-building-blocks-a-new-management-api-client">The Building Blocks: A New Management API Client</h2>

<p>A content migration tool interacts heavily with Storyblok’s Management API &amp;mdash, creating stories, uploading assets, and managing tags. Each operation needs to be reliable and predictable. Our brand-new client simplifies these interactions through intuitive method calls: The client handles authentication, request formatting, and response parsing behind the scenes, letting devs focus on content operations rather than API mechanics.</p>

<h3 id="design-for-reliability">Design For Reliability</h3>

<p>Content migrations often involve hundreds of API calls. Our client includes built-in mechanisms for handling common scenarios like rate limiting and failed requests. The response handling pattern provides clear feedback about operation success: A logger can be injected into the client class, as we did using the Drush logger in our migration script from Drupal.</p>

<h3 id="improving-the-development-experience">Improving The Development Experience</h3>

<p>Beyond basic API operations, the client reduces cognitive load through predictable patterns. Data objects provide a structured way to prepare content for Storyblok: This pattern validates data early in the process, catching potential issues before they reach the API.</p>

<h2 id="designing-the-migration-workflow">Designing The Migration Workflow</h2>

<p>Moving from Drupal’s entity-based structure to Storyblok’s <strong>component model</strong> required careful planning of the migration workflow. Our goal was to create a process that would be both reliable and adaptable to different content structures.</p>

<h3 id="command-structure">Command Structure</h3>

<p>The migration leverages Drupal’s <em>entity query</em> system to extract content systematically. By default, <strong>access checks were disabled</strong> (a reversible business decision) to focus solely on migrating <strong>published nodes</strong>.</p>

<h3 id="key-steps-and-insights">Key Steps And Insights</h3>

<ul>
<li><p><strong>Text Fields</strong></p>

<ul>
<li>Required minimal effort: values like <code>value()</code> mapped directly to Storyblok fields.<br /></li>
<li>Rich text posed no encoding challenges, enabling straightforward 1:1 transfers.</li>
</ul></li>

<li><p><strong>Handling Images</strong></p>

<ol>
<li><strong>Upload</strong>: Assets were sent to an AWS S3 bucket.</li>
<li><strong>Link</strong>: Storyblok’s <strong>Asset API</strong> <code>upload()</code> method returned an <code>object_id</code>, simplifying field mapping.</li>
<li><strong>Assign</strong>: The asset ID and filename were attached to the story.</li>
</ol></li>

<li><p><strong>Managing Tags</strong></p>

<ul>
<li>Tags extracted from Drupal were pre-created via Storyblok’s <strong>Tag API</strong> (optional but ensures consistency).</li>
<li>When assigning tags to stories, Storyblok automatically creates missing ones, streamlining the process.</li>
</ul></li>
</ul>

<h3 id="why-staged-workflows-matter">Why Staged Workflows Matter</h3>

<p>The migration avoids broken references by prioritizing dependencies (assets first, tags next, content last). While pre-creating tags add control, teams can adapt this logic—for example, letting Storyblok auto-generate tags to save time.</p>

<blockquote><p>Flexibility is key: every decision (access checks, tag workflows) can be adjusted to align with project goals.</p></blockquote>

<h2 id="real-world-implementation-challenges">Real-World Implementation Challenges</h2>

<p>Migrating content between Drupal and Storyblok presents challenges that you, as the implementer, may encounter.</p>

<p>For example, when dealing with large datasets, you may find that Drupal sites with thousands of nodes can quickly hit the rate limits enforced by Storyblok’s management API. In such cases, a batching mechanism for your requests is worth considering. Instead of processing every node at once, you can process a subset of records, wait for a short period of time, and then continue.</p>

<p>Alternatively, you could use the <code>createBulk</code> method of the Story API in the Management API, which allows you to handle multiple story creations with built-in rate limit handling and retries. Another potential hurdle is the conversion of complex field types, especially when Drupal’s nested structures or Paragraph fields need to be mapped to Storyblok’s more flexible block-based model.</p>

<p>One approach is first to analyze the nesting depth and structure of the Drupal content, then flatten deeply nested elements into reusable Storyblok components while maintaining the correct hierarchy. For example, a <code>paragraph</code> field with embedded media and text can be split into blocks within Storyblok, with each component representing a logical section of content. By structuring data this way before migration, you ensure that content remains editable and properly structured in the new system.</p>

<p>Data consistency is another aspect that you need to manage carefully. When migrating hundreds of records, partial failures are always risky. One approach to managing this is to log detailed information for each migration operation and implement a retry mechanism for failed operations.</p>

<p>For example, wrapping API calls in a <code>try-catch</code> block and logging errors can be a practical way to ensure that no records are silently dropped. When dealing with fields such as taxonomy terms or tags created on the fly in Storyblok, you may run into duplication issues. A good practice is to perform a check before creating a new tag. This could involve maintaining a local cache of previously created tags and checking against them before sending a create request to the API.</p>

<p>The same goes for images; a check could ensure you don’t upload the same asset twice.</p>

<h2 id="lessons-learned-and-looking-forward">Lessons Learned And Looking Forward</h2>

<p>A <strong>dedicated API client</strong> for Storyblok streamlined interactions, abstracting backend complexity while improving code maintainability. Early use of <strong>structured data objects</strong> to prepare content proved critical, enabling pre-emptive error detection and reducing API failures.</p>

<p>We also ran into some challenges and see room for improvement:</p>

<ul>
<li><strong>Encoding issues</strong> in rich text (e.g., HTML entities) were resolved with a pre-processing step</li>
<li><strong>Performance bottlenecks</strong> with large text/images required memory optimization and refined request handling</li>
</ul>

<p>Enhancements could include support for <em>Drupal Layout Builder</em>, advanced validation layers, or dynamic asset management systems.</p>

<blockquote><p>💡 For deeper dives into our Management API client or migration strategies, reach out via <a href="https://discord.gg/jKrbAMz">Discord</a>, explore the <a href="https://github.com/storyblok/php-management-api-client">PHP Client repo</a>, or connect with me on <a href="https://hachyderm.io/@edodusi">Mastodon</a>. Feedback and contributions are welcome!</p></blockquote>

<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>(il)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Teng Wei Herr</author><title>Adaptive Video Streaming With Dash.js In React</title><link>https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/</link><pubDate>Thu, 27 Mar 2025 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/</guid><description>HTML &lt;code>&amp;lt;video&amp;gt;&lt;/code> is the de facto element we turn to for embedding video content, but it comes with constraints. For example, it downloads the video file linearly over HTTP, which leads to performance hiccups, especially for large videos consumed on slower connections. But with adaptive bitrate streaming, we can split the video into multiple segments at different bitrates and resolutions.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/03/adaptive-video-streaming-dashjs-react/" />
              <title>Adaptive Video Streaming With Dash.js In React</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Adaptive Video Streaming With Dash.js In React</h1>
                  
                    
                    <address>Teng Wei Herr</address>
                  
                  <time datetime="2025-03-27T13:00:00&#43;00:00" class="op-published">2025-03-27T13:00:00+00:00</time>
                  <time datetime="2025-03-27T13:00:00&#43;00:00" class="op-modified">2025-12-25T10:03:08+00:00</time>
                </header>
                
                

<p>I was recently tasked with creating video reels that needed to be played smoothly under a slow network or on low-end devices. I started with the native HTML5 <code>&lt;video&gt;</code> tag but quickly hit a wall &mdash; it just doesn’t cut it when connections are slow or devices are underpowered.</p>

<p>After some research, I found that <strong>adaptive bitrate streaming</strong> was the solution I needed. But here’s the frustrating part: finding a comprehensive, beginner-friendly guide was so difficult. The resources on MDN and other websites were helpful but lacked the end-to-end tutorial I was looking for.</p>

<p>That’s why I’m writing this article: to provide you with the step-by-step guide I wish I had found. I’ll bridge the gap between writing FFmpeg scripts, encoding video files, and implementing the DASH-compatible video player (<a href="https://dashjs.org/">Dash.js</a>) with code examples you can follow.</p>

<h2 id="going-beyond-the-native-html5-video-tag">Going Beyond The Native HTML5 <code>&lt;video&gt;</code> Tag</h2>

<p>You might be wondering why you can’t simply rely on the HTML <code>&lt;video&gt;</code> element. There’s a good reason for that. Let’s compare the difference between a native <code>&lt;video&gt;</code> element and adaptive video streaming in browsers.</p>

<h3 id="progressive-download">Progressive Download</h3>

<p>With progressive downloading, your browser downloads the video file linearly from the server over HTTP and starts playback as long as it has buffered enough data. This is the default behavior of the <code>&lt;video&gt;</code> element.</p>

<pre><code class="language-html">&lt;video src="rabbit320.mp4" /&gt;
</code></pre>

<p>When you play the video, check your browser’s network tab, and you’ll see multiple requests with the <code>206 Partial Content</code> status code.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="140"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png"
			
			sizes="100vw"
			alt="HTTP 206 Range Requests"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/adaptive-video-streaming-dashjs-react/http-206-range-requests.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests">HTTP 206 Range Requests</a> to fetch the video file in chunks. The server sends specific byte ranges of the video to your browser. When you seek, the browser will make more range requests asking for new byte ranges (e.g., “Give me bytes 1,000,000–2,000,000”).</p>

<p class="c-pre-sidenote--left">In other words, it doesn’t fetch the entire file all at once. Instead, it delivers partial byte ranges from the single MP4 video file on demand. This is still considered a <strong>progressive download</strong> because only a single file is fetched over HTTP &mdash; there is no bandwidth or quality adaptation.</p>
<p class="c-sidenote c-sidenote--right">If the server or browser doesn’t support range requests, the entire video file will be downloaded in a single request, returning a <code>200 OK</code> status code. In that case, the video can only begin playing once the entire file has finished downloading.</p> 

<p><strong>The problems?</strong> If you’re on a slow connection trying to watch high-resolution video, you’ll be waiting a long time before playback starts.</p>

<h3 id="adaptive-bitrate-streaming">Adaptive Bitrate Streaming</h3>

<p>Instead of serving one single video file, <strong>adaptive bitrate (ABR) streaming</strong> splits the video into multiple segments at different bitrates and resolutions. During playback, the ABR algorithm will automatically select the highest quality segment that can be downloaded in time for smooth playback based on your network connectivity, bandwidth, and other device capabilities. It continues adjusting throughout to adapt to changing conditions.</p>

<p>This magic happens through two key browser technologies:</p>

<ul>
<li><strong>Media Source Extension (MSE)</strong><br />
It allows passing a <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaSource">MediaSource</a> object to the <code>src</code> attribute in <code>&lt;video&gt;</code>, enabling sending multiple <a href="https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer">SourceBuffer</a> objects that represent video segments.
<br /></li>
</ul>

<div class="break-out">
<pre><code class="language-javascript">&lt;video src="blob:https://example.com/6e31fe2a-a0a8-43f9-b415-73dc02985892" /&gt;</code></pre>
</div>
    

<ul>
<li><strong>Media Capabilities API</strong><br />
It provides information on your device’s video decoding and encoding abilities, enabling ABR to make informed decisions about which resolution to deliver.</li>
</ul>

<p>Together, they enable the core functionality of ABR, serving video chunks optimized for your specific device limitations in real time.</p>

<h2 id="streaming-protocols-mpeg-dash-vs-hls">Streaming Protocols: MPEG-DASH Vs. HLS</h2>

<p>As mentioned above, to stream media adaptively, a video is split into chunks at different quality levels across various time points. We need to facilitate the process of switching between these segments adaptively in real time. To achieve this, ABR streaming relies on specific protocols. The two most common ABR protocols are:</p>

<ul>
<li>MPEG-DASH,</li>
<li>HTTP Live Streaming (HLS).</li>
</ul>

<p>Both of these protocols utilize HTTP to send video files. Hence, they are compatible with HTTP web servers.</p>

<p>This article focuses on MPEG-DASH. However, it’s worth noting that DASH isn’t supported by Apple devices or browsers, as mentioned in <a href="https://www.mux.com/articles/hls-vs-dash-what-s-the-difference-between-the-video-streaming-protocols">Mux’s article</a>.</p>

<h3 id="mpeg-dash">MPEG-DASH</h3>

<p>MPEG-DASH enables adaptive streaming through:</p>

<ul>
<li><strong>A Media Presentation Description (MPD) file</strong><br />
This XML manifest file contains information on how to select and manage streams based on adaptive rules.</li>
<li><strong>Segmented Media Files</strong><br />
Video and audio files are divided into segments at different resolutions and durations using MPEG-DASH-compliant codecs and formats.</li>
</ul>

<p>On the client side, a DASH-compliant video player reads the MPD file and continuously monitors network bandwidth. Based on available bandwidth, the player selects the appropriate bitrate and requests the corresponding video chunk. This process repeats throughout playback, ensuring smooth, optimal quality.</p>

<p>Now that you understand the fundamentals, let’s build our adaptive video player!</p>

<h2 id="steps-to-build-an-adaptive-bitrate-streaming-video-player">Steps To Build an Adaptive Bitrate Streaming Video Player</h2>

<p>Here’s the plan:</p>

<ol>
<li>Transcode the MP4 video into audio and video renditions at different resolutions and bitrates with FFmpeg.</li>
<li>Generate an MPD file with FFmpeg.</li>
<li>Serve the output files from the server.</li>
<li>Build the DASH-compatible video player to play the video.</li>
</ol>

<h3 id="install-ffmpeg">Install FFmpeg</h3>

<p>For macOS users, install FFmpeg using Brew by running the following command in your terminal:</p>

<pre><code class="language-bash">brew install ffmpeg
</code></pre>

<p>For other operating systems, please <a href="https://www.ffmpeg.org/download.html">refer to FFmpeg’s documentation</a>.</p>

<h3 id="generate-audio-rendition">Generate Audio Rendition</h3>

<p>Next, run the following script to extract the audio track and encode it in WebM format for DASH compatibility:</p>

<div class="break-out">
<pre><code class="language-bash">ffmpeg -i "input_video.mp4" -vn -acodec libvorbis -ab 128k "audio.webm"
</code></pre>
</div>

<ul>
<li><code>-i &quot;input_video.mp4&quot;</code>: Specifies the input video file.</li>
<li><code>-vn</code>: Disables the video stream (audio-only output).</li>
<li><code>-acodec libvorbis</code>: Uses the <a href="https://ffmpeg.org/ffmpeg-codecs.html#libvorbis"><strong>libvorbis</strong></a> codec to encode audio.</li>
<li><code>-ab 128k</code>: Sets the audio bitrate to <strong>128 kbps</strong>.</li>
<li><code>&quot;audio.webm&quot;</code>: Specifies the output audio file in WebM format.</li>
</ul>

<h3 id="generate-video-renditions">Generate Video Renditions</h3>

<p>Run this script to create three video renditions with varying resolutions and bitrates. The largest resolution should match the input file size. For example, if the input video is <strong>576×1024</strong> at 30 frames per second (fps), the script generates renditions optimized for vertical video playback.</p>

<div class="break-out">
<pre><code class="language-bash">ffmpeg -i "input_video.mp4" -c:v libvpx-vp9 -keyint_min 150 -g 150 \
-tile-columns 4 -frame-parallel 1 -f webm \
-an -vf scale=576:1024 -b:v 1500k "input_video_576x1024_1500k.webm" \
-an -vf scale=480:854 -b:v 1000k "input_video_480x854_1000k.webm" \
-an -vf scale=360:640 -b:v 750k "input_video_360x640_750k.webm"
</code></pre>
</div>

<ul>
<li><code>-c:v libvpx-vp9</code>: Uses the <a href="https://trac.ffmpeg.org/wiki/Encode/VP9"><strong>libvpx-vp9</strong></a> as the VP9 video encoder for WebM.</li>
<li><code>-keyint_min 150</code> and <code>-g 150</code>: Set a <strong>150-frame keyframe interval</strong> (approximately every 5 seconds at 30 fps). This allows bitrate switching every 5 seconds.</li>
<li><code>-tile-columns 4</code> and <code>-frame-parallel 1</code>: Optimize encoding performance through parallel processing.</li>
<li><code>-f webm</code>: Specifies the output format as WebM.</li>
</ul>

<p>In each rendition:</p>

<ul>
<li><code>-an</code>: Excludes audio (video-only output).</li>
<li><code>-vf scale=576:1024</code>: Scales the video to a resolution of <strong>576x1024</strong> pixels.</li>
<li><code>-b:v 1500k</code>: Sets the video bitrate to <strong>1500 kbps</strong>.</li>
</ul>

<p>WebM is chosen as the output format, as they are smaller in size and optimized yet widely compatible with most web browsers.</p>

<h3 id="generate-mpd-manifest-file">Generate MPD Manifest File</h3>

<p>Combine the video renditions and audio track into a DASH-compliant MPD manifest file by running the following script:</p>

<div class="break-out">
<pre><code class="language-bash">ffmpeg \
  -f webm_dash_manifest -i "input_video_576x1024_1500k.webm" \
  -f webm_dash_manifest -i "input_video_480x854_1000k.webm" \
  -f webm_dash_manifest -i "input_video_360x640_750k.webm" \
  -f webm_dash_manifest -i "audio.webm" \
  -c copy \
  -map 0 -map 1 -map 2 -map 3 \
  -f webm_dash_manifest \
  -adaptation_sets "id=0,streams=0,1,2 id=1,streams=3" \
  "input_video_manifest.mpd"
</code></pre>
</div>

<ul>
<li><code>-f webm_dash_manifest -i &quot;…&quot;</code>: Specifies the inputs so that the ASH video player will switch between them dynamically based on network conditions.</li>
<li><code>-map 0 -map 1 -map 2 -map 3</code>: Includes all video (0, 1, 2) and audio (3) in the final manifest.</li>
<li><code>-adaptation_sets</code>: Groups streams into adaptation sets:

<ul>
<li><code>id=0,streams=0,1,2</code>: Groups the video renditions into a single adaptation set.</li>
<li><code>id=1,streams=3</code>: Assigns the audio track to a separate adaptation set.</li>
</ul></li>
</ul>

<p>The resulting MPD file (<code>input_video_manifest.mpd</code>) describes the streams and enables adaptive bitrate streaming in MPEG-DASH.</p>

<div class="break-out">
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;MPD
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="urn:mpeg:DASH:schema:MPD:2011"
  xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011"
  type="static"
  mediaPresentationDuration="PT81.166S"
  minBufferTime="PT1S"
  profiles="urn:mpeg:dash:profile:webm-on-demand:2012"&gt;

  &lt;Period id="0" start="PT0S" duration="PT81.166S"&gt;
    &lt;AdaptationSet
      id="0"
      mimeType="video/webm"
      codecs="vp9"
      lang="eng"
      bitstreamSwitching="true"
      subsegmentAlignment="false"
      subsegmentStartsWithSAP="1"&gt;
      
      &lt;Representation id="0" bandwidth="1647920" width="576" height="1024"&gt;
        &lt;BaseURL&gt;input_video_576x1024_1500k.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="16931581-16931910"&gt;
          &lt;Initialization range="0-645" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
      &lt;Representation id="1" bandwidth="1126977" width="480" height="854"&gt;
        &lt;BaseURL&gt;input_video_480x854_1000k.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="11583599-11583986"&gt;
          &lt;Initialization range="0-645" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
      &lt;Representation id="2" bandwidth="843267" width="360" height="640"&gt;
        &lt;BaseURL&gt;input_video_360x640_750k.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="8668326-8668713"&gt;
          &lt;Initialization range="0-645" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
    &lt;/AdaptationSet&gt;
    
    &lt;AdaptationSet
      id="1"
      mimeType="audio/webm"
      codecs="vorbis"
      lang="eng"
      audioSamplingRate="44100"
      bitstreamSwitching="true"
      subsegmentAlignment="true"
      subsegmentStartsWithSAP="1"&gt;
      
      &lt;Representation id="3" bandwidth="89219"&gt;
        &lt;BaseURL&gt;audio.webm&lt;/BaseURL&gt;
        &lt;SegmentBase indexRange="921727-922055"&gt;
          &lt;Initialization range="0-4889" /&gt;
        &lt;/SegmentBase&gt;
      &lt;/Representation&gt;
      
    &lt;/AdaptationSet&gt;
  &lt;/Period&gt;
&lt;/MPD&gt;
</code></pre>
</div>

<p>After completing these steps, you’ll have:</p>

<ol>
<li>Three video renditions (<code>576x1024</code>, <code>480x854</code>, <code>360x640</code>),</li>
<li>One audio track, and</li>
<li>An MPD manifest file.</li>
</ol>

<pre><code class="language-bash">input&#95;video.mp4
audio.webm
input&#95;video&#95;576x1024&#95;1500k.webm
input&#95;video&#95;480x854&#95;1000k.webm
input&#95;video&#95;360x640&#95;750k.webm
input&#95;video&#95;manifest.mpd
</code></pre>

<p>The original video <code>input_video.mp4</code> should also be kept to serve as a fallback video source later.</p>

<h3 id="serve-the-output-files">Serve The Output Files</h3>

<p>These output files can now be uploaded to cloud storage (e.g., AWS S3 or Cloudflare R2) for playback. While they can be served directly from a local folder, I highly recommend storing them in cloud storage and leveraging a CDN to cache the assets for better performance. Both AWS and Cloudflare support HTTP range requests out of the box.</p>

<h3 id="building-the-dash-compatible-video-player-in-react">Building The DASH-Compatible Video Player In React</h3>

<p>There’s nothing like a real-world example to help understand how everything works. There are different ways we can implement a DASH-compatible video player, but I’ll focus on an approach using React.</p>

<p>First, install the <a href="https://github.com/Dash-Industry-Forum/dash.js">Dash.js</a> npm package by running:</p>

<pre><code class="language-bash">npm i dashjs
</code></pre>

<p>Next, create a component called <code>&lt;DashVideoPlayer /&gt;</code> and initialize the Dash <a href="https://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html">MediaPlayer</a> instance by pointing it to the MPD file when the component mounts.</p>

<p class="c-pre-sidenote--left">The ref callback function runs upon the component mounting, and within the callback function, <code>playerRef</code> will refer to the actual Dash <a href="https://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html">MediaPlayer</a> instance and be bound with event listeners. We also include the original MP4 URL in the <code>&lt;source&gt;</code> element as a fallback if the browser doesn’t support MPEG-DASH.</p>
<p class="c-sidenote c-sidenote--right">If you’re using <strong>Next.js app router</strong>, remember to add the <code>‘use client’</code> directive to enable client-side hydration, as the video player is only initialized on the client side.</p>

<p>Here is the full example:</p>

<div class="break-out">
<pre><code class="language-javascript">import dashjs from 'dashjs'
import { useCallback, useRef } from 'react'

export const DashVideoPlayer = () =&gt; {
  const playerRef = useRef()

  const callbackRef = useCallback((node) =&gt; {
    if (node !== null) {  
      playerRef.current = dashjs.MediaPlayer().create()

      playerRef.current.initialize(node, "https://example.com/uri/to/input&#95;video&#95;manifest.mpd", false)
  
      playerRef.current.on('canPlay', () =&gt; {
        // upon video is playable
      })
  
      playerRef.current.on('error', (e) =&gt; {
        // handle error
      })
  
      playerRef.current.on('playbackStarted', () =&gt; {
        // handle playback started
      })
  
      playerRef.current.on('playbackPaused', () =&gt; {
        // handle playback paused
      })
  
      playerRef.current.on('playbackWaiting', () =&gt; {
        // handle playback buffering
      })
    }
  },[])

  return (
    &lt;video ref={callbackRef} width={310} height={548} controls&gt;
      &lt;source src="https://example.com/uri/to/input&#95;video.mp4" type="video/mp4" /&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
  )
}</code></pre>
</div>

<h3 id="result">Result</h3>

<p>Observe the changes in the video file when the network connectivity is adjusted from Fast 4G to 3G using Chrome DevTools. It switches from 480p to 360p, showing how the experience is optimized for more or less available bandwidth.</p>


<figure class="video-embed-container break-out">
  <div class="video-embed-container--wrapper"
	
  >
    <iframe class="video-embed-container--wrapper-iframe" src="https://player.vimeo.com/video/1070045535"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>ABR example</figcaption>
	
</figure>

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

<p>That’s it! We just implemented a working DASH-compatible video player in React to establish a video with adaptive bitrate streaming. Again, the benefits of this are rooted in <strong>performance</strong>. When we adopt ABR streaming, we’re requesting the video in smaller chunks, allowing for more immediate playback than we’d get if we needed to fully download the video file first. And we’ve done it in a way that supports multiple versions of the same video, allowing us to serve the best format for the user’s device.</p>

<h3 id="references">References</h3>

<ul>
<li>“<a href="https://www.zeng.dev/post/2023-http-range-and-play-mp4-in-browser/">Http Range Request And MP4 Video Play In Browser</a>,” Zeng Xu</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Media/Audio_and_video_delivery/Setting_up_adaptive_streaming_media_sources">Setting up adaptive streaming media sources</a> (Mozilla Developer Network)</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Media/DASH_Adaptive_Streaming_for_HTML_5_Video">DASH Adaptive Streaming for HTML video</a> (Mozilla Developer Network)</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></channel></rss>