<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>Mon, 09 Feb 2026 03:03:08 +0000</lastBuildDate><item><author>Blake Lundquist</author><title>CSS &lt;code>@scope&lt;/code>: An Alternative To Naming Conventions And Heavy Abstractions</title><link>https://www.smashingmagazine.com/2026/02/css-scope-alternative-naming-conventions/</link><pubDate>Thu, 05 Feb 2026 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/02/css-scope-alternative-naming-conventions/</guid><description>Prescriptive class name conventions are no longer enough to keep CSS maintainable in a world of increasingly complex interfaces. Can the new &lt;code>@scope&lt;/code> rule finally give developers the confidence to write CSS that can keep up with modern front ends?</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/02/css-scope-alternative-naming-conventions/" />
              <title>CSS &lt;code&gt;@scope&lt;/code&gt;: An Alternative To Naming Conventions And Heavy Abstractions</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>CSS &lt;code&gt;@scope&lt;/code&gt;: An Alternative To Naming Conventions And Heavy Abstractions</h1>
                  
                    
                    <address>Blake Lundquist</address>
                  
                  <time datetime="2026-02-05T08:00:00&#43;00:00" class="op-published">2026-02-05T08:00:00+00:00</time>
                  <time datetime="2026-02-05T08:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>When learning the principles of basic CSS, one is taught to write modular, reusable, and descriptive styles to ensure maintainability. But when developers become involved with real-world applications, it often feels impossible to add UI features without styles leaking into unintended areas.</p>

<p>This issue often snowballs into a self-fulfilling loop; styles that are theoretically scoped to one element or class start showing up where they don’t belong. This forces the developer to create even more specific selectors to override the leaked styles, which then accidentally override global styles, and so on.</p>

<p>Rigid class name conventions, such as <a href="https://getbem.com/introduction/">BEM</a>, are one theoretical solution to this issue. The <strong>BEM (Block, Element, Modifier) methodology</strong> is a <a href="https://www.smashingmagazine.com/2012/04/a-new-front-end-methodology-bem/">systematic way of naming CSS classes</a> to ensure reusability and structure within CSS files. Naming conventions like this can <a href="https://www.smashingmagazine.com/2018/06/bem-for-beginners/">reduce cognitive load by leveraging domain language to describe elements and their state</a>, and if implemented correctly, <a href="https://www.smashingmagazine.com/2025/06/css-cascade-layers-bem-utility-classes-specificity-control/">can make styles for large applications easier to maintain</a>.</p>

<p>In the real world, however, it doesn’t always work out like that. Priorities can change, and with change, implementation becomes inconsistent. Small changes to the HTML structure can require many CSS class name revisions. With highly interactive front-end applications, class names following the BEM pattern can become long and unwieldy (e.g., <code>app-user-overview__status--is-authenticating</code>), and not fully adhering to the naming rules breaks the system’s structure, thereby negating its benefits.</p>

<p>Given these challenges, it’s no wonder that developers have turned to frameworks, Tailwind being <a href="https://2024.stateofcss.com/en-US/tools/">the most popular CSS framework</a>. Rather than trying to fight what seems like an unwinnable specificity war between styles, it is easier to give up on the <a href="https://css-tricks.com/the-c-in-css-the-cascade/">CSS Cascade</a> and use tools that guarantee complete isolation.</p>

<h2 id="developers-lean-more-on-utilities">Developers Lean More On Utilities</h2>

<p>How do we know that some developers are keen on avoiding cascaded styles? It’s the rise of “modern” front-end tooling &mdash; like <a href="https://www.smashingmagazine.com/2016/04/finally-css-javascript-meet-cssx/">CSS-in-JS frameworks</a> &mdash; designed specifically for that purpose. Working with isolated styles that are tightly scoped to specific components can seem like a breath of fresh air. It removes the need to name things &mdash; <a href="https://24ways.org/2014/naming-things/">still one of the most hated and time-consuming front-end tasks</a> &mdash; and allows developers to be productive without fully understanding or leveraging the benefits of CSS inheritance.</p>

<p>But ditching the CSS Cascade comes with its own problems. For instance, composing styles in JavaScript requires heavy build configurations and often leads to styles awkwardly intermingling with component markup or HTML. Instead of carefully considered naming conventions, we allow build tools to autogenerate selectors and identifiers for us (e.g., <code>.jsx-3130221066</code>), requiring developers to keep up with yet another pseudo-language in and of itself. (As if the cognitive load of understanding what all your component’s <code>useEffect</code>s do weren’t already enough!)</p>

<p>Further abstracting the job of naming classes to tooling means that basic debugging is often constrained to specific application versions compiled for development, rather than leveraging native browser features that support live debugging, such as Developer Tools.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aIt%e2%80%99s%20almost%20like%20we%20need%20to%20develop%20tools%20to%20debug%20the%20tools%20we%e2%80%99re%20using%20to%20abstract%20what%20the%20web%20already%20provides%20%e2%80%94%20all%20for%20the%20sake%20of%20running%20away%20from%20the%20%e2%80%9cpain%e2%80%9d%20of%20writing%20standard%20CSS.%0a&url=https://smashingmagazine.com%2f2026%2f02%2fcss-scope-alternative-naming-conventions%2f">
      
It’s almost like we need to develop tools to debug the tools we’re using to abstract what the web already provides — all for the sake of running away from the “pain” of writing standard CSS.

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

<p>Luckily, modern CSS features not only make writing standard CSS more flexible but also give developers like us a great deal more power to manage the cascade and make it work for us. <a href="https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/">CSS Cascade Layers</a> are a great example, but there’s another feature that gets a surprising lack of attention &mdash; although that is changing now that it has recently become <strong>Baseline compatible</strong>.</p>

<h2 id="the-css-scope-at-rule">The CSS <code>@scope</code> At-Rule</h2>

<p>I consider the <strong>CSS <code>@scope</code> at-rule</strong> to be a potential cure for the sort of style-leak-induced anxiety we’ve covered, one that does not force us to compromise native web advantages for abstractions and extra build tooling.</p>

<blockquote>“The <code>@scope</code> CSS at-rule enables you to select elements in specific DOM subtrees, targeting elements precisely without writing overly-specific selectors that are hard to override, and without coupling your selectors too tightly to the DOM structure.”<br /><br />&mdash; <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@scope">MDN</a></blockquote>

<p>In other words, we can work with isolated styles in specific instances <strong>without sacrificing inheritance, cascading, or even the basic separation of concerns</strong> that has been a long-running guiding principle of front-end development.</p>

<p>Plus, it has <a href="https://caniuse.com/css-cascade-scope">excellent browser coverage</a>. In fact, <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/146">Firefox 146</a> added support for <code>@scope</code> in December, making it <a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility">Baseline compatible</a> for the first time. Here is a simple comparison between a button using the BEM pattern versus the <code>@scope</code> rule:</p>

<pre><code class="language-html">&lt;!-- BEM --&gt; 
&lt;button class="button button--primary"&gt;
  &lt;span class="button&#95;&#95;text"&gt;Click me&lt;/span&gt;
  &lt;span class="button&#95;&#95;icon"&gt;→&lt;/span&gt;
&lt;/button&gt;

&lt;style&gt;
  .button .button&#95;&#95;text { /&#42; button text styles &#42;/ }
  .button .button&#95;&#95;icon { /&#42; button icon styles &#42;/ }
  .button--primary { primary button styles &#42;/ }
&lt;/style&gt;
</code></pre>

<pre><code class="language-html">&lt;!-- @scope --&gt; 
&lt;button class="primary-button"&gt;
  &lt;span&gt;Click me&lt;/span&gt;
  &lt;span&gt;→&lt;/span&gt;
&lt;/button&gt;

&lt;style&gt;
  @scope (.primary-button) {
    span:first-child { /&#42; button text styles &#42;/ }
    span:last-child { /&#42; button icon styles &#42;/ }
  }
&lt;/style&gt;
</code></pre>

<p>The <code>@scope</code> rule allows for <strong>precision with less complexity</strong>. The developer no longer needs to create boundaries using class names, which, in turn, allows them to write selectors based on native HTML elements, thereby eliminating the need for prescriptive CSS class name patterns. By simply removing the need for class name management, <code>@scope</code> can alleviate the fear associated with CSS in large projects.</p>

<h2 id="basic-usage">Basic Usage</h2>

<p>To get started, add the <code>@scope</code> rule to your CSS and insert a root selector to which styles will be scoped:</p>

<pre><code class="language-css">@scope (&lt;selector&gt;) {
  /&#42; Styles scoped to the &lt;selector&gt; &#42;/
}
</code></pre>

<p>So, for example, if we were to scope styles to a <code>&lt;nav&gt;</code> element, it may look something like this:</p>

<div class="break-out">
<pre><code class="language-css">@scope (nav) {
  a { /&#42; Link styles within nav scope &#42;/ }

  a:active { /&#42; Active link styles &#42;/ }

  a:active::before { /&#42; Active link with pseudo-element for extra styling &#42;/ }

  @media (max-width: 768px) {
    a { /&#42; Responsive adjustments &#42;/ }
  }
}
</code></pre>
</div>

<p>This, on its own, is not a groundbreaking feature. However, a second argument can be added to the scope to create a <strong>lower boundary</strong>, effectively defining the scope’s start and end points.</p>

<div class="break-out">
<pre><code class="language-css">/&#42; Any `a` element inside `ul` will not have the styles applied &#42;/
@scope (nav) to (ul) {
  a {
    font-size: 14px;
  }
}
</code></pre>
</div>

<p>This practice is called <strong>donut scoping</strong>, and <a href="https://css-tricks.com/solved-by-css-donuts-scopes/">there are several approaches</a> one could use, including a series of similar, highly specific selectors coupled tightly to the DOM structure, a <code>:not</code> pseudo-selector, or assigning specific class names to <code>&lt;a&gt;</code> elements within the <code>&lt;nav&gt;</code> to handle the differing CSS.</p>

<p>Regardless of those other approaches, the <code>@scope</code> method is much more concise. More importantly, it prevents the risk of broken styles if classnames change or are misused or if the HTML structure were to be modified. Now that <code>@scope</code> is Baseline compatible, we no longer need workarounds!</p>

<p>We can take this idea further with multiple end boundaries to create a “style figure eight”:</p>

<div class="break-out">
<pre><code class="language-css">/&#42; Any &lt;a&gt; or &lt;p&gt; element inside &lt;aside&gt; or &lt;nav&gt; will not have the styles applied &#42;/
@scope (main) to (aside, nav) {
  a {
    font-size: 14px;
  }
  p {
    line-height: 16px;
    color: darkgrey;
  }
}
</code></pre>
</div>

<p>Compare that to a version handled without the <code>@scope</code> rule, where the developer has to “reset” styles to their defaults:</p>

<div class="break-out">
<pre><code class="language-css">main a {
  font-size: 14px;
}

main p {
  line-height: 16px;
  color: darkgrey;
}

main aside a,
main nav a {
  font-size: inherit; /&#42; or whatever the default should be &#42;/
}

main aside p,
main nav p {
  line-height: inherit; /&#42; or whatever the default should be &#42;/
  color: inherit; /&#42; or a specific color &#42;/
}
</code></pre>
</div>

<p>Check out the following example. Do you notice how simple it is to target some nested selectors while exempting others?</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="wBWXggN"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [@scope example [forked]](https://codepen.io/smashingmag/pen/wBWXggN) by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/wBWXggN">@scope example [forked]</a> by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</figcaption>
</figure>

<p>Consider a scenario where unique styles need to be applied to slotted content within <a href="https://www.smashingmagazine.com/2025/07/web-components-working-with-shadow-dom/">web components</a>. When slotting content into a web component, that content becomes part of the Shadow DOM, but still inherits styles from the parent document. The developer might want to implement different styles depending on which web component the content is slotted into:</p>

<pre><code class="language-html">&lt;!-- Same &lt;user-card&gt; content, different contexts --&gt;
&lt;product-showcase&gt;
  &lt;user-card slot="reviewer"&gt;
    &lt;img src="avatar.jpg" slot="avatar"&gt;
    &lt;span slot="name"&gt;Jane Doe&lt;/span&gt;
  &lt;/user-card&gt;
&lt;/product-showcase&gt;

&lt;team-roster&gt;
  &lt;user-card slot="member"&gt;
    &lt;img src="avatar.jpg" slot="avatar"&gt;
    &lt;span slot="name"&gt;Jane Doe&lt;/span&gt;
  &lt;/user-card&gt;
&lt;/team-roster&gt;
</code></pre>

<p>In this example, the developer might want the <code>&lt;user-card&gt;</code> to have distinct styles only if it is rendered inside <code>&lt;team-roster&gt;</code>:</p>

<pre><code class="language-css">@scope (team-roster) {
  user-card {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
  }
  
  user-card img {
    border-radius: 50%;
    width: 40px;
    height: 40px;
  }
}
</code></pre>

<h2 id="more-benefits">More Benefits</h2>

<p>There are additional ways that <code>@scope</code> can remove the need for class management without resorting to utilities or JavaScript-generated class names. For example, <code>@scope</code> opens up the possibility to easily <strong>target descendants of any selector</strong>, not just class names:</p>

<div class="break-out">
<pre><code class="language-css">/&#42; Only div elements with a direct child button are included in the root scope &#42;/
@scope (div:has(&gt; button)) {
  p {
    font-size: 14px;
  }
}
</code></pre>
</div>

<p>And they <strong>can be nested</strong>, creating scopes within scopes:</p>

<pre><code class="language-css">@scope (main) {
  p {
    font-size: 16px;
    color: black;
  }
  @scope (section) {
    p {
      font-size: 14px;
      color: blue;
    }
    @scope (.highlight) {
      p {
        background-color: yellow;
        font-weight: bold;
      }
    }
  }
}
</code></pre>

<p>Plus, the root scope can be easily referenced within the <code>@scope</code> rule:</p>

<div class="break-out">
<pre><code class="language-css">/&#42; Applies to elements inside direct child `section` elements of `main`, but stops at any direct `aside` that is a direct chiled of those sections &#42;/
@scope (main &gt; section) to (:scope &gt; aside) {
  p {
    background-color: lightblue;
    color: blue;
  }
  /&#42; Applies to ul elements that are immediate siblings of root scope  &#42;/
  :scope + ul {
    list-style: none;
  }
}
</code></pre>
</div>

<p>The <code>@scope</code> at-rule also introduces a new <strong>proximity</strong> dimension to CSS specificity resolution. In traditional CSS, when two selectors match the same element, the selector with the higher specificity wins. With <code>@scope</code>, when two elements have equal specificity, the one whose scope root is closer to the matched element wins. This eliminates the need to override parent styles by manually increasing an element’s specificity, since inner components naturally supersede outer element styles.</p>

<div class="break-out">
<pre><code class="language-html">&lt;style&gt;
  @scope (.container) {
    .title { color: green; } 
  }
  &lt;!-- The &lt;h2&gt; is closer to .container than to .sidebar so "color: green" wins. --&gt;
  @scope (.sidebar) {
    .title { color: red; }
  }
&lt;/style&gt;

&lt;div class="sidebar"&gt;
  &lt;div class="container"&gt;
    &lt;h2 class="title"&gt;Hello&lt;/h2&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
</div>
    

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

<p>Utility-first CSS frameworks, such as Tailwind, work well for prototyping and smaller projects. Their benefits quickly diminish, however, when used in larger projects involving more than a couple of developers.</p>

<p>Front-end development has become increasingly overcomplicated in the last few years, and CSS is no exception. While the <code>@scope</code> rule isn’t a cure-all, it can reduce the need for complex tooling. When used in place of, or alongside strategic class naming, <code>@scope</code> can make it easier and more fun to write maintainable CSS.</p>

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

<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@scope">CSS <code>@scope</code></a> (MDN)</li>
<li>“<a href="https://css-tricks.com/almanac/rules/s/scope/">CSS <code>@scope</code></a>”, Juan Diego Rodríguez (CSS-Tricks)</li>
<li><a href="https://www.firefox.com/en-US/firefox/146.0/releasenotes/">Firefox 146 Release Notes</a> (Firefox)</li>
<li><a href="https://caniuse.com/css-cascade-scope">Browser Support</a> (CanIUse)</li>
<li><a href="https://2024.stateofcss.com/en-US/tools/">Popular CSS Frameworks</a> (State of CSS 2024)</li>
<li>“<a href="https://css-tricks.com/the-c-in-css-the-cascade/">The “C” in CSS: Cascade</a>”, Thomas Yip (CSS-Tricks)</li>
<li><a href="https://getbem.com/introduction/">BEM Introduction</a> (Get BEM)</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>Stefan Kaltenegger</author><title>Practical Use Of AI Coding Tools For The Responsible Developer</title><link>https://www.smashingmagazine.com/2026/01/practical-use-ai-coding-tools-responsible-developer/</link><pubDate>Fri, 30 Jan 2026 13:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/01/practical-use-ai-coding-tools-responsible-developer/</guid><description>AI coding tools like agents can be valuable allies in everyday development work. They help handle time-consuming grunt work, guide you through large legacy codebases, and offer low-risk ways to implement features in previously unfamiliar programming languages. Here are practical, easy-to-apply techniques to help you use these tools to improve your workflow.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/01/practical-use-ai-coding-tools-responsible-developer/" />
              <title>Practical Use Of AI Coding Tools For The Responsible Developer</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Practical Use Of AI Coding Tools For The Responsible Developer</h1>
                  
                    
                    <address>Stefan Kaltenegger</address>
                  
                  <time datetime="2026-01-30T13:00:00&#43;00:00" class="op-published">2026-01-30T13:00:00+00:00</time>
                  <time datetime="2026-01-30T13:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Over the last two years, my team at <a href="https://work.co/">Work &amp; Co</a> and I have been testing out and gradually integrating AI coding tools like Copilot, Cursor, Claude, and ChatGPT to help us ship web experiences that are used by the masses. Admittedly, after some initial skepticism and a few aha moments, various AI tools have found their way into my daily use. Over time, the list of applications where we found it made sense to let AI take over started to grow, so I decided to share some <strong>practical use cases</strong> for AI tools for what I call the “responsible developer”.</p>

<p>What do I mean by a <strong>responsible developer</strong>?</p>

<p>We have to make sure that we deliver quality code as expected by our stakeholders and clients. Our contributions (i.e., pull requests) should not become a burden on our colleagues who will have to review and test our work. Also, in case you work for a company: The tools we use need to be approved by our employer. Sensitive aspects like security and privacy need to be handled properly: Don’t paste secrets, customer data (PII), or proprietary code into tools without policy approval. Treat it like code from a stranger on the internet. Always test and verify.</p>

<p><strong>Note</strong>: <em>This article assumes some very basic familiarity with AI coding tools like Copilot inside VSCode or Cursor. If all of this sounds totally new and unfamiliar to you, the <a href="https://github.com/features/copilot/tutorials">Github Copilot video tutorials</a> can be a fantastic starting point for you.</em></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="518"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png"
			
			sizes="100vw"
			alt="Screenshot of of VSCode with Copilot Chat open in the right panel"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      View of VSCode with Copilot Chat open in the right panel. (<a href='https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/01-vscode-copilot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="helpful-applications-of-ai-coding-tools">Helpful Applications Of AI Coding Tools</h2>

<p><strong>Note</strong>: The following examples will mainly focus on working in JavaScript-based web applications like React, Vue, Svelte, or Angular.</p>

<h3 id="getting-an-understanding-of-an-unfamiliar-codebase">Getting An Understanding Of An Unfamiliar Codebase</h3>

<p>It’s not uncommon to work on established codebases, and joining a large legacy codebase can be intimidating. Simply open your project and your AI agent (in my case, Copilot Chat in VSCode) and start asking questions just like you would ask a colleague. In general, I like to talk to any AI agent just as I would to a fellow human.</p>

<p>Here is a more refined example prompt:</p>

<blockquote>“Give me a high-level architecture overview: entrypoints, routing, auth, data layer, build tooling. Then list 5 files to read in order. Treat explanations as hypotheses and confirm by jumping to referenced files.”</blockquote>

<p>You can keep asking follow-up questions like <em>“How does the routing work in detail?”</em> or <em>“Talk me through the authentication process and methods”</em> and it will lead you to helpful directions to shine some light into the dark of an unfamiliar codebase.</p>

<h3 id="triaging-breaking-changes-when-upgrading-dependencies">Triaging Breaking Changes When Upgrading Dependencies</h3>

<p>Updating npm packages, especially when they come with breaking changes, can be tedious and time-consuming work, and make you debug a fair amount of regressions. I recently had to upgrade the data visualization library <a href="plotly.js">plotly.js</a> up one major release version from version 2 to 3, and as a result of that, the axis labeling in some of the graphs stopped working.</p>

<p>I went on to ask ChatGPT:</p>

<blockquote>“I updated my Angular project that uses Plotly. I updated the plotly.js &mdash; dist package from version 2.35.2 to 3.1.0 &mdash; and now the labels on the x and y axis are gone. What happened?”</blockquote>

<p>The agent came back with a solution promptly (see for yourself below).</p>

<p><strong>Note</strong>: <em>I still verified the explanation against the official migration guide before shipping the fix.</em></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="647"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png"
			
			sizes="100vw"
			alt="Screenshot of the response from ChatGPT when prompted “I updated my Angular project that uses plotly. I updated the plotly package from plotly.js-dist: ^2.35.2 to plotly.js-dist: ^3.1.0, - and now the labels on the x and y axis are gone - what happened? Thought for 19s. Short answer: Plotly.js v3 removed the string shorthand for titles. You must use the new object form.”"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/02-chatgpt-plotly.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="replicating-refactors-safely-across-files">Replicating Refactors Safely Across Files</h3>

<p>Growing codebases most certainly unveil opportunities for code consolidation. For example, you notice code duplication across files that can be extracted into a single function or component. As a result, you decide to create a shared component that can be included instead and perform that refactor in one file. Now, instead of manually carrying out those changes to your remaining files, you ask your agent to roll out the refactor for you.</p>

<p>Agents let you select multiple files as context. Once the refactor for one file is done, I can add both the refactored and untouched files into context and prompt the agent to roll out the changes to other files like this: <em>“Replicate the changes I made in file A to file B as well”</em>.</p>

<h3 id="implementing-features-in-unfamiliar-technologies">Implementing Features In Unfamiliar Technologies</h3>

<p>One of my favorite aha-moments using AI coding tools was when it helped me create a quite complex animated gradient animation in GLSL, a language I have been fairly unfamiliar with. On a recent project, our designers came up with an animated gradient as a loading state on a 3D object. I really liked the concept and wanted to deliver something unique and exciting to our clients. The problem: I only had two days to implement it, and GLSL has quite the steep learning curve.</p>

<p>Again, an AI tool (in this case, ChatGPT) came in handy, and I started quite simply prompting it to create a standalone HTML file for me that renders a canvas and a very simple animated color gradient. Step after step, I prompted the AI to add more finesse to it until I arrived at a decent result so I could start integrating the shader into my actual codebase.</p>

<p>The end result: Our clients were super happy, and we delivered a complex feature in a small amount of time thanks to AI.</p>

<h3 id="writing-tests">Writing Tests</h3>

<p>In my experience, there’s rarely enough time on projects to continuously write and maintain a proper suite of unit and integration tests, and on top of that, many developers don’t really enjoy the task of writing tests. Prompting your AI helper to set up and write tests for you is entirely possible and can be done in a small amount of time. Of course, you, as a developer, should still make sure that your tests actually take a look at the critical parts of your application and follow sensible testing principles, but you can “outsource” the writing of the tests to our AI helper.</p>

<p>Example prompt:</p>

<blockquote>“Write unit tests for this function using Jest. Cover happy path, edge cases, and failure modes. Explain why each test exists.”</blockquote>

<p>You can even pass along testing guru Kent C. Dodds’ testing best practices as guidelines to your agent, like below:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://x.com/kentcdodds/status/2008940437747409406">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="795"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png"
			
			sizes="100vw"
			alt="A post on X by @kentcdodds that reads “When telling AI to write tests, I find those tests improve a lot when I send: Could you apply these principles to the tests? https://kentcdodds.com/blog/avoid-nesting-when-youre-testing and https://epicweb.dev/better-test-setup-with-disposable-objects. Just did it and the tests got much more clear and terse.”"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Image source: <a href='https://x.com/kentcdodds/status/2008940437747409406'>x.com/kentcdodds</a>. (<a href='https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/03-post-testing.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="internal-tooling">Internal Tooling</h3>

<p>Somewhat similar to the shader example mentioned earlier, I was recently tasked to analyze code duplication in a codebase and compare before and after a refactor. Certainly not a trivial task if you don’t want to go the time-consuming route of comparing files manually. With the help of Copilot, I created a script that analyzed code duplication for me, arranged and ordered the output in a table, and exported it to Excel. Then I took it a step further. When our code refactor was done, I prompted the agent to take my existing Excel sheet as the baseline, add in the current state of duplication in separate columns, and calculate the delta.</p>

<h3 id="updating-code-written-a-long-time-ago">Updating Code Written A Long Time Ago</h3>

<p>Recently, an old client of mine hit me up, as over time, a few features weren’t working properly on his website anymore.</p>

<p>The catch: The website was built almost ten years ago, and the JavaScript and SCSS were using rather old compile tools like requireJS, and the setup required an older version of Node.js that wouldn’t even run on my 2025 MacBook.</p>

<p>Updating the whole build process by hand would have taken me days, so I decided to prompt the AI agent, <em>“Can you update the JS and SCSS build process to a lean 2025 stack like Vite?”</em> It sure did, and after around an hour of refining with the agent, I had my SCSS and JS build switched to Vite, and I was able to focus on actual bugfixing. Just make sure to properly validate the output and compiled files when doing such integral changes to your build process.</p>

<h3 id="summarizing-and-drafting">Summarizing And Drafting</h3>

<p>Would you like to summarize all your recent code changes in one sentence for a commit message, or have a long list of commits and would like to sum them up in three bullet points? No problem, let the AI take care of it, but please make sure to proofread it.</p>

<p>An example prompt is as simple as messaging a fellow human: <em>“Please sum up my recent changes in concise bullet points”</em>.</p>

<p>My advice here would be to use GPT for writing with caution, and as with code, please check the output before sending or submitting.</p>

<h2 id="recommendations-and-best-practices">Recommendations And Best Practices</h2>

<h3 id="prompting">Prompting</h3>

<p>One of the not-so-obvious benefits of using AI is that the more specific and tailored your prompts are, the better the output. The process of prompting an AI agent forces us to <strong>formulate our requirements as specifically as possible</strong> before we write and code. This is why, as a general rule, I highly recommend being as specific as possible with your prompting.</p>

<p>Ryan Florence, co-author of Remix, suggests a simple yet powerful way to improve this process by finishing your initial prompt with the sentence:</p>

<blockquote>“Before we start, do you have any questions for me?”</blockquote>

<p>At this point, the AI usually comes back with helpful questions where you can clarify your specific intent, guiding the agent to provide you with a <strong>more tailored approach</strong> for your task.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://x.com/ryanflorence/status/1959407056286568828">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="690"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png"
			
			sizes="100vw"
			alt="A post on X by @ryanflorence that reads: “I always ask Cursor: Do you have any questions before you begin to implement this? Usually, some good questions arise, I answer them, and have the model update the feature explanation doc and spec doc. Eventually, it has no more questions and often nails the sessions.”"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Image source: <a href='https://x.com/ryanflorence/status/1959407056286568828'>x.com/ryanflorence</a>. (<a href='https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/04-post-cursor.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="use-version-control-and-work-in-digestible-chunks">Use Version Control And Work In Digestible Chunks</h3>

<p>Using version control like git not only comes in handy when collaborating as a team on a single codebase but also to provide you as an individual contributor with stable points to roll back to in case of an emergency. Due to its non-deterministic nature, AI can sometimes go rogue and make changes that are simply not helpful for what you are trying to achieve and eventually break things irreparably.</p>

<p>Splitting up your work into <strong>multiple commits</strong> will help you create stable points that you can revert to in case things go sideways. And your teammates will thank you as well, as they will have an easier time reviewing your code when it is split up into semantically well-structured chunks.</p>

<h3 id="review-thoroughly">Review Thoroughly</h3>

<p>This is more of a general best practice, but in my opinion, it becomes even more important when using AI tools for development work: <strong>Be the first critical reviewer of your code</strong>. Make sure to take some time to go over your changes line by line, just like you would review someone else’s code, and only submit your work once it passes your own self-review.</p>

<blockquote>“Two things are both true to me right now: AI agents are amazing and a huge productivity boost. They are also massive slop machines if you turn off your brain and let go completely.”<br /><br />&mdash; Armin Ronacher in his blog post <a href="https://lucumr.pocoo.org/2026/1/18/agent-psychosis/">Agent Psychosis: Are We Going Insane?</a></blockquote>

<h2 id="conclusion-and-critical-thoughts">Conclusion And Critical Thoughts</h2>

<p>In my opinion, AI coding tools can improve our productivity as developers on a daily basis and free up mental capacity for more planning and high-level thinking. They force us to articulate our desired outcome with meticulous detail.</p>

<p>Any AI can, at times, hallucinate, which basically means it lies in a confident tone. So please make sure to check and test, especially when you are in doubt. AI is not a silver bullet, and I believe, excellence and the ability to solve problems as a developer will never go out of fashion.</p>

<p>For developers who are just starting out in their career these tools can be highly tempting to do the majority of the work for them. What may get lost here is the often draining and painful work through bugs and issues that are tricky to debug and solve, aka “the grind”. Even Cursor AI’s very own Lee Robinson questions this in one of his posts:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://x.com/leerob/status/1996281383535382909">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="1093"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png"
			
			sizes="100vw"
			alt="A post on X by @leerob that reads: “My biggest worries about coding with AI: 1. Beginners not actually learning 2. Atrophy of skills I’m seeing #1 happen, and I don’t have a good answer yet. Leveling up as an engineer requires grinding, and it’s not always fun. If AI can solve most of the problems for you, when do you lean into the healthy friction? When do you embrace the suck? Coupled with fewer opportunities for pair programming, it’s definitely tougher for those starting their engineering career.”"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Image source: <a href='https://x.com/leerob/status/1996281383535382909'>x.com/leerob</a>. (<a href='https://files.smashing.media/articles/practical-use-ai-coding-tools-responsible-developer/05-post-leerob.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>AI coding tools are evolving at a fast pace, and I am excited for what will come next. I hope you found this article and its tips helpful and are excited to try out some of these for yourself.</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>Gabriel Shoyombo</author><title>Unstacking CSS Stacking Contexts</title><link>https://www.smashingmagazine.com/2026/01/unstacking-css-stacking-contexts/</link><pubDate>Tue, 27 Jan 2026 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/01/unstacking-css-stacking-contexts/</guid><description>In CSS, we can create “stacking contexts” where elements are visually placed one on top of the next in a three-dimensional sense that creates the perception of depth. Stacking contexts are incredibly useful, but they’re also widely misunderstood and often mistakenly created, leading to a slew of layout issues that can be tricky to solve.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/01/unstacking-css-stacking-contexts/" />
              <title>Unstacking CSS Stacking Contexts</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Unstacking CSS Stacking Contexts</h1>
                  
                    
                    <address>Gabriel Shoyombo</address>
                  
                  <time datetime="2026-01-27T10:00:00&#43;00:00" class="op-published">2026-01-27T10:00:00+00:00</time>
                  <time datetime="2026-01-27T10:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                
                

<p>Have you ever set <code>z-index: 99999</code> on an element in your CSS, and it doesn’t come out on top of other elements? A value that large should easily place that element visually on top of anything else, assuming all the different elements are set at either a lower value or not set at all.</p>

<p>A webpage is usually represented in a two-dimensional space; however, by applying specific CSS properties, an imaginary z-axis plane is introduced to convey depth. This plane is perpendicular to the screen, and from it, the user perceives the order of elements, one on top of the other. The idea behind the imaginary z-axis, the user’s perception of stacked elements, is that the CSS properties that create it combine to form what we call a <strong>stacking context</strong>.</p>

<p>We’re going to talk about how elements are “stacked” on a webpage, what controls the stacking order, and practical approaches to “unstack” elements when needed.</p>

<h2 id="about-stacking-contexts">About Stacking Contexts</h2>

<p>Imagine your webpage as a desk. As you add HTML elements, you’re laying pieces of paper, one after the other, on the desk. The last piece of paper placed is equivalent to the most recently added HTML element, and it sits on top of all the other papers placed before it. This is the normal document flow, even for nested elements. The desk itself represents the root stacking context, formed by the <code>&lt;html&gt;</code> element, which contains all other folders.</p>

<p>Now, specific CSS properties come into play.</p>

<p>Properties like <code>position</code> (with <code>z-index</code>), <code>opacity</code>, <code>transform</code>, and <code>contain</code>) act like a folder. This folder takes an element and all of its children, extracts them from the main stack, and groups them into a separate sub-stack, creating what we call a <strong>stacking context</strong>. For positioned elements, this happens when we declare a <code>z-index</code> value other than <code>auto</code>. For properties like <code>opacity</code>, <code>transform</code>, and <code>filter</code>, the stacking context is created automatically when specific values are applied.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="436"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png"
			
			sizes="100vw"
			alt="Before (global stacking order) and after (stacking context order)"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      When the browser decides what goes on top, it stacks the folders first, not the individual papers inside them. This is “The Golden Rule” of stacking contexts that many developers miss. (<a href='https://files.smashing.media/articles/unstacking-css-stacking-contexts/1-stacking-context-order.png'>Large preview</a>)
    </figcaption>
  
</figure>

<blockquote>Try to understand this: Once a piece of paper (i.e., a child element) is inside a folder (i.e., the parent’s stacking context), it can never exit that folder or be placed between papers in a different folder. Its <code>z-index</code> is now only relevant inside its own folder.</blockquote>

<p>In the illustration below, Paper B is now within the stacking context of Folder B, and can only be ordered with other papers in the folder.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="436"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png"
			
			sizes="100vw"
			alt="Before (global stacking order) and after (stacking context order)"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/unstacking-css-stacking-contexts/2-stacking-contexts.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Imagine, if you will, that you have two folders on your desk:</p>

<pre><code class="language-html">&lt;div class="folder-a"&gt;Folder A&lt;/div&gt;
&lt;div class="folder-b"&gt;Folder B&lt;/div&gt;
</code></pre>

<pre><code class="language-css">.folder-a { z-index: 1; }
.folder-b { z-index: 2; }
</code></pre>

<p>Let’s update the markup a bit. Inside Folder A is a special page, <code>z-index: 9999</code>. Inside Folder B is a plain page, <code>z-index: 5</code>.</p>

<pre><code class="language-html">&lt;div class="folder-a"&gt;
   &lt;div class="special-page"&gt;Special Page&lt;/div&gt;
&lt;/div&gt;

&lt;div class="folder-b"&gt;
  &lt;div class="plain-page"&gt;Plain Page&lt;/div&gt;
&lt;/div&gt;
</code></pre>

<pre><code class="language-css">.special-page { z-index: 9999; }
.plain-page { z-index: 5; }
</code></pre>

<p>Which page is on top?</p>

<p>It’s the <code>.plain-page</code> in Folder B. The browser ignores the child papers and stacks the two folders first. It sees Folder B (<code>z-index: 2</code>) and places it on top of Folder A (<code>z-index: 1</code>) because we know that two is greater than one. Meanwhile, the <code>.special-page</code> set to <code>z-index: 9999</code> page is at the bottom of the stack even though its <code>z-index</code> is set to the highest possible value.</p>

<p>Stacking contexts can also be nested (folders inside folders), creating a “family tree.” The same principle applies: a child can never escape its parents’ folder.</p>

<p>Now that you get how stacking contexts behave like folders that group and reorder layers, it’s worth asking: why do certain properties &mdash; like <code>transform</code> and <code>opacity</code> &mdash; create new stacking contexts?</p>

<p>Here’s the thing: these properties don’t create stacking contexts because of how they look; they do it because of how the browser works under the hood. When you apply <code>transform</code>, <code>opacity</code>, <code>filter</code>, or <code>perspective</code>, you’re telling the browser, <em>“Hey, this element might move, rotate, or fade, so be ready!”</em></p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png">
    
    <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/unstacking-css-stacking-contexts/3-diagram-stacking-context.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png"
			
			sizes="100vw"
			alt="Diagram illustrating the main document layout with an applied transform that creates a new stacking context, which in turn, runs on the GPU to handle the transformation. It indicates that a new stacking context is promoted for performance."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/unstacking-css-stacking-contexts/3-diagram-stacking-context.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>When you use these properties, the browser creates a new stacking context to manage rendering more efficiently. This allows the browser to handle animations, transforms, and visual effects independently, reducing the need to recalculate how these elements interact with the rest of the page. Think of it as the browser saying, <em>“I’ll handle this folder separately so I don’t have to reshuffle the entire desk every time something inside it changes.”</em></p>

<p>But there’s a side effect. Once the browser lifts an element into its own layer, it must “flatten” everything within it, creating a new stacking context. It’s like taking a folder off the desk to handle it separately; everything inside that folder gets grouped, and the browser now treats it as a single unit when deciding what sits on top of what.</p>

<p>So even though the <code>transform</code> and <code>opacity</code> properties might not appear to affect the way that elements stack visually, they do, and it’s for performance optimisation. Several other CSS properties can also create stacking contexts for similar reasons. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context#features_creating_stacking_contexts">MDN provides a complete list</a> if you want to dig deeper. There are quite a few, which only illustrates how easy it is to inadvertently create a stacking context without knowing it.</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="the-unstacking-problem">The “Unstacking” Problem</h2>

<p>Stacking issues can arise for many reasons, but some are more common than others. Modal components are a classic pattern because they require toggling the component to “open” on a top layer above all other elements, then removing it from the top layer when it is “closed.”</p>

<p>I’m pretty confident that all of us have run into a situation where we open a modal and, for whatever reason, it doesn’t appear. It’s not that it didn’t open properly, but that it is out of view in a lower layer of the stacking context.</p>

<p>This leaves you to wonder “how come?” since you set:</p>

<div class="break-out">
<pre><code class="language-css">.overlay {
  position: fixed; /&#42; creates the stacking context &#42;/
  z-index: 1; /&#42; puts the element on a layer above everything else &#42;/
  inset: 0; 
  width: 100%; 
  height: 100vh; 
  overflow: hidden;
  background-color: &#35;00000080;
}
</code></pre>
</div>

<p>This looks correct, but if the parent element containing the modal trigger is a child element within another parent element that’s also set to <code>z-index: 1</code>, that technically places the modal in a sublayer obscured by the main folder. Let’s look at that specific scenario and a couple of other common stacking-context pitfalls. I think you’ll see not only how easy it is to inadvertently create stacking contexts, but also how to mismanage them. Also, how you return to a managed state depends on the situation.</p>

<h3 id="scenario-1-the-trapped-modal">Scenario 1: The Trapped Modal</h3>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="pvbddjd"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scenario 1: The Trapped Modal (Problem) [forked]](https://codepen.io/smashingmag/pen/pvbddjd) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvbddjd">Scenario 1: The Trapped Modal (Problem) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<p>If you click the “Open Modal” button in the header, you’ll notice that the overlay and modal appear behind the main content. This is because the modal is a child of the header container, which has a lower stacking context order (<code>z-index: 1</code>) than the main container (<code>z-index</code> of <code>2</code>). Despite the modal overlay and the modal having <code>z-index</code> values of <code>9998</code> and <code>9999</code>, respectively, the main container with a <code>z-index: 2</code> still sits right above them.</p>

<h3 id="scenario-2-the-submerged-dropdown">Scenario 2: The Submerged Dropdown</h3>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="zxBPPvm"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scenario 2: The Submerged Dropdown (Problem) [forked]](https://codepen.io/smashingmag/pen/zxBPPvm) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/zxBPPvm">Scenario 2: The Submerged Dropdown (Problem) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<p>Here, we have a similar issue with the first scenario. When you hover over the “services” link, the dropdown shows, but behind the main container. I intentionally set the main container’s <code>margin-top</code> to <code>20px</code> to make the dropdown visible enough for you to see it appear, but keep it just behind the main container. This is another popular issue front-end developers encounter, stemming from context stacking. While it is similar to the first scenario, there’s another approach to resolving it, which will be explored soon.</p>

<h3 id="scenario-3-the-clipped-tooltip">Scenario 3: The Clipped Tooltip</h3>

<p>Now, this is an interesting one. It’s not about which element has the higher <code>z-index</code>. It’s about <code>overflow: hidden</code> doing <a href="https://www.smashingmagazine.com/2021/04/css-overflow-issues/">what it’s designed to do</a>: preventing content from visually escaping its container, even when that content has <code>z-index: 1000</code>.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="GgqOOoo"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scenario 3: The Clipped Tooltip (Problem) [forked]](https://codepen.io/smashingmag/pen/GgqOOoo) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/GgqOOoo">Scenario 3: The Clipped Tooltip (Problem) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<p>Who would have thought <code>overflow: hidden</code> could stop a <code>z-index: 1000</code>? Well, it did stop it, as you can see in the Codepen above.</p>

<p>I think developers trust <code>z-index</code> so much that they expect it to pull them out of any obscurity issue, but in reality, it doesn’t work that way. Not that it isn’t powerful, it’s just that other factors determine its ability to push your element to the top.</p>

<p>Before you slap <code>z-index</code> on that element, remember that while this might get you out of the current jam, it might also throw you into a greater one <a href="https://www.matuzo.at/blog/2025/never-lose-a-z-index-battle-again">that even <code>z-index: infinity</code> won’t get you out of</a>.</p>

<p>Let’s try to understand the problem before attempting to fix it.</p>

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

<h2 id="identifying-the-trapped-layer">Identifying The Trapped Layer</h2>

<p>When you encounter an issue such as those listed above, it is helpful to know that the element isn’t possessed; instead, an ancestor has sinned, and the child is paying the debt. In non-spiritual English terms, the obscured element isn’t the problem; an ancestor element has created a lower-level stacking context that has led the children to be below the children of a parent with a higher-level stacking context.</p>

<p>A good way to track and find that parent is to descend into the browser’s devtools to inspect the element and make your way up, checking each parent level to see which has a property or properties that trigger a stacking context, and find out its position in the order compared to sibling elements. Let’s create a checklist to order our steps.</p>

<h3 id="your-debugging-checklist">Your Debugging Checklist</h3>

<ol>
<li><strong>Inspect the Problem Element.</strong><br />
Right-click your hidden element (the modal, the dropdown menu, the tooltip) and click “Inspect.”</li>
<li><strong>Check its Styles.</strong><br />
In the “Styles” or “Computed” pane, verify that it has the expected high <code>z-index</code> (e.g., <code>z-index: 9999;</code>).</li>
<li><strong>Climb the DOM Tree.</strong><br />
In the “Elements” panel, look at the element’s immediate parent. Click on it.</li>
<li><strong>Investigate the Parent’s Styles.</strong><br />
Look at the parent’s CSS in the “Styles” pane. You are now hunting for any property that creates a new stacking context. Look for any properties related to positioning, visual effects, and containment.</li>
<li><strong>Repeat.</strong><br />
If the immediate parent is clean, click on its parent (the grandparent of your element). Repeat Step 4. Keep climbing the DOM tree, one parent at a time, until you find the culprit.</li>
</ol>

<p>Now, let’s apply this checklist to our three scenarios.</p>

<h3 id="problem-1-the-trapped-modal">Problem 1: The Trapped Modal</h3>

<ol>
<li><strong>Inspect:</strong> We inspect the <code>.modal-content</code>.</li>
<li><strong>Check Styles:</strong> We see <code>z-index: 9999</code>. That’s not the problem.</li>
<li><strong>Climb:</strong> We look at its parent, <code>.modal-container</code>. It has no trapping properties.</li>
<li><strong>Climb Again:</strong> We look at its parent, the <strong><code>.header</code></strong>.</li>
<li><strong>Investigate:</strong> We check the styles for <code>.header</code> and find the culprit: <code>position: absolute</code> and <code>z-index: 1</code>. This element is creating a stacking context. We’ve seen our trap! The modal’s <code>z-index: 9999</code> is “trapped” inside a <code>z-index: 1</code> folder.</li>
</ol>

<h3 id="problem-2-the-submerged-dropdown">Problem 2: The Submerged Dropdown</h3>

<ol>
<li><strong>Inspect:</strong> We inspect the <code>.dropdown-menu</code>.</li>
<li><strong>Check Styles:</strong> We see <code>z-index: 100</code>.</li>
<li><strong>Climb:</strong> We check its parent <code>li</code>, then its parent <code>ul</code>, then its parent <strong><code>.navbar</code></strong>.</li>
<li>Investigate: We find <code>.navbar</code> has <code>position: relative</code> and <code>z-index: 1</code>. This creates Stacking Context A.</li>
<li><strong>Analyse Siblings:</strong> This isn’t the whole story. Why is it under the content? We now inspect the sibling of <code>.navbar</code>, which is <code>.content</code>. We find it has <code>position: relative</code> and <code>z-index: 2</code> (Stacking Context B). The browser is stacking the “folders”: <code>.content</code> (2) on top of <code>.navbar</code> (1). We’ve found the root cause.</li>
</ol>

<h3 id="problem-3-the-clipped-tooltip">Problem 3: The Clipped Tooltip</h3>

<ol>
<li><strong>Inspect:</strong> We inspect the <code>.tooltip</code>.</li>
<li><strong>Check Styles:</strong> We see <code>z-index: 1000</code>.</li>
<li><strong>Climb:</strong> We check its parent, <code>.tooltip-trigger</code>. It’s fine.</li>
<li><strong>Climb Again:</strong> We check its parent, the <strong><code>.card-container</code></strong>.</li>
<li><strong>Investigate:</strong> We scan its styles and find the culprit: <code>overflow: hidden</code>. This is a special type of trap. It clips any child that tries to render outside its boundaries, regardless of <code>z-index</code> values.</li>
</ol>

<h3 id="advanced-tooling">Advanced Tooling</h3>

<p>While climbing the DOM tree works, it can be slow. Here are tools that speed things up.</p>

<h4 id="devtools-3d-view">DevTools 3D View</h4>

<p>Some browsers, such as Microsoft Edge (in the “More Tools” menu) and Firefox (in the “Inspector” tab), include a “3D View” or “Layers” panel. This tool is a lifesaver. It visually explodes the webpage into its different layers, showing you exactly how the stacking contexts are grouped.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="534"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png"
			
			sizes="100vw"
			alt="Microsoft Edge 3D Stacking Context View"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Microsoft Edge 3D Stacking Context View. (<a href='https://files.smashing.media/articles/unstacking-css-stacking-contexts/4-microsoft-edge-stacking-context.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can immediately see your modal trapped in a low-level layer and identify the parent.</p>

<h4 id="browser-extensions">Browser Extensions</h4>

<p>Smart developers have built extensions to help. Tools like this <a href="https://chrome.google.com/webstore/detail/z-context/jigamimbjojkdgnlldajknogfgncplbhhttps://chrome.google.com/webstore/detail/z-context/jigamimbjojkdgnlldajknogfgncplbh">“CSS Stacking Context Inspector” Chrome extension</a> add an extra <code>z-index</code> tab to your DevTools to show you information about elements that create a stacking context.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="341"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png"
			
			sizes="100vw"
			alt="CSS Stacking Context Inspector Chrome extension"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/unstacking-css-stacking-contexts/5-browser-extensions.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h4 id="ide-extensions">IDE Extensions</h4>

<p>You can even spot issues during development with an extension <a href="https://marketplace.visualstudio.com/items?itemName=mikerheault.vscode-better-css-stacking-contexts">like this one for VS Code</a>, which highlights potential stacking context issues directly in your editor.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="468"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png"
			
			sizes="100vw"
			alt="Better CSS Stacking Contexts Extension"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Better CSS Stacking Contexts Extension. (<a href='https://files.smashing.media/articles/unstacking-css-stacking-contexts/6-ide-extensions.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="unstacking-and-regaining-control">Unstacking And Regaining Control</h2>

<p>After we’ve identified the root cause, the next step is to deal with it. There are several approaches you can take to tackle this problem, and I’ll list them in order. You can choose anyone at any level, though; no one can complain or obstruct another.</p>

<h3 id="change-the-html-structure">Change The HTML Structure</h3>

<p>This is considered the optimal fix. For you to run into a stacking context issue, you must have placed some elements in funny positions within your HTML. Restructuring the page will help you reshape the DOM and eliminate the stacking context problem. Find the problematic element and remove it from the trapping element in the HTML markup. For instance, we can solve the first scenario, “The Trapped Modal,” by moving the <code>.modal-container</code> out of the header and placing it in the <code>&lt;body&gt;</code> element by itself.</p>

<div class="break-out">
<pre><code class="language-html">&lt;header class="header"&gt;
  &lt;h2&gt;Header&lt;/h2&gt;
  &lt;button id="open-modal"&gt;Open Modal&lt;/button&gt;
  &lt;!-- Former position --&gt;
&lt;/header&gt;
&lt;main class="content"&gt;
  &lt;h1&gt;Main Content&lt;/h1&gt;
  &lt;p&gt;This content has a z-index of 2 and will still not cover the modal.&lt;/p&gt;
&lt;/main&gt;

&lt;!-- New position  --&gt;
&lt;div id="modal-container" class="modal-container"&gt;
  &lt;div class="modal-overlay"&gt;&lt;/div&gt;
  &lt;div class="modal-content"&gt;
    &lt;h3&gt;Modal Title&lt;/h3&gt;
    &lt;p&gt;Now, I'm not behind anything. I've gotten a better position as a result of DOM restructuring.&lt;/p&gt;
    &lt;button id="close-modal"&gt;Close&lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
</div> 

<p>When you click the “Open Modal” button, the modal is positioned in front of everything else as it’s supposed to be.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="azZVVNP"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scenario 1: The Trapped Modal (Solution) [forked]](https://codepen.io/smashingmag/pen/azZVVNP) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/azZVVNP">Scenario 1: The Trapped Modal (Solution) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<h3 id="adjust-the-parent-stacking-context-in-css">Adjust The Parent Stacking Context In CSS</h3>

<p>What if the element is one you can’t move without breaking the layout? It’s better to address the issue: <strong>the parent establishes the context.</strong> Find the CSS property (or properties) responsible for triggering the context and remove it. If it has a purpose and cannot be removed, give the parent a higher <code>z-index</code> value than its sibling elements to lift the entire container. With a higher <code>z-index</code> value, the parent container moves to the top, and its children appear closer to the user.</p>

<p>Based on what we learned in “<a href="/scl/fi/ue0rufxffviprc9858j25/Debugging-CSS-Stacking-Contexts.paper?rlkey=ezbdaiq6mojvb7xzezxlds29b&amp;dl=0#:uid=376729122027939635792428&amp;h2=Problem-2:-The-Submerged-Dropd">The Submerged Dropdown</a>” scenario, we can’t move the dropdown out of the navbar; it wouldn’t make sense. However, we can increase the <code>z-index</code> value of the <code>.navbar</code> container to be greater than the <code>.content</code> element’s <code>z-index</code> value.</p>

<pre><code class="language-css">.navbar {
  background: &#35;333;
  /&#42; z-index: 1; &#42;/
  z-index: 3;
  position: relative;
}
</code></pre>

<p>With this change, the <code>.dropdown-menu</code> now appears in front of the content without any issue.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="YPWEEWz"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scenario 2: The Submerged Dropdown (Solution) [forked]](https://codepen.io/smashingmag/pen/YPWEEWz) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/YPWEEWz">Scenario 2: The Submerged Dropdown (Solution) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<h3 id="try-portals-if-using-a-framework">Try Portals, If Using A Framework</h3>

<p>In frameworks like <a href="https://react.dev/reference/react-dom/createPortal">React</a> or <a href="https://www.digitalocean.com/community/tutorials/vuejs-portal-vue">Vue</a>, a Portal is a feature that lets you render a component outside its normal parent hierarchy in the DOM. Portals are like a teleportation device for your components. They let you render a component’s HTML anywhere in the document (typically right into <code>document.body</code>) while keeping it logically connected to its original parent for props, state, and events. This is perfect for escaping stacking context traps since the rendered output literally appears outside the problematic parent container.</p>

<pre><code class="language-javascript">ReactDOM.createPortal(
  &lt;ToolTip /&gt;,
  document.body
);
</code></pre>

<p>This ensures your dropdown content isn’t hidden behind its parent, even if the parent has <code>overflow: hidden</code> or a lower <code>z-index</code>.</p>

<p>In the “The Clipped Tooltip” scenario we looked at earlier, I used a Portal to rescue the tooltip from the <code>overflow: hidden</code> clip by placing it in the document body and positioning it above the trigger within the container.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="myEqqEe"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Scenario 3: The Clipped Tooltip (Solution) [forked]](https://codepen.io/smashingmag/pen/myEqqEe) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/myEqqEe">Scenario 3: The Clipped Tooltip (Solution) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

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

<h2 id="introducing-stacking-context-without-side-effects">Introducing Stacking Context Without Side Effects</h2>

<p>All the approaches explained in the previous section are aimed at “unstacking” elements from problematic stacking contexts, but there are some situations where you’ll actually need or want to create a stacking context.</p>

<p>Creating a new stacking context is easy, but all approaches come with a side effect. That is, except for using <a href="https://css-tricks.com/almanac/properties/i/isolation/"><code>isolation: isolate</code></a>. When applied to an element, the stacking context of that element’s children is determined relative to each child and within that context, rather than being influenced by elements outside of it. A classic example is assigning that element a negative value, such as <code>z-index: -1</code>.</p>

<p>Imagine you have a <code>.card</code> component. You want to add a decorative shape that sits behind the <code>.card</code>’s text, but on top of the card’s background. Without a stacking context on the card, <code>z-index: -1</code> sends the shape to the bottom of the root stacking context (the whole page). This makes it disappear behind the <code>.card</code>’s white background:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="QwEOOEM"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Negative z-index (problem) [forked]](https://codepen.io/smashingmag/pen/QwEOOEM) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/QwEOOEM">Negative z-index (problem) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<p>To solve this, we declare <code>isolation: isolate</code> on the parent <code>.card</code>:</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="MYeOOeG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Negative z-index (solution) [forked]](https://codepen.io/smashingmag/pen/MYeOOeG) by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/MYeOOeG">Negative z-index (solution) [forked]</a> by <a href="https://codepen.io/drprime01">Shoyombo Gabriel Ayomide</a>.</figcaption>
</figure>

<p>Now, the <code>.card</code> element itself becomes a stacking context. When its child element &mdash; the decorative shape created on the <code>:before</code> pseudo-element &mdash; has <code>z-index: -1</code>, it goes to the very bottom of the parent’s stacking context. It sits perfectly behind the text and on top of the card’s background, as intended.</p>

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

<p>Remember: the next time your <code>z-index</code> seems out of control, it’s a trapped stacking context.</p>

<h3 id="references">References</h3>

<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context">Stacking context</a> (MDN)</li>
<li><a href="https://web.dev/learn/css/z-index">Z-index and stacking contexts</a> (web.dev)</li>
<li>“<a href="https://www.freecodecamp.org/news/the-css-isolation-property/">How to Create a New Stacking Context with the Isolation Property in CSS</a>”, Natalie Pina</li>
<li>“<a href="https://www.joshwcomeau.com/css/stacking-contexts/">What The Heck, z-index??</a>”, Josh Comeau</li>
</ul>

<h3 id="further-reading-on-smashingmag">Further Reading On SmashingMag</h3>

<ul>
<li>“<a href="https://www.smashingmagazine.com/2021/02/css-z-index-large-projects/">Managing CSS Z-Index In Large Projects</a>”, Steven Frieson</li>
<li>“<a href="https://www.smashingmagazine.com/2024/09/sticky-headers-full-height-elements-tricky-combination/">Sticky Headers And Full-Height Elements: A Tricky Combination</a>”, Philip Braunen</li>
<li>“<a href="https://www.smashingmagazine.com/2019/04/z-index-component-based-web-application/">Managing Z-Index In A Component-Based Web Application</a>”, Pavel Pomerantsev</li>
<li>“<a href="https://www.smashingmagazine.com/2009/09/the-z-index-css-property-a-comprehensive-look/">The Z-Index CSS Property: A Comprehensive Look</a>”, Louis Lazaris</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>Daniel Schwarz</author><title>Penpot Is Experimenting With MCP Servers For AI-Powered Design Workflows</title><link>https://www.smashingmagazine.com/2026/01/penpot-experimenting-mcp-servers-ai-powered-design-workflows/</link><pubDate>Thu, 08 Jan 2026 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/01/penpot-experimenting-mcp-servers-ai-powered-design-workflows/</guid><description>&lt;a href="https://penpot.app/?utm_source=SmashingMagazine&amp;amp;utm_medium=Article&amp;amp;utm_campaign=MCPserver">Penpot&lt;/a> is experimenting with MCP (Model Context Protocol) servers, which could lead to designers and developers being able to perform tasks in Penpot using AI that’s able to understand and interact with Penpot design files. Daniel Schwarz explains how &lt;a href="https://github.com/penpot/penpot-mcp">Penpot MCP&lt;/a> servers work, what they could mean for creating and managing designs in Penpot, and what you can do to help shape their development.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/01/penpot-experimenting-mcp-servers-ai-powered-design-workflows/" />
              <title>Penpot Is Experimenting With MCP Servers For AI-Powered Design Workflows</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Penpot Is Experimenting With MCP Servers For AI-Powered Design Workflows</h1>
                  
                    
                    <address>Daniel Schwarz</address>
                  
                  <time datetime="2026-01-08T08:00:00&#43;00:00" class="op-published">2026-01-08T08:00:00+00:00</time>
                  <time datetime="2026-01-08T08:00:00&#43;00:00" class="op-modified">2026-02-09T03:03:08+00:00</time>
                </header>
                <p>This article is sponsored by <b>Penpot</b></p>
                

<p>Imagine that your Penpot file contains a full icon set in addition to the design itself, which uses some but not all of those icons. If you were to ask an AI such as Claude or Gemini to export only the icons that are being used, it wouldn’t be able to do that. It’s not able to interact with Penpot files.</p>

<p>However, a <strong>Penpot MCP server</strong> can. It can perform a handpicked number of operations under set rules and permissions, especially since Penpot has an extensive API and even more so because it’s <strong>open-source</strong>.</p>

<p>The AI’s job is simply to understand your intent, choose the right operation for the MCP server to perform (an export in this case), and pass along any parameters (i.e., icons that are being used). The MCP server then translates this into a structured API request and executes it.</p>

<p>It might help to think of AI as a server in a restaurant that takes your order, the MCP server as both the menu and chef, and the API request as (hopefully) a hot pizza pie on a warm plate.</p>

<p>Why MCP servers, exactly? Well, Penpot isn’t able to understand your intent because it’s not an LLM, nor does it allow third-party LLMs to interact with your Penpot files for the security and privacy of your Penpot data. Although Penpot MCP servers do act as a <strong>secure bridge</strong>, translating AI intent into API requests using your Penpot files and data as context.</p>

<p>What’s even better is that because Penpot takes a <strong>design-expressed-as-code approach</strong>, designs can be programmatically created, edited, and analyzed on a granular level. It’s more contextual, more particular, and therefore more powerful in comparison to what other MCP servers offer, and <em>far</em> more thoughtful than the subpar ‘Describe → Generate’ AI workflow that I don’t think anybody really wants. <a href="https://penpot.app/blog/penpot-ai-whitepaper/">Penpot’s AI whitepaper</a> describes this as the bad approach and the ‘Convert to Code’ approach as the ugly approach, whereas MCP servers are more refined and adaptable.</p>

<h2 id="features-and-technical-details">Features And Technical Details</h2>

<p>Before we move on to use cases, here are some features and technical details that further explain how Penpot MCP servers work:</p>

<ul>
<li>Complies with MCP standards;</li>
<li>Integrates with the Penpot API for real-time design data;</li>
<li>Includes a Python SDK, REST API, plugin system, and CLI tools;</li>
<li>Works with any MCP-enabled AI assistant (Claude in VS Code, Claude in Cursor, Claude Desktop, etc.);</li>
<li>Supports sharing design context with AI models, and letting them see and understand components;</li>
<li>Facilitates communication with Penpot using natural language.</li>
</ul>

<p>What, then, could MCP servers enable us to do in Penpot, and what have existing experiments already achieved? Let’s take a look.</p>

<h2 id="penpot-mcp-server-use-cases">Penpot MCP Server Use-Cases</h2>

<p>If you just want to skip to what Penpot MCP servers can do, Penpot have a few <a href="https://drive.google.com/drive/u/0/folders/1CCuBqHEevWsp15bYkf3W7CLXLja5R_M_">MCP demos</a> stashed in a Google Drive that are more than worth watching. Penpot CEO Pablo Ruiz-Múzquiz mentioned that videos 03, 04, 06, 08, and 12 are their favorites.</p>

<p>An even faster way to summarize MCP servers is to <a href="https://www.youtube.com/watch?v=KNsvFc4Elfs&amp;list=PLgcCPfOv5v57Sp_vY4VggqUpAbP5fTIkM">watch the unveiling from Penpot Fest 2025</a>.</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="KNsvFc4Elfs"
      
			
		></lite-youtube>
	</div>
	
</figure>

<p>Otherwise, let’s take a look at some of the more refined examples that Penpot demonstrated in their <a href="https://community.penpot.app/t/penpot-mcp-server-showcase-ask-for-help/10040?utm_source=SmashingMagazine&amp;utm_medium=Article&amp;utm_campaign=MCPserver">public showcase</a>.</p>

<h3 id="design-to-code-and-back-again-and-more">Design-to-Code and Back Again (and More)</h3>

<p>Running on from what I was saying earlier about how Penpot designs are expressed as code, this means that MCP servers can be used to convert design to code using AI, but also code to design, design to documentation, documentation to design system elements, design to code again <em>based</em> on said design system, and then completely new components based on said design system.</p>

<p>It sounds surreal, but the demo below shows off this <strong>transmutability</strong>, and it’s not from vague instruction but rather previous design choices, regardless of how they were expressed (design, code, or documentation). There are no surprises &mdash; these are simply the decisions that you would’ve made anyway based on previous decisions, executed swiftly.</p>

<p>In the demo, Juan de la Cruz García, Designer at Penpot, frictionlessly transmutes some simple components into documentation, design system elements, code, new components, and even a complete Storybook project like a piece of Play-Doh:</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/1152331606"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Quick demo: Penpot MCP server in action</figcaption>
	
</figure>

<h3 id="design-to-code-design-code-validation-and-simple-operations">Design-to-Code, Design/Code Validation, And Simple Operations</h3>

<p>In a similar demo below, Dominik Jain, Co-Founder at Oraios AI, creates a Node.js web app based on the design before updating the frontend styles, saves names and identifiers to memory to ensure smooth design-to-code translation before checking it for consistency, adds a comment next to the selected shape in Penpot, and then replaces a scribble in Penpot with an adapted component. There’s a lot happening here, but you can see exactly what Dominik is typing into Claude Desktop as well as Claude’s responses, and it’s <em>very</em> robust:</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/1152352889"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Penpot MCP Server: Developer Workflow. Applications</figcaption>
	
</figure>

<p>By the way, the previous demo used Claude in VS Code, so I should note that <strong>Penpot MCP servers are LLM-agnostic</strong>. Your tech stack is totally up to you. IvanTheGeek managed to <a href="https://community.penpot.app/t/penpot-mcp-server-showcase-ask-for-help/10040/3?utm_source=SmashingMagazine&amp;utm_medium=Article&amp;utm_campaign=MCPserver">set up their MCP server with the JetBrains Rider IDE and Junie AI</a>.</p>

<h3 id="more-use-cases">More Use Cases</h3>

<p>Translate a Penpot board to production-ready semantic HTML and modular CSS while leveraging any Penpot design tokens (remember that Penpot designs are already expressed as code, so this isn’t a shot in the dark):</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="yoOOOPPXfc4"
      
			
		></lite-youtube>
	</div>
	
</figure>

<p>Generate an interactive web prototype without changing the existing HTML:</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="raha7nvY_j4"
      
			
		></lite-youtube>
	</div>
	
</figure>

<p>As shown earlier, convert a scribble into a component, leveraging existing design and/or design system elements:</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="9fudhgOhMDc"
      
			
		></lite-youtube>
	</div>
	
</figure>

<p>Create design system documentation from a Penpot file:</p>


<figure class="video-embed-container">
  <div
  
  class="video-embed-container--wrapper">
		<lite-youtube
			videoid="kNc6kUZvB6k"
      
			
		></lite-youtube>
	</div>
	
</figure>

<p>And here are some more use-cases from Penpot and the community:</p>

<ul>
<li>Advanced exports,</li>
<li>Search for design elements using natural language,</li>
<li>Pull data from external APIs using natural language,</li>
<li>Easily connect Penpot to other external tools,</li>
<li>Saving repetitive tasks to memory and executing them,</li>
<li>Visual regression testing,</li>
<li>Design consistency and redundancy checking,</li>
<li>Accessibility and usability analysis and feedback,</li>
<li>Design system compliance checking,</li>
<li>Guideline compliance checking (brand, content, etc.),</li>
<li>Monitor adoption and usage with design analytics,</li>
<li>Automatically keep documentation in sync with design,</li>
<li>Design file organization (e.g., tagging/categorization).</li>
</ul>

<p>Essentially, Penpot MCP servers lead the way to an <strong>infinite number of workflows</strong> thanks to the efficiency and ease of your chosen LLM/LLM client, but without exposing your data to it.</p>

<h2 id="what-would-you-use-mcp-servers-for">What Would You Use MCP Servers For?</h2>

<p>Penpot MCP servers aren’t even at the beta stage, but it is an <strong>active experiment</strong> that you can be a part of. Penpot users have already begun exploring use cases for MCP servers, but Penpot wants to see more. To ensure that the next generation of design tools meets the needs of designers, developers, and product teams in general, they must be built <strong>collectively</strong> and <strong>collaboratively</strong>, especially where AI is concerned.</p>

<p><strong>Note</strong>: <em>Penpot is looking for beta testers eager to explore, experiment with, and help refine Penpot’s MCP Server. To join, write to <a href="mailto:support@penpot.app">support@penpot.app</a> with the subject line “MCP beta test volunteer.”</em></p>

<p>Is there anything that you feel Penpot MCP servers could do that current tools aren’t able to do well enough, fast enough, or aren’t able to do at all?</p>

<p>You can learn <a href="https://github.com/penpot/penpot-mcp">how to set up a Penpot MCP server right here</a> and start tinkering today, or if your brain’s buzzing with ideas already, Penpot want you to <a href="https://community.penpot.app/t/penpot-mcp-server-showcase-ask-for-help/10040?utm_source=SmashingMagazine&amp;utm_medium=Article&amp;utm_campaign=MCPserver">join the discussion</a>, share your feedback, and talk about your use-cases. Alternatively, the comment section right below isn’t a bad place to start either!</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>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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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">2026-02-09T03: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></channel></rss>