<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>UI on Smashing Magazine — For Web Designers And Developers</title><link>https://www.smashingmagazine.com/category/ui/index.xml</link><description>Recent content in UI on Smashing Magazine — For Web Designers And Developers</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 30 May 2026 06:46:31 +0000</lastBuildDate><item><author>Joas Pambou</author><title>Designing Stable Interfaces For Streaming Content</title><link>https://www.smashingmagazine.com/2026/05/designing-stable-interfaces-streaming-content/</link><pubDate>Fri, 01 May 2026 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/05/designing-stable-interfaces-streaming-content/</guid><description>Streaming UIs are an easy concept on the surface, but are quite complicated in practice. There are many considerations that need to be accounted for, from layout shifts and motion preferences to proper markup and various states, that may not be instantly obvious. What happens if the stream is interrupted? Can users tab through the UI on the keyboard as it shifts? What ARIA attributes might be needed? Those are the sorts of things we will tackle in this article.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/05/designing-stable-interfaces-streaming-content/" />
              <title>Designing Stable Interfaces For Streaming Content</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Designing Stable Interfaces For Streaming Content</h1>
                  
                    
                    <address>Joas Pambou</address>
                  
                  <time datetime="2026-05-01T08:00:00&#43;00:00" class="op-published">2026-05-01T08:00:00+00:00</time>
                  <time datetime="2026-05-01T08:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>More interfaces now render while the response is still being generated. The UI begins in one state, then updates as more data comes in. You see this in chat apps, logs, transcription tools, and other real-time systems.</p>

<p>The tricky part is that the <strong>interface is not in a fixed state</strong>; it keeps changing as new content comes in. It grows where lines become longer and new blocks appear. Something that was just below the screen can suddenly move, and the user’s scroll position becomes harder to manage. Parts of the UI might even be incomplete while the user is already interacting with it.</p>

<p>In this article, we’ll take a simple interface and make it handle this properly. We’ll look at how to keep things stable, manage scrolling, and render partial content without breaking the reading experience.</p>

<h2 id="what-does-a-streaming-ui-actually-look-like">What Does A Streaming UI Actually Look Like?</h2>

<p>I’ve built three demos that stream content in different ways: a chat bubble, a log feed, and a transcription view. They look different on the surface, but they all run into the same three problems.</p>

<p>The first is <strong>scroll</strong>. When content is streaming in, most interfaces keep the viewport pinned to the bottom. That works if you are just watching, but the moment you scroll up to read something, the page snaps back down. You did not ask for that. The interface decided for you, and now you’re fighting it instead of reading.</p>

<p>The second is <strong>layout shift</strong>. Streaming content means containers are constantly growing, and as they do, everything below shifts downward. A button you were about to click is no longer where it was. A line you were reading has moved. The page is not broken; it is just that nothing stays still long enough to interact with comfortably.</p>

<p>The third is <strong>render frequency</strong>. Browsers paint the screen around 60 times per second, but streams can arrive much faster than that. This means the DOM, which is the browser’s internal representation of everything on the page, ends up being updated for frames the user will never actually see. Each update still costs something, and that cost adds up quietly until performance starts to slip.</p>

<p>As you go through each demo, pay attention to where things start feeling off. That small moment of friction when the interface starts getting in your way. This is exactly what we are here to fix.</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="example-1-streaming-ai-chat-responses">Example 1: Streaming AI Chat Responses</h2>

<p>This is the most familiar case. You click <strong>Stream</strong>, and the message starts growing token by token, just like a typical AI chat interface.</p>














<figure class="
  
  
  ">
  
    <a href="https://codesandbox.io/embed/swmjpl?view=preview">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="566"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png"
			
			sizes="100vw"
			alt="Streaming AI Chat Responses"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Open in <a href='https://codesandbox.io/embed/swmjpl?view=preview'>CodeSandbox</a>. (<a href='https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/1-streaming-ai-chat-responses.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Here’s what I want you to try:</p>

<ul>
<li>Click the <strong>Stream</strong> button.</li>
<li>Try scrolling upwards while the message is streaming.</li>
<li>Increase the speed (to something like 10ms).</li>
</ul>

<p>You will notice something subtle but important: the UI keeps trying to pull you back down. Basically, it is making a decision for you about where your attention should be.</p>

<p>That’s one example. Let’s look at another.</p>

<h2 id="example-2-live-processing-in-a-log-viewer">Example 2: Live Processing In A Log Viewer</h2>

<p>This example looks different on the surface, but the problem is actually very similar to the first example. Rather than a message that gets longer over time, new lines are appended continuously, like a terminal or a log stream.</p>

<p>The interesting part here is the tail toggle. It makes the trade-off between interaction and stable interfaces very clear:</p>














<figure class="
  
  
  ">
  
    <a href="https://codesandbox.io/embed/cytscf?view=preview">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="515"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png"
			
			sizes="100vw"
			alt="Live Processing In A Log Viewer"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Open in <a href='https://codesandbox.io/embed/cytscf?view=preview'>CodeSandbox</a>. (<a href='https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/2-live-processing-log-viewer.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Again, here is what I want you to try:</p>

<ul>
<li>Click the <strong>Start</strong> button.</li>
<li>Allow the logs to stream past the container’s height.</li>
<li>Scroll up to the beginning.</li>
<li>Stop the stream and disable the “tail” option.</li>
</ul>

<p>Notice that, when tail is enabled, the UI follows the new content. But you’re unable to scroll up and stay in place. Instead, you need to stop the stream or enable “tail” to explore the content.</p>

<h2 id="example-3-dashboard-displaying-real-time-metrics">Example 3: Dashboard Displaying Real-Time Metrics</h2>

<p>In this case, the UI updates in place:</p>

<ul>
<li>Numbers change,</li>
<li>Charts shift,</li>
<li>Values refresh continuously.</li>
</ul>














<figure class="
  
  
  ">
  
    <a href="https://codesandbox.io/embed/8rtsrm?view=preview">
    
    <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/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png"
			
			sizes="100vw"
			alt="Dashboard Displaying Real-Time Metrics"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Open in <a href='https://codesandbox.io/embed/8rtsrm?view=preview'>CodeSandbox</a>. (<a href='https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/3-dashboard-display-real-time-metrics.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>There is no scroll tension this time, but a different issue shows up. That’s what we’ll get into next.</p>

<h2 id="why-the-ui-feels-unstable-and-how-to-fix-it">Why The UI Feels Unstable And How To Fix It</h2>

<p>If you tried the chat demo and scrolled upward while the responses were coming in, you may have spotted the first issue right away: the UI keeps pulling you back down to the latest streamed content as it updates. This takes you out of context and never allows you the time to fully digest the content once it has passed.</p>

<p>We see that exact same issue in the second example, the log viewer. Without the tail toggle, the streamed content overrides your scroll position.</p>

<p>These aren’t bugs in the traditional sense that they produce code errors; rather, they are accessibility issues that affect <em>all</em> users. That said, they can be fixed and prevented with careful UX considerations as you plan and test your work.</p>

<h3 id="ensure-predictable-scroll-behavior">Ensure Predictable Scroll Behavior</h3>

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

<ul>
<li>Enable auto-scrolling when detecting that the user is at the bottom of the stream.</li>
<li>Stop auto-scrolling when the user has scrolled upwards.</li>
<li>Resume auto-scrolling if the user scrolls back to the bottom of the stream.</li>
</ul>

<p>To do that, we need to know whether the user has intentionally moved away from the bottom, which we can assume is true when the scroll position is manually changed. We can track that behavior with a flag.</p>

<pre><code class="language-javascript">let userScrolled = false;

chatEl.addEventListener('scroll', () =&gt; {
  const gap = chatEl.scrollHeight
            - chatEl.scrollTop
            - chatEl.clientHeight;

  userScrolled = gap &gt; 60;
});
</code></pre>

<p>That <code>60px</code> threshold matters. Without it, tiny layout changes (like a new line) would briefly create a gap and break auto-scroll, even if the user didn’t actually scroll.</p>

<p>Now let’s make sure that we enable auto-scrolling only when the user’s scroll position is equal to the stream’s scroll height, i.e., the user is at the bottom of the stream:</p>

<pre><code class="language-javascript">function autoScroll() {
  if (!userScrolled) {
    chatEl.scrollTop = chatEl.scrollHeight;
  }
}
</code></pre>

<p>One small thing that’s easy to miss: we need to reset <code>userScrolled</code> once a new stream begins. Otherwise, one scroll from a previous message can silently disable auto-scroll for the next one.</p>

<h3 id="solidify-layout-stability">Solidify Layout Stability</h3>

<p>We saw this in the first example as well. As new content streams in, the layout jumps, or shifts, taking you out of your current context. To be specific about what’s shifting: it’s not the page layout in a broad sense, it’s the content directly below the chat bubble.</p>

<p>There’s also a subtler artifact worth calling out before we look at the code: cursor flicker. Because we’re wiping <code>innerHTML</code> and recreating every element on every tick, the cursor is being destroyed and re-added constantly, up to 80 times per second at fast speeds.</p>

<p>At normal speed, it’s easy to miss, but slow the slider down to around 30ms, and you’ll see a faint but persistent flicker at the end of the text. Once we fix the rebuild pattern, the flicker disappears entirely.</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/1187532028"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>That rebuild pattern is right here; this is what runs on every single incoming character:</p>

<pre><code class="language-javascript">bubble.innerHTML = '';

fullText.split('\n').forEach(line =&gt; {
  const p = document.createElement('p');
  p.textContent = line || '\u00A0';
  bubble.appendChild(p);
});

bubble.appendChild(cursorEl);
</code></pre>

<p>This works, but it’s expensive. Every update wipes the DOM and rebuilds it, forcing layout recalculation each time.</p>

<p>Now we write directly into a live node:</p>

<pre><code class="language-javascript">let currentP = null;

function initBubble(bubble, cursor) {
  currentP = document.createElement('p');
  currentP.appendChild(document.createTextNode(''));
  bubble.insertBefore(currentP, cursor);
}
</code></pre>

<p>What we can do next is to create one paragraph with an empty text node and insert it before the cursor. That gives us a live node we can write into directly.</p>

<p>Then, for each character that arrives:</p>

<pre><code class="language-javascript">function appendChar(char, bubble, cursor) {
  if (char === '\n') {
    currentP = document.createElement('p');
    currentP.appendChild(document.createTextNode(''));
    bubble.insertBefore(currentP, cursor);
  } else {
    currentP.firstChild.textContent += char;
  }
}
</code></pre>

<p>For a regular character, we extend the text node by one character. The browser doesn’t need to recalculate the layout for that; the text grew, but nothing moved. For a newline, we create a fresh paragraph and move <code>currentP</code> forward. Layout recalculates once for that new paragraph, and that’s it.</p>

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

<h3 id="render-frequency">Render Frequency</h3>

<p>This one is most visible in the first example, the chat UI. Even with scrolling and a layout fixed, we’re still writing to the DOM on every single incoming character.</p>

<p>When the stream is moving fast, you end up hammering the DOM with updates that don’t actually matter. The fix is straightforward: hold the incoming text in a buffer instead of writing it out immediately. Once you’ve collected enough, write it all to the DOM in one go; that’s what a <strong>flush</strong> is.</p>

<p>To pull this off, we keep a simple buffer and make sure we only schedule a single update at a time. When it fires, <code>requestAnimationFrame</code> takes everything that has built up and writes it to the DOM in one shot.</p>

<pre><code class="language-javascript">let pending   = '';
let rafQueued = false;
</code></pre>

<p>When a new character streams in, we then add it to the buffer. If no flush is scheduled yet, we queue one:</p>

<pre><code class="language-javascript">function onChar(char) {
  pending += char;

  if (!rafQueued) {
    rafQueued = true;
    requestAnimationFrame(flush);
  }
}
</code></pre>

<p>The <code>rafQueued</code> flag is important. Without it, every character would schedule its own frame, and you’d end up with dozens of unnecessary flushes.</p>

<p>When the flush fires, it drains the entire buffer in one pass:</p>

<pre><code class="language-javascript">function flush() {
  for (const char of pending) {
    appendChar(char);
  }
  pending   = '';
  rafQueued = false;
  autoScroll();
}
</code></pre>

<p>All the characters that arrive after the last frame are then rendered together, right before the browser paints them. Then we clear the buffer, reset the flag, and run auto-scroll once.</p>

<pre><code class="language-javascript">let userScrolled = false;

chatEl.addEventListener('scroll', () =&gt; {
  const gap = chatEl.scrollHeight
            - chatEl.scrollTop
            - chatEl.clientHeight;

  userScrolled = gap &gt; 60;
});

function autoScroll() {
  if (!userScrolled) {
    chatEl.scrollTop = chatEl.scrollHeight;
  }
}
</code></pre>

<p>If the gap is small, we keep auto-scrolling. If it grows, we assume the user scrolled up, and we stop. That small threshold helps avoid jitter when new lines slightly change the height. Also, remember to reset <code>userScrolled</code> when a new stream starts.</p>

<p>Once scrolling is under control, another issue becomes obvious. As the message grows, it keeps shifting:</p>

<ul>
<li>It starts as one line,</li>
<li>It expands, then</li>
<li>It pushes everything below it.</li>
</ul>

<p>Nothing is technically broken, but it doesn’t feel stable. A common approach is to rebuild the whole message on every update:</p>

<pre><code class="language-javascript">bubble.innerHTML = '';

fullText.split('\n').forEach(line =&gt; {
  const p = document.createElement('p');
  p.textContent = line || '\u00A0';
  bubble.appendChild(p);
});

bubble.appendChild(cursorEl);
</code></pre>

<p>This works, but it is doing too much work. Every update destroys and rebuilds the DOM, forcing layout recalculation each time. That’s why everything keeps shifting. The idea is to write into the current paragraph and only create a new one when we actually hit a line break.</p>

<pre><code class="language-javascript">let currentP = null;

function initBubble(bubble, cursor) {
  currentP = document.createElement('p');
  currentP.appendChild(document.createTextNode(''));
  bubble.insertBefore(currentP, cursor);
}
</code></pre>

<p>And then update it character by character:</p>

<pre><code class="language-javascript">function appendChar(char, bubble, cursor) {
  if (char === '\n') {
    currentP = document.createElement('p');
    currentP.appendChild(document.createTextNode(''));
    bubble.insertBefore(currentP, cursor);
  } else {
    currentP.firstChild.textContent += char;
  }
}
</code></pre>

<p>Now we’re no longer rebuilding everything. Most updates just extend a text node, which is cheap and doesn’t trigger large layout shifts. It also fixes the small cursor flicker you might have noticed earlier, since we’re no longer removing and re-adding it.</p>

<p>At this point, the UI already feels better, but there is still something subtle going on. We are still updating the DOM on every character. At higher speeds, that becomes a lot of small updates, many of which you never actually see.</p>

<p>Instead of rendering immediately, we can buffer the incoming characters and apply them once per frame.</p>

<pre><code class="language-javascript">let pending = '';
let rafQueued = false;

function onChar(char) {
  pending += char;

  if (!rafQueued) {
    rafQueued = true;
    requestAnimationFrame(flush);
  }
}
</code></pre>

<p>At this point, we’re not touching the DOM yet, but only collecting characters as they arrive. Then, right before the next frame is painted, we flush everything at once:</p>

<pre><code class="language-javascript">function flush() {
  for (const char of pending) {
    appendChar(char);
  }

  pending = '';
  rafQueued = false;

  autoScroll();
}
</code></pre>

<p>These separate two things that were previously tied together:</p>

<ol>
<li>How fast data arrives, and</li>
<li>When the UI updates.</li>
</ol>

<p>The result looks the same, but the browser does less work, resulting in the UI feeling smoother, especially when the stream is set to a faster speed.</p>














<figure class="
  
  
  ">
  
    <a href="https://codesandbox.io/embed/pk7tk5?view=preview">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="566"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png"
			
			sizes="100vw"
			alt="Broken vs. fixed"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Open in <a href='https://codesandbox.io/embed/pk7tk5?view=preview'>CodeSandbox</a>. (<a href='https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/4-broken-vs-fixed.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>None of these changes is a big effort on its own. But once they are in place, the interface stops reacting blindly to every update. It becomes easier to read, easier to control, and a lot less distracting, even though the content is still coming in continuously.</p>

<p>There are even more considerations to take into account for ensuring a stable, predictable, and good user experience. For example, what happens if the stream is canceled mid-flow? And what can we do to ensure that user preferences are respected for things like reduced motion, keyboard navigation, and screen reader accessibility? Let’s get into those next.</p>

<h2 id="handling-interrupted-streams">Handling Interrupted Streams</h2>

<p>Most streaming interfaces include a way to stop or cancel the stream. We saw that in the demos. But stopping often leaves the UI in an awkward state. The cursor might keep blinking, buttons don’t update, and the message just freezes mid-stream with no clear indication that it didn’t finish.</p>

<p>The problem is that the stop is usually wired to do one thing: cancel the timer. That’s not enough. You also need to (1) clear the pending buffer, (2) remove the cursor, (3) mark the response as incomplete, and (4) reset the buttons. Here’s how we accomplish those.</p>

<h3 id="1-stop-the-stream-cleanly">1. Stop The Stream Cleanly</h3>

<p>Here’s what <code>stopStream</code> needs to do, in order:</p>

<ol>
<li>Cancel the timer and flip the <code>isStreaming</code> flag so no more ticks run.</li>
<li>Clear the <code>requestAnimationFrame</code> (RAF) buffer so nothing still queued gets written on the next frame.</li>
</ol>

<pre><code class="language-javascript">function stopStream() {
  clearTimeout(streamTimer);
  isStreaming = false;
  pending     = '';
  rafQueued   = false;
}
</code></pre>

<p>Clearing the <code>pending</code> property matters because there might be characters buffered from the last stream instance that haven’t been flushed yet. If you don’t clear it, the next <code>requestAnimationFrame</code> fires, drains the buffer, and writes those characters to the DOM after the stream has officially stopped.</p>

<p>Now we move on to removing the cursor by calling <code>markStopped</code> on the bubble:</p>

<pre><code class="language-javascript">if (cursorEl && cursorEl.parentNode) cursorEl.remove();
  markStopped(aiBubble);

  stopBtn.style.display  = 'none';
  retryBtn.style.display = '';
  playBtn.style.display  = '';
  setStatus('Stopped', 'stopped');
  chat.removeEventListener('scroll', onScroll);
}
</code></pre>

<p>The <code>cursorEl.parentNode</code> check is there because <code>stopStream</code> is also called internally when a new message fires mid-stream, at which point the cursor might already be gone. Calling <code>remove()</code> on a detached node throws, so we check first.</p>

<p><code>markStopped</code> appends a small label to the bottom of the bubble so the user knows the response didn’t finish:</p>

<pre><code class="language-javascript">function markStopped(bubble) {
  if (!bubble) return;
  bubble.classList.add('stopped');

  const label = document.createElement('span');
  label.className = 'stopped-label';
  label.textContent = 'response stopped';
  bubble.appendChild(label);
}
</code></pre>

<p>The null check on <code>bubble</code> handles the edge case where stop fires before the AI message element has been initialized, which can happen if the user clicks stop during the 300ms delay before the bubble appears.</p>

<h3 id="provide-a-retry-option">Provide A Retry Option</h3>

<p>If the stream simply stops &mdash; perhaps due to a network issue or some other unexpected error &mdash; we ought to provide the user with a path to re-attempt the stream. What that basically means is preventing the UI from doing the expensive work needed to scroll back up to the top, re-read the prompt, and retype it. With a retry option, the user only needs to click a button, and the stream restarts from the current position.</p>

<p>To make that work, we need to hold onto the question when the stream starts:</p>

<pre><code class="language-javascript">let lastQuestion = '';

function startStream(question, answer) {
  lastQuestion = question;
  // rest of setup...
}
</code></pre>

<p>Then, when the retry attempt runs, we reset everything and start fresh:</p>

<pre><code class="language-javascript">function retryStream() {
  if (currentMsgEl && currentMsgEl.parentNode) {
    currentMsgEl.remove();
  }

  charIndex    = 0;
  userScrolled = false;
  pending      = '';
  rafQueued    = false;
  isStreaming  = true;

  retryBtn.style.display = 'none';
  stopBtn.style.display  = '';
  setStatus('Streaming...', 'streaming');

  chat.addEventListener('scroll', onScroll, { passive: true });

  setTimeout(() =&gt; {
    initAIMsg();
    tick(lastAnswer);
  }, 200);
}
</code></pre>

<p>The reset is critical. Every piece of state needs to go back to its initial value, just like a brand new stream.</p>

<p><strong>Note:</strong> We remove the entire message row (<code>currentMsgEl</code>), not just the bubble. If only the bubble is removed, the layout wrapper and avatar remain persistent and break the structure.</p>

<h3 id="send-a-new-message-mid-stream">Send A New Message Mid-Stream</h3>

<p>There’s one more edge case that’s easy to miss. If the user sends a new message while a stream is still running, you end up with two loops writing to the DOM at the same time. The result is messy, and characters from different responses get mixed together.</p>

<p>Here’s what to do: stop the current stream before starting a new one.</p>

<pre><code class="language-javascript">function startStream(question, answer) {
  if (isStreaming) {
    clearTimeout(streamTimer);
    isStreaming = false;
    pending     = '';
    rafQueued   = false;
    if (cursorEl && cursorEl.parentNode) cursorEl.remove();
    chat.removeEventListener('scroll', onScroll);
  }

  // now reset and start fresh
  charIndex    = 0;
  userScrolled = false;
  isStreaming  = true;
  lastQuestion = question;
  // ...
}
</code></pre>

<p>Here, we inline the cleanup rather than calling <code>stopStream</code> directly because <code>stopStream</code> also calls <code>markStopped</code> and resets the buttons. The next demo has all three behaviors wired up. You can start a stream, hit “Stop” mid-stream, and the cursor disappears, the “response stopped” label appears, and a “Retry” buttons displayed.</p>














<figure class="
  
  
  ">
  
    <a href="https://codesandbox.io/embed/9cfy92?view=preview">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="505"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png"
			
			sizes="100vw"
			alt="Interruptible stream"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Open in <a href='https://codesandbox.io/embed/9cfy92?view=preview'>CodeSandbox</a>. (<a href='https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/5-interruptible-stream.png'>Large preview</a>)
    </figcaption>
  
</figure>

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

<h2 id="accessibility">Accessibility</h2>

<p>Streaming interfaces are often built and tested with a mouse, so they may feel just fine in a browser, but break down in other situations that may not have been considered, like whether a screen reader announces new content at all. Or navigating with a keyboard might get stuck or lose focus as things update. And, of course, moving text can be uncomfortable &mdash; or even disabling &mdash; for <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">those with motion sensitivities</a>.</p>

<p>The good part is that you do not need to rebuild everything to accommodate these things; they can be fixed with solutions that sit on top of what is already there.</p>

<h3 id="accommodating-assistive-technology-with-live-regions">Accommodating Assistive Technology With Live Regions</h3>

<p>Screen readers don’t automatically announce content that shows up on its own. They usually read things when the user moves to them. So, in a streaming UI, where text builds up over time, nothing gets announced. The content is there, but the user doesn’t hear anything.</p>

<p>The fix is <a href="https://w3c.github.io/aria/#aria-live"><code>aria-live</code></a>. It tells the browser to watch a container and announce updates as they happen, without the user needing to move focus.</p>

<pre><code class="language-html">&lt;div
  id="chat"
  role="log"
  aria-live="polite"
  aria-atomic="false"
  aria-label="Chat messages"
&gt;&lt;/div&gt;
</code></pre>

<ul>
<li><code>role=&quot;log&quot;</code> tells assistive tech this is a stream of updates, like a running transcript. Some tools handle this automatically, but it’s safer to be explicit so behavior stays consistent.</li>
<li><code>aria-atomic=&quot;false&quot;</code> makes sure only the new content is announced. Without it, some screen readers try to read the whole message again on every update, which quickly becomes unusable.</li>
<li><code>aria-live=&quot;polite&quot;</code> queues updates instead of interrupting. Use <code>assertive</code> only for things that really need immediate attention, like errors.</li>
</ul>

<h3 id="handling-incomplete-states">Handling Incomplete States</h3>

<p>Earlier, we inserted a “Response Stopped” label to the message when the stream stops mid-stream. Visually, that’s enough. But for a screen reader, that change needs to be announced.</p>

<p>Since the message is inside a live region with <code>aria-live=&quot;polite&quot;</code>, the label will be automatically announced as new content when it’s added to the DOM. The live region already handles the announcement, so no additional ARIA is needed on the label itself.</p>

<p>The <strong>Retry</strong> button that appears next also needs context. If a screen reader simply says “Retry, button,” it’s not clear what action that refers to. You can fix that by adding an <code>aria-label</code> that includes the original question:</p>

<pre><code class="language-javascript">retryBtn.setAttribute(
  'aria-label',
  `Retry: ${lastQuestion.slice(0, 60)}`
);
</code></pre>

<p>What you can do here is to set this label when the button appears, not on page load:</p>

<pre><code class="language-javascript">retryBtn.style.display = 'inline-block';
retryBtn.setAttribute(
  'aria-label',
  `Retry: ${lastQuestion.slice(0, 60)}`
);
</code></pre>

<p>We also call <code>retryBtn.focus()</code> after stopping. That way, keyboard users don’t have to <code>Tab</code> around with the keyboard to find the next action.</p>

<p><strong>Testing with assistive technology:</strong> Don’t rely on assumptions about how screen readers announce this. Test with actual tools like NVDA (Windows), JAWS (Windows), or VoiceOver (Mac/iOS). Browser DevTools can show you what’s exposed in the accessibility tree, but they can’t tell you how the content <em>sounds</em>. A real screen reader will reveal whether the announcement is happening at the right time and in the right way.</p>

<h3 id="account-for-keyboard-navigation">Account For Keyboard Navigation</h3>

<p>The controls need to work with the keyboard while the UI is live, so the Stop button has to be reachable. For someone not using a mouse, <kbd>Tab</kbd> + <kbd>Enter</kbd> is the only way to cancel a running stream.</p>

<p>Using <code>display: none</code> is fine for hiding buttons; it removes them from the tab order. The problem is using things like <code>opacity: 0</code> or <code>visibility: hidden</code>. Those hide elements visually, but they can still receive focus, so users end up tabbing onto something they can’t see.</p>

<p>Use <code>:focus-visible</code> so the focus ring shows up for keyboard navigation, but not for mouse clicks:</p>

<pre><code class="language-css">btn:focus-visible {
  outline: 2px solid &#35;1d9e75;
  outline-offset: 2px;
}
</code></pre>

<p>The cursor inside the message should have <code>aria-hidden=&quot;true&quot;</code>. It’s just visual. Without that, some screen readers try to read it as text, which gets distracting.</p>

<h3 id="motion-sensitivity">Motion Sensitivity</h3>

<p>The typewriter effect we see in practically every AI interface produces constant motion. As we’ve already discussed, certain amounts of motion can be disabling. Thankfully, browsers expose <code>prefers-reduced-motion</code>, which detects a user’s motion preferences at the operating system level.</p>

<p>For streaming, the best approach is simple: skip the animation and render the full response at once. The content stays the same, only without the motion.</p>

<pre><code class="language-javascript">const reducedMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;
</code></pre>

<pre><code class="language-javascript">if (reducedMotion) {
  initAIMsg();
  for (const char of text) appendChar(char);
  if (cursorEl && cursorEl.parentNode) cursorEl.remove();
  done();
  return;
}
tick(text); // normal animation
</code></pre>

<p>In CSS, the cursor blink also needs to stop. Despite being a minor detail, a blinking cursor element counts as <a href="https://www.w3.org/WAI/WCAG21/Understanding/three-flashes-or-below-threshold.html">flashing content</a>.</p>

<pre><code class="language-css">@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; opacity: 1; }
}
</code></pre>

<p>There we go! The demo below puts everything from this article together, so you can see how these patterns work in practice. It also includes a reduced motion toggle, so you can test the instant render version easily.</p>














<figure class="
  
  
  ">
  
    <a href="https://codesandbox.io/embed/vd9mnk?view=preview">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="594"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png"
			
			sizes="100vw"
			alt="Accessible streaming"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Open in <a href='https://codesandbox.io/embed/vd9mnk?view=preview'>CodeSandbox</a>. (<a href='https://files.smashing.media/articles/designing-stable-interfaces-streaming-content/6-accessible-streaming.png'>Large preview</a>)
    </figcaption>
  
</figure>

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

<p>Streaming itself is mostly solved. Getting data from the server to the client is not the hard part anymore. What breaks is the UI on top of it.</p>

<p>When content updates continuously, small things start to matter, like scroll behavior, layout stability, render timing, and how the interface responds to user actions. If those aren’t handled well, the UI feels unstable and hard to use.</p>

<p>The patterns in this article fix that by:</p>

<ul>
<li>Keeping scroll position under the user’s control,</li>
<li>Updating only what has changed,</li>
<li>Batching renders per frame,</li>
<li>Handling stop and retry actions, and</li>
<li>Making the interface accessible.</li>
</ul>

<p>You don’t need all of these every time. But when streaming is involved, these are the places things usually go wrong.</p>

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

<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Using Server-Sent Events</a><br />
How to open a connection, handle events, and reconnect when needed. This is the transport layer, everything here builds on.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Streams API</a><br />
Streaming data directly from <code>fetch</code>. Useful when you need more control than SSE.</li>
<li><a href="https://developer.chrome.com/docs/devtools/performance">Chrome DevTools Performance panel</a><br />
Helps you see layout recalculations and paint costs, so you can verify performance improvements.</li>
<li>“<a href="https://web.dev/articles/dom-size-and-interactivity">How Large DOM Sizes Affect Interactivity, And What You Can Do About It</a>”, Jeremy Wagner<br />
Why large DOM trees slow things down, and how to keep them under control in long streaming sessions.</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>Vitaly Friedman</author><title>A Practical Guide To Design Principles</title><link>https://www.smashingmagazine.com/2026/04/practical-guide-design-principles/</link><pubDate>Wed, 01 Apr 2026 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/04/practical-guide-design-principles/</guid><description>Design principles with references, examples, and methods for quick look-up. Brought to you by &lt;a href="https://ai-design-patterns.com">Design Patterns For AI Interfaces&lt;/a>, &lt;strong>friendly video courses on UX&lt;/strong> and design patterns by Vitaly.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/04/practical-guide-design-principles/" />
              <title>A Practical Guide To Design Principles</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>A Practical Guide To Design Principles</h1>
                  
                    
                    <address>Vitaly Friedman</address>
                  
                  <time datetime="2026-04-01T10:00:00&#43;00:00" class="op-published">2026-04-01T10:00:00+00:00</time>
                  <time datetime="2026-04-01T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>We often see design principles as rigid guidelines that dictate design decisions. But actually, they are an incredible tool to <strong>rally the team around a shared purpose</strong> and document the values and beliefs that an organization embodies.</p>

<p>They align teams and inform decision-making. They also keep us afloat amidst all the hype, big assumptions, desire for faster delivery, and AI workslop. But how do we choose the right ones, and how do we get started? Let’s find out.</p>

<h2 id="real-world-design-principles">Real-World Design Principles</h2>

<p>In times when we can generate any passable design and code within minutes, we need to decide better <strong>what’s worth designing and building</strong> &mdash; and what values we want our products to embody.</p>

<p>It’s similar to voice and tone. You might not design it intentionally, but then end users will define it for you. And so, without principles, many company initiatives are <strong>random, sporadic, ad-hoc</strong> &mdash; and feel vague, inconsistent, or simply dull to the outside world.</p>

<p><strong>Design principles</strong> are guidelines and design considerations that <a href="https://ixdf.org/literature/topics/design-principles">designers apply with discretion</a> &mdash; by default, without debating or discussing what has already been agreed upon.</p>

<p>One fantastic resource that I keep coming back to after all these years is Ben Brignell’s <a href="https://principles.design">Principles.design</a>. It has <strong>230 pointers for design principles and methods</strong>, searchable and tagged, covering everything from language and infrastructure to hardware and organizations.</p>

<h2 id="10-principles-of-good-design">10 Principles Of Good Design</h2>

<p>There is no shortage of principles out there. But the good ones are more than just being <em>visionary</em> &mdash; they <strong>have a point of view</strong>, and they explain what we <em>don’t do</em> as much as what we do. They also explain what <strong>we stand for</strong> in the world &mdash; beyond profits, stock prices, and all the hype and noise around us.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.vitsoe.com/gb/about/good-design#good-design-is-innovative">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="559"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg"
			
			sizes="100vw"
			alt="10 legendary principles for good design"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      10 legendary principles for good design, by <a href='https://www.vitsoe.com/gb/about/good-design#good-design-is-innovative'>Dieter Rams</a>. Still relevant, after all these years. (<a href='https://files.smashing.media/articles/stop-endless-debates-design-principles/1-principles-good-design.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Many years ago, I encountered <a href="https://www.vitsoe.com/gb/about/good-design#good-design-is-innovative">Dieter Rams’ 10 principles of good design</a> (see above), a very <strong>humble, practical and tangible</strong> overview of principles that were informing, shaping, and guarding his design work at Braun.</p>

<p>There are <strong>no visionary claims</strong>, and no big bold statements: just a clear overview of what we do, and where our ambition and care lie for the products we are designing. It’s honest, sincere, and in many ways beautifully <strong>humane</strong>.</p>

<h3 id="examples-of-design-principles">Examples Of Design Principles</h3>

<p>There are plenty of <strong>wonderful examples</strong> that I keep close:</p>

<ul>
<li><a href="https://www.anthropic.com/constitution">Anthropic’s Constitution</a></li>
<li><a href="https://principles.design/examples/principles-of-product-design">Principles of Product Design</a>, by Joshua Porter</li>
<li><a href="https://principles.design/examples/20-guiding-principles-for-experience-design">Guiding Principles for Experience Design</a>, by Whitney Hess, PCC</li>
<li><a href="https://github.com/Heydon/principles-of-web-accessibility">Principles of Web Accessibility</a>, by Heydon Pickering</li>
<li><a href="https://humanebydesign.com">Humane by Design</a>, by Jon Yablonski</li>
<li><a href="https://principles.design/examples/designing-for-voice-interfaces">Designing Voice UX Principles</a>, by Brian Colcord</li>
<li><a href="https://linear.app/developers/aig">Agentic Design Principles</a>, by Linear</li>
<li><a href="https://www.intercom.com/blog/principles-bot-design/">AI Chatbot Design Principles</a>, by Emmet Connolly</li>
<li><a href="https://voiceprinciples.com">Voice UX Principles</a>, by Ben Sauer</li>
</ul>

<h3 id="design-principles-in-design-systems">Design Principles In Design Systems</h3>

<ul>
<li><a href="https://guides.18f.org/">18F</a></li>
<li><a href="https://styleguide.audi.com/document/2440#/-/experience-principles">Audi</a></li>
<li><a href="https://www.ibm.com/design/language/philosophy/principles/">Carbon (IBM)</a></li>
<li><a href="https://acorn.firefox.com/latest/get-started/firefox-design-principles-5ezPvNdo">Firefox</a></li>
<li><a href="https://www.gov.uk/guidance/government-design-principles">Gov.uk</a></li>
<li><a href="https://contentdesign.intuit.com/style-and-usage/our-principles/">Intuit</a></li>
<li><a href="https://service-manual.nhs.uk/design-system/design-principles">NHS</a></li>
<li><a href="https://nordhealth.design/principles/">Nordhealth</a></li>
<li><a href="https://base.uber.com/6d2425e9f/p/434f39-principles">Uber</a></li>
</ul>

<h2 id="how-to-establish-design-principles">How To Establish Design Principles</h2>

<p>Design principles can be personal, but usually they are committed to and shaped by the <strong>entire product team</strong>. Design principles <strong>aren’t just for designers</strong>. User’s experience is <em>everything</em> from performance to support to customer service, and ideally, participants would cover these areas as well.</p>

<p>In practice, though, establishing principles might feel incredibly challenging. They are abstract and fluffy and often ambiguous, and often very difficult to agree upon.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.figma.com/community/file/1051212964426062558">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="461"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png"
			
			sizes="100vw"
			alt="Workshop kit for a design principles workshop"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://www.figma.com/community/file/1051212964426062558'>One of many workshop kits</a> for a design principles workshop. (<a href='https://files.smashing.media/articles/stop-endless-debates-design-principles/2-design-principles-workshop.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>You can get started with a <strong>simple 8-step workshop</strong> (inspired by <a href="https://medium.com/@marcintreder/design-system-sprint-4-design-principles-8efb22d8a208">Marcin Treder</a>, <a href="https://medium.com/design-bootcamp/design-principles-workshop-a-template-15c7c90458f2">Maria Meireles</a> and <a href="https://www.better.care/blog-en/establishing-design-principles-for-a-design-system-and-what-it-taught-us/">Better</a>):</p>

<ol>
<li><strong>Pre-session Research</strong><br />
Study how users speak about the products, what they appreciate, and the words they use.</li>
<li><strong>Get Into Principles Mode</strong><br />
Invite 6–8 participants, ask them to choose their favorite object, and describe it in 3 words.</li>
<li><strong>Product Analogies</strong><br />
Compare product to tangible items (e.g., ‘A Porsche 911’ or ‘a Braun audio system’).</li>
<li><strong>Extract Attributes</strong><br />
Individually, in silence, everyone writes 3–5 initial principles, which are then grouped by theme for review.</li>
<li><strong>Link Attributes To Research</strong><br />
Link attributes to actual user pain points or desires, to make sure they are grounded in reality.</li>
<li><strong>Value Statements</strong><br />
We write <em>‘We want X because of Y’</em> sentences that express the rationale behind our thinking.</li>
<li><strong>Move to Principles</strong><br />
Remove analogies to create enduring rules that will guide our design process.</li>
<li><strong>Reality Check</strong><br />
Search for both positive and negative examples in our products to see where principles are being met or ignored.</li>
</ol>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.better.care/blog-en/establishing-design-principles-for-a-design-system-and-what-it-taught-us/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="492"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg"
			
			sizes="100vw"
			alt="Variants of sentences for establishing design principles"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Voting for the most relevant sentences in keyword groups. From <a href='https://www.better.care/blog-en/establishing-design-principles-for-a-design-system-and-what-it-taught-us/'>Better</a>. (<a href='https://files.smashing.media/articles/stop-endless-debates-design-principles/3-design-principles.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h3 id="useful-starter-kits-for-principles-workshops">Useful Starter Kits For Principles Workshops</h3>

<ul>
<li><a href="https://medium.com/design-bootcamp/design-principles-workshop-a-template-15c7c90458f2">Design Principles Workshop (Figma Template)</a>, by Maria Meireles</li>
<li><a href="https://www.figma.com/community/file/1051212964426062558">Design Principles Workshop (FigJam Template)</a>, by Richard Picot</li>
<li><a href="https://miro.com/templates/design-principles-workshop/">How to Create Design Principles (Miro Workshop Template)</a>, by NanoGiants</li>
</ul>

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

<p>Creating principles is only a small portion of the work; most work is about <strong>effectively sharing and embedding them</strong>. It’s difficult to get anywhere without finding ways to <strong>make design principles a default</strong> &mdash; by revisiting settings, templates, naming conventions, and output.</p>

<p>Principles help <strong>avoid endless discussions</strong> that often stem from personal preferences or taste. But design should not be a matter of taste; it must be guided by our goals and values. Design principles can help with just that.</p>

<h2 id="meet-design-patterns-for-ai-interfaces">Meet “Design Patterns For AI Interfaces”</h2>

<p>Meet <a href="https://ai-design-patterns.com/"><strong>Design Patterns For AI Interfaces</strong></a>, Vitaly’s new <strong>video course</strong> with 100s of real-life examples and UX guidelines to design AI features that people actually use &mdash; with a <a href="https://smashingconf.com/online-workshops/workshops/ai-interfaces-vitaly-friedman/">live UX training</a> later this year. <a href="https://www.youtube.com/watch?v=jhZ3el3n-u0">Jump to a free preview</a>.</p>

<p><figure class="article__image" style="margin-bottom: 0"><a href="https://ai-design-patterns.com/"><img style="border-radius:11px" loading="lazy" decoding="async" fetchpriority="low" width="800" height="414" srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 2000w" src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png" sizes="100vw" alt="Design Patterns For AI Interfaces promo picture"></a><figcaption class="op-vertical-bottom">Meet <a href="https://ai-design-patterns.com/">Design Patterns For AI Interfaces</a>, Vitaly’s video course on interface design &amp; UX.</figcaption></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>450<sup class="sup">.00</sup></span></span> <span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>799<sup class="sup">.00</sup></span></span></span></span>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3476562?price_id=4401578" class="btn btn--full btn--medium btn--text-shadow">
Get Video + UX Training<div></div></a><p class="book-cta__desc">30 video lessons (10h) + <a href="https://smashingconf.com/online-workshops/workshops/ai-interfaces-vitaly-friedman/">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>275<sup class="sup">.00</sup></span></span><span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>395<sup class="sup">.00</sup></span></span></span></div>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3476562?price_id=4397456" 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">30 video lessons (10h). 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></p>

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

<ul>
<li><a href="https://principles.design">Design Principles Collection</a>, by Ben Brignell</li>
<li>“<a href="https://medium.com/@marcintreder/design-system-sprint-4-design-principles-8efb22d8a208">How To Establish Design Principles</a>”, by Marcin Treder</li>
<li>“<a href="https://www.better.care/blog-en/establishing-design-principles-for-a-design-system-and-what-it-taught-us/">Establishing Design Principles for a Design System and What It Taught Us</a>”, by Better Design Team</li>
<li><a href="https://principles.adactio.com">Design Principles</a>, by Jeremy Keith</li>
<li><a href="https://www.designprinciplesftw.com">Design Principles Collection</a>, by Gabriel Svennerberg</li>
<li><a href="https://medium.com/design-bootcamp/design-principles-workshop-a-template-15c7c90458f2">Design Principles Workshop (Figma Template)</a>, by Maria Meireles</li>
<li><a href="https://www.figma.com/community/file/1051212964426062558">Design Principles Workshop (FigJam Template)</a>, by Richard Picot</li>
<li><a href="https://miro.com/templates/design-principles-workshop/">How to Create Design Principles (Miro Workshop Template)</a>, by NanoGiants</li>
<li><a href="https://designsystems.surf/components/modal">Modals in Design Systems</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>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Vitaly Friedman</author><title>Modal vs. Separate Page: UX Decision Tree</title><link>https://www.smashingmagazine.com/2026/03/modal-separate-page-ux-decision-tree/</link><pubDate>Thu, 19 Mar 2026 15:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/03/modal-separate-page-ux-decision-tree/</guid><description>How to choose between modals and pages, when to avoid modals, and how to determine the right level of interruption or navigation. Brought to you by &lt;a href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns&lt;/a>, a &lt;strong>friendly video course on UX&lt;/strong> and design patterns by Vitaly.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/03/modal-separate-page-ux-decision-tree/" />
              <title>Modal vs. Separate Page: UX Decision Tree</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Modal vs. Separate Page: UX Decision Tree</h1>
                  
                    
                    <address>Vitaly Friedman</address>
                  
                  <time datetime="2026-03-19T15:00:00&#43;00:00" class="op-published">2026-03-19T15:00:00+00:00</time>
                  <time datetime="2026-03-19T15:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>You probably have been there before. How do we choose between <strong>showing a modal</strong> to users, and when do we navigate them to a separate, new page? And does it matter at all?</p>

<p>Actually, it does. The decision influences users’ flow, their context, their ability to look up details, and with it <strong>error frequency and task completion</strong>. Both options can be disruptive and frustrating &mdash; at the wrong time, and at the wrong place.</p>

<p>So we’d better get it right. Well, let’s see how to do just that.</p>

<h2 id="modals-vs-dialogs-vs-overlays-vs-lightboxes">Modals vs. Dialogs vs. Overlays vs. Lightboxes</h2>

<p>While we often speak about a single modal UI component, we often ignore fine, intricate nuances between all the different types of modals. In fact, <strong>not every modal is the same</strong>. Modals, dialogs, overlays, and lightboxes &mdash; all sound similar, but they are actually quite different:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.nngroup.com/articles/popups/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="573"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg"
			
			sizes="100vw"
			alt="A 2x2 grid illustrating four types of dialog boxes: nonlightbox modal, nonlightbox nonmodal, lightbox modal, and lightbox nonmodal. Each shows a modal window on a browser interface."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Understanding modal vs. nonmodal and lightbox vs. nonlightbox dialog boxes for good UX. (Image source: <a href='https://www.nngroup.com/articles/popups/'>Popups by NN/g</a>) (<a href='https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/2-modal-nonmodal-lightbox-nonlightbox-dialog-boxes.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<ul>
<li><strong>Dialog</strong><br />
A generic term for “conversation” (user ↔ system).</li>
<li><strong>Overlay</strong><br />
A small content panel displayed on top of a page.</li>
<li><strong>Modal</strong><br />
User must interact with overlay + background <strong>disabled</strong>.</li>
<li><strong>Nonmodal</strong><br />
User must interact with overlay + background <strong>enabled</strong>.</li>
<li><strong>Lightbox</strong><br />
Dimmed background to focus attention on the modal.</li>
</ul>

<p>As Anna Kaley <a href="https://www.nngroup.com/articles/popups/">highlights</a>, most overlays appear at the wrong time, interrupt users during critical tasks, use poor language, and break users’ flow. They are <strong>interruptive by nature</strong>, and typically with a high level of severity without a strong need for that.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://miro.medium.com/v2/1*wUxWMpp5GXXvg2fHhQWoWQ.jpeg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="385"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg"
			
			sizes="100vw"
			alt="A diagram categorizing overlay types into modal and non-modal components, with examples like dialogs, navigation drawers, snackbars, and tooltips."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The many sides of modals and overlays. A little tree to understand the differences for UI components. (Image source: <a href='https://uxplanet.org/modal-vs-page-a-decision-making-framework-34453e911129'>Ryan Neufeld</a>) (<a href='https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/3-overlay-types-modal-non-modal-components.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Surely users <em>must</em> be slowed down and interrupted if the consequences of their action have a high impact, but for most scenarios <strong>non-modals are much more subtle</strong> and a more friendly option to bring something to the user’s attention. If anything, I always suggest it to be a <strong>default</strong>.</p>

<h2 id="modals-for-single-self-contained-tasks">Modals → For Single, Self-Contained Tasks</h2>

<p>As designers, we often dismiss modals as irrelevant and annoying &mdash; <em>and often they are!</em> &mdash; yet they have their value as well. They can be very helpful to <strong>warn users about potential mistakes</strong> or help them avoid data loss. They can also help perform related actions or drill down into details without interrupting the current state of the page.</p>

<p>But the biggest advantage of modals is that they help users <strong>keep the context</strong> of the current screen. It doesn’t mean just the UI, but also edited input, scrolling position, state of accordions, selection of filters, sorting, and so on.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="526"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png"
			
			sizes="100vw"
			alt="Equity filters panel showing categories and a modal interface to set intraday price change conditions."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Nonmodal in action: large and small overlays for filters and a modal for customization work well on <a href='https://finance.yahoo.com/markets/stocks/most-active/'>Yahoo! Finance</a>. (<a href='https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/4-nonmodal.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>At times, users need to <strong>confirm a selection quickly</strong> (e.g., filters as shown above) and then proceed immediately from there. Auto-save can achieve the same, of course, but it’s not always needed or desired. And blocking the UI is often not a good idea.</p>

<p>However, modals aren’t used for any tasks. Typically, we use them for <strong>single, self-contained tasks</strong> where users should jump in, complete a task, and then return to where they were. Unsurprisingly, they do work well for high-priority, short interactions (e.g., alerts, destructive actions, quick confirmations).</p>

<p><strong>When modals help</strong>:</p>

<p>🚫 Modals are often disruptive, invasive, and confusing.<br />
🚫 They make it difficult to compare and copy-paste.<br />
✅ Yet modals allow users to maintain multiple contexts.<br />
✅ Useful to prevent irreversible errors and data loss.<br />
✅ Useful if sending users to a new page would be disruptive.</p>

<p>✅ Show a modal only if users will value the disruption.<br />
✅ By default, prefer non-blocking dialogs (“nonmodals”).<br />
✅ Allow users to minimize, hide, or restore the dialog later.<br />
✅ Use a modal to slow users down, e.g., verify complex input.<br />
✅ Give a way out with “Close”, ESC key, or click outside the box.</p>

<h2 id="pages-for-complex-multi-step-workflows">Pages → For Complex, Multi-Step Workflows</h2>

<p>Wizards or <strong>tabbed navigation within modals</strong> doesn’t work too well, even in complex enterprise products — there, side panels or drawers typically work better. Troubles start when users need to compare or reference data points — yet modals block this behavior, so they re-open the same page in multiple tabs instead.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="735"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg"
			
			sizes="100vw"
			alt="A modal with the text saying ‘We use too many damn modals. Let us just not’."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Perhaps, we use <a href='https://modalzmodalzmodalz.com'>Too Many Modals</a>. A not-very-modal-friendly project by Adrian Egger. (<a href='https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/5-modal.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For more complex flows and multi-step processes, <strong>standalone pages work best</strong>. Pages also work better when they demand the user’s full attention, and reference to the previous screen isn’t very helpful. And drawers work for sub-tasks that are too complex for a simple modal, but don&rsquo;t need a full page navigation.</p>

<p><strong>When to avoid modals</strong>:</p>

<p>🚫 Avoid modals for <strong>error messages</strong>.<br />
🚫 Avoid modals for <strong>feature notifications</strong>.<br />
🚫 Avoid modals for <strong>onboarding experience</strong>.<br />
🚫 Avoid modals for complex, <strong>lengthy multi-step-tasks</strong>.<br />
🚫 Avoid <strong>multiple nested modals</strong> and use prev/next instead.<br />
🚫 Avoid <strong>auto-triggered modals</strong> unless absolutely necessary.</p>

<h2 id="avoid-both-for-repeated-tasks">Avoid Both For Repeated Tasks</h2>

<p>In many complex, task-heavy products, users will find themselves performing the same tasks repeatedly, over and over again. There, <strong>both modals and new page navigations add friction</strong> because they interrupt the flow or force users to gather missing data between all the different tabs or views.</p>

<p>Too often, users end up with a broken experience, full of never-ending confirmations, exaggerated warnings, verbose instructions, or just missing reference points. As <a href="https://www.linkedin.com/feed/update/urn:li:activity:7417845782365560832/?dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287417848602338902016%2Curn%3Ali%3Aactivity%3A7417845782365560832%29">Saulius Stebulis mentioned</a>, in these scenarios, <strong>expandable sections</strong> or <strong>in-place editing</strong> often work better &mdash; they keep the task anchored to the current screen.</p>

<p>In practice, in many scenarios, users don’t complete their tasks in isolation. They need to look up data, copy-paste values, refine entries in different places, or just review similar records as they work through their tasks.</p>

<p>Overlays and drawers are more helpful in maintaining access to background data during the task. As a result, the context always stays in its place, available for reference or copy-paste. Save modals and page navigation for moments where the interruption genuinely adds value &mdash; especially to prevent critical mistakes.</p>

<h2 id="modals-vs-pages-a-decision-tree">Modals vs. Pages: A Decision Tree</h2>

<p>A while back, Ryan Neufeld put together a <a href="https://uxplanet.org/modal-vs-page-a-decision-making-framework-34453e911129">very helpful guide</a> to help designers <strong>choose between modals and pages</strong>. It comes with a handy <a href="https://miro.medium.com/v2/1*JQSGKbw1_iv5b85xYyNL-Q.png">PNG cheatsheet</a> and a <a href="https://docs.google.com/spreadsheets/d/1fZhXsV-IFWM0ZuMLLc8gG1BXqeQxFkCoYvSJt0gl2II/edit?gid=150659778#gid=150659778">Google Doc template</a> with questions broken down across 7 sections.</p>

<p>It’s lengthy, extremely thorough, but very easy to follow:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="2402"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png"
			
			sizes="100vw"
			alt="A decision tree diagram for UI design, asking questions to determine whether to use a Page, Non-Modal Component, Dialog, or Sheet Nav Drawer."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A flowchart to choose between page vs. modal, with the page being the default, and modals reserved for interruption and focus. Put together by wonderful <a href='https://uxplanet.org/modal-vs-page-a-decision-making-framework-34453e911129'>Ryan Neufeld</a>. (<a href='https://files.smashing.media/articles/modal-separate-page-ux-decision-tree/6-decision-tree-diagram-ui-design.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It might look daunting, but it&rsquo;s a quite simple <strong>4-step process</strong>:</p>

<ol>
<li><strong>Context of the screen</strong>.<br />
First, we check if users need to maintain the context of the underlying screen.</li>
<li><strong>Task complexity and duration</strong>.<br />
Simpler, focused, non-distracting tasks could use a modal, but long, complex flows need a page.</li>
<li><strong>Reference to underlying page</strong>.<br />
Then, we check if users often need to refer to data in the background or if the task is a simple confirmation or selection.</li>
<li><strong>Choosing the right overlay</strong>.<br />
Finally, if an overlay is indeed a good option, it guides us to choose between modal or nonmodal (leaning towards a nonmodal).</li>
</ol>

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

<p>Whenever possible, avoid blocking the entire UI. Have a dialog floating, partially covering the UI, but allowing navigation, scrolling, and copy-pasting. Or show the contents of the modal as a side drawer. Or use a vertical accordion instead. Or bring users to a separate page if you need to show a lot of detail.</p>

<p>But if you want to boost users’ efficiency and speed, <strong>avoid modals at all costs</strong>. Use them to slow users down, to bundle their attention, to prevent mistakes. As <a href="https://www.nngroup.com/articles/modal-nonmodal-dialog/">Therese Fessenden noted</a>, no one likes to be interrupted, but if you must, make sure it’s absolutely worth the cost.</p>

<h2 id="meet-smart-interface-design-patterns">Meet “Smart Interface Design Patterns”</h2>

<p>You can find a <strong>whole section about modals</strong> and alternatives in <a href="https://smart-interface-design-patterns.com/"><strong>Smart Interface Design Patterns</strong></a>, our <strong>15h-video course</strong> with 100s of practical examples from real-life projects &mdash; with a live UX training later this year. Everything from mega-dropdowns to complex enterprise tables &mdash; with 5 new segments added every year. <a href="https://www.youtube.com/watch?v=jhZ3el3n-u0">Jump to a free preview</a>. Use code <a href="https://smart-interface-design-patterns.com"><strong>BIRDIE</strong></a> to <strong>save 15%</strong> off.</p>

<figure style="margin-bottom: 0"><a href="https://smart-interface-design-patterns.com/"><img style="border-radius: 11px" decoding="async" fetchpriority="low" width="950" height="492" srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 2000w" src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg" sizes="100vw" alt="Smart Interface Design Patterns"></a><figcaption class="op-vertical-bottom">Meet <a href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns</a>, our video course on interface design &amp; UX.</figcaption></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>579<sup class="sup">.00</sup></span></span> <span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>699<sup class="sup">.00</sup></span></span></span></span>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/1645405?price_id=2163032" class="btn btn--full btn--medium btn--text-shadow">
Get Video + UX Training<div></div></a><p class="book-cta__desc">25 video lessons (15h) + <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>275<sup class="sup">.00</sup></span></span><span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>350<sup class="sup">.00</sup></span></span></span></div>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/1645405?et=paid" 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">40 video lessons (15h). 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>

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

<ul>
<li><a href="https://www.nngroup.com/articles/popups/">Different Types of Popups</a>, by Anna Kaley</li>
<li><a href="https://app.uxcel.com/courses/ui-components-n-patterns/modals--dialogs-best-practices-166">Best Practices for Designing UI Modals</a>, by Uxcel</li>
<li><a href="https://modalzmodalzmodalz.com/">We Use Too Many Damn Modals: UX Guidelines</a>, by Adrian Egger</li>
<li><a href="https://www.nngroup.com/articles/modal-nonmodal-dialog/">Modal &amp; Nonmodal Dialogs</a>, by Therese Fessenden</li>
<li><a href="https://medium.com/pulsar/modern-enterprise-ui-design-part-2-modal-dialogs-2ccd3cc33c92">Modern Enterprise UI Design: Modal Dialogs</a>, by James Jacobs</li>
<li><a href="https://designsystems.surf/components/modal">Modals in Design Systems</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>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Brecht De Ruyte</author><title>Beyond `border-radius`: What The CSS `corner-shape` Property Unlocks For Everyday UI</title><link>https://www.smashingmagazine.com/2026/03/beyond-border-radius-css-corner-shape-property-ui/</link><pubDate>Thu, 12 Mar 2026 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/03/beyond-border-radius-css-corner-shape-property-ui/</guid><description>For years, developers have been hacking around the limitations of &lt;code>border-radius&lt;/code>, using clip-path, SVG masks, and fragile workarounds just to get anything other than round corners. The new &lt;code>corner-shape&lt;/code> property finally changes that, opening the door to beveled, scooped, and squircle corners.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/03/beyond-border-radius-css-corner-shape-property-ui/" />
              <title>Beyond `border-radius`: What The CSS `corner-shape` Property Unlocks For Everyday UI</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Beyond `border-radius`: What The CSS `corner-shape` Property Unlocks For Everyday UI</h1>
                  
                    
                    <address>Brecht De Ruyte</address>
                  
                  <time datetime="2026-03-12T10:00:00&#43;00:00" class="op-published">2026-03-12T10:00:00+00:00</time>
                  <time datetime="2026-03-12T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>When I first started building websites, rounded corners required five background images, one for each corner, one for the body, and a prayer that the client wouldn’t ask for a different radius. Then the <code>border-radius</code> property landed, and the entire web collectively sighed with relief. That was over fifteen years ago, and honestly, we’ve been riding that same wave ever since. Just as then, I hope that we can look at this feature as a progressive enhancement slowly making its way to other browsers.</p>

<p>I like a good <code>border-radius</code> like any other guy, but the fact is that it only gives us one shape. Round. That’s it. Want beveled corners? Clip-path. Scooped ticket edges? SVG mask. Squircle app icons? A carefully tuned SVG that you hope nobody asks you to animate. We’ve been hacking around the limitations of <code>border-radius</code> for years, and those hacks come with real trade-offs: borders don’t follow clip-paths, shadows get cut off, and you end up with brittle code that breaks the moment someone changes a padding value.</p>

<p>Well, the new <strong><code>corner-shape</code></strong> changes all of that.</p>

<h2 id="what-is-corner-shape">What Is <code>corner-shape</code>?</h2>

<p>The <a href="https://css-tricks.com/almanac/properties/c/corner-shape/"><code>corner-shape</code></a> property is a companion to <code>border-radius</code>. It doesn’t replace it; it modifies the <em>shape</em> of the curve that <code>border-radius</code> creates. Without <code>border-radius</code>, <code>corner-shape</code> does nothing. But together, they’re a powerful pair.</p>

<p>The property accepts these values:</p>

<ul>
<li><strong><code>round</code></strong>: the default, same as regular <code>border-radius</code>,</li>
<li><strong><code>squircle</code></strong>: a superellipse, the smooth Apple-style rounded square,</li>
<li><strong><code>bevel</code></strong>: a straight line between the two radius endpoints (snipped corners),</li>
<li><strong><code>scoop</code></strong>: an inverted curve, creating concave corners,</li>
<li><strong><code>notch</code></strong>: sharp inward cuts,</li>
<li><strong><code>square</code></strong>: effectively removes the rounding, overriding <code>border-radius</code>.</li>
</ul>

<p>And you can set different values per corner, just like <code>border-radius</code>:</p>

<pre><code class="language-css">&#42;corner-shape: bevel round scoop squircle;
/&#42; top-left, top-right, bottom-right, bottom-left &#42;/
</code></pre>

<p>You can also use the <a href="https://css-tricks.com/almanac/functions/s/superellipse/"><code>superellipse()</code></a> function with a numeric parameter for fine-grained control.</p>

<pre><code class="language-css">.element { 
  border-radius: 25px;
  corner-shape: superellipse(0); /&#42; equal to 'bevel' &#42;/
}
</code></pre>

<p>So the question here might be: why not call this property “<code>border-shape</code>” instead? Well, first of all, that is <a href="https://una.im/border-shape">something completely different that we’ll get to play around with soon</a>. Second, it does apply to a bit more than borders, such as outlines, box shadows, and backgrounds. That’s the thing that the <code>clip-path</code> property could never do.</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-progressive-enhancement-matters-here">Why Progressive Enhancement Matters Here</h2>

<p>At the time of writing (March 2026), <code>corner-shape</code> is only supported in Chrome 139+ and other Chromium-based browsers. That’s a significant chunk of users, but certainly not everyone. The temptation is to either ignore the property until it’s everywhere or to build demos that fall apart without it.</p>

<p>I don’t think either approach is right. The way I see it, <code>corner-shape</code> is the perfect candidate for progressive enhancement, just as <code>border-radius</code> was in the age of Internet Explorer 6. The baseline should use the techniques we already know, such as <code>border-radius</code>, <code>clip-path</code>, <code>radial-gradient</code> masks and look intentionally good. Then, for browsers that support <code>corner-shape</code>, we upgrade the experience. Sometimes this can be as simple as just providing a more basic default; sometimes it might need to be a bit more.</p>

<p><strong>Every demo in this article is created with that progressive enhancement idea.</strong> The structure for the demos looks like:</p>

<pre><code class="language-css">@layer base, presentation, demo;
</code></pre>

<p>The <code>presentation</code> layer contains the full polished UI using proven techniques. The <code>demo</code> layer wraps everything in <code>@supports</code>:</p>

<pre><code class="language-css">@layer demo {
  @supports (corner-shape: bevel) {
    /&#42; upgrade styles here &#42;/
  }
}
</code></pre>

<p>No fallback banners, no “your browser doesn’t support this” messages. Just two tiers of design: good and better. I thought it could be nice just to show some examples. There are a few out there already, but I hope I can add a bit of extra inspiration on top of those.</p>

<h2 id="demo-1-product-cards-with-ribbon-badges">Demo 1: Product Cards With Ribbon Badges</h2>

<p>Every e-commerce site has them: those little “New” or “Sale” badges pinned to the corner of a product card. Traditionally, getting that ribbon shape means reaching for <code>clip-path: polygon()</code> or a rotated pseudo-element, let&rsquo;s call it “fiddly code” that has the chance to fall apart the moment someone changes a padding value.</p>

<p>But here’s the thing: we don’t <em>need</em> the ribbon shape in the baseline. A simple badge with slightly rounded corners tells the same story and looks perfectly fine:</p>

<pre><code class="language-css">.product__badge {
  border-radius: 0 4px 4px 0;
  background-color: var(--badge-bg);
}
</code></pre>

<p>That’s it. A small, clean label sitting flush against the left edge of the card. Nothing fancy, nothing broken. It works in every browser.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.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/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png"
			
			sizes="100vw"
			alt="Product cards with colored corner badges like “New,” “–30%,” and “Limited.”"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/1-product-cards-corner-badges.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For browsers that support <code>corner-shape</code>, we enhance:</p>

<pre><code class="language-css">@layer demo {
  /&#42; If the browser supports `corner-shape` &#42;/
  @supports (corner-shape: bevel) {
    .product {
      border-radius: 40px;
      corner-shape: squircle;
    }

    .product&#95;&#95;badge {
      padding: 0.35rem 1.4rem 0.35rem 1rem;
      border-radius: 0 16px 16px 0;
      corner-shape: round bevel bevel round;
    }
  }
}
</code></pre>

<p>The <code>round bevel bevel round</code> combination creates a directional ribbon. Round where it meets the card edge, beveled to a point on the other side. No <code>clip-path</code>, no pseudo-element tricks. Borders, shadows, and backgrounds all follow the declared shape because it <em>is</em> the shape.</p>

<p>The cards themselves upgrade from <code>border-radius: 12px</code> to a larger size and the <code>squircle</code> corner-shape, that smooth superellipse curve that makes standard rounding look slightly off by comparison. Designers will notice immediately. Everyone else will just say it “feels more premium.”</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.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/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png"
			
			sizes="100vw"
			alt="Product cards with arrow-shaped corner badges labeled “New,” “–30%,” and “Limited,” pointing inward."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/2-product-cards-ribbon-badges.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Hot tip:</strong> Using the <code>squircle</code> value on card components is one of those upgrades where the before-and-after difference can be subtle in isolation, but transformative across an entire page. It’s the iOS effect: once everything uses superellipse curves, plain circular arcs start looking out of place. In this demo, I did exaggerate a bit.</p>

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

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

<h2 id="demo-2-buttons-tags-and-components">Demo 2: Buttons, Tags, And Components</h2>

<p>This is the “component library demo”, the one that shows <code>corner-shape</code> isn’t just for hero sections. It’s practical, everyday UI: solid buttons, outlined buttons, status tags, directional arrows, notification badges.</p>

<p>The set-up is intentionally clean. Standard <code>border-radius: 10px</code> buttons with a polished typeface. Everything works, everything looks professional. You could do this without hesitation.</p>

<p>The <code>corner-shape</code> layer turns it into a showcase. Each button type gets its own shape to demonstrate the range of what’s possible:</p>

<pre><code class="language-css">@layer demo {
  @supports (corner-shape: bevel) {
    .btn--primary {
      corner-shape: bevel;
      transition: corner-shape 0.3s ease;

      &:hover {
        corner-shape: squircle;
      }
    }

    .btn--secondary {
      border-radius: 25px;
      corner-shape: superellipse(0.5);
    }

    .btn--danger {
      border-radius: 16px;
      corner-shape: squircle;
    }

    .btn--notch {
      border-radius: 12px;
      corner-shape: notch;
    }

    .btn--scoop {
      border-radius: 14px;
      corner-shape: scoop;
    }
  }
}
</code></pre>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.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/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png"
			
			sizes="100vw"
			alt="Buttins and tags before"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Before. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/3-buttons-before.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.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/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png"
			
			sizes="100vw"
			alt="Buttins and tags after"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      After. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/4-buttons-after.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The primary button starts beveled, faceted, and gem-like, and softens to <code>squircle</code> on hover. Because <code>corner-shape</code> values animate via their <code>superellipse()</code> equivalents, the transition is smooth. It’s a fun interaction that used to be hard to achieve but is now a single property (used alongside <code>border-radius</code>, of course).</p>

<p>The secondary button uses <code>superellipse(0.5)</code>, a value that is <em>between</em> a standard circle and a squircle, combined with a larger <code>border-radius</code> for a distinctive pill-like shape. The danger button gets a more prominent <code>squircle</code> with a generous radius. And <code>notch</code> and <code>scoop</code> each bring their own sharp or concave personality.</p>

<p>Beyond buttons, the status tags get <code>corner-shape: notch</code>, those sharp inward cuts that give them a machine-stamped look. The directional arrow tags use <code>round bevel bevel round</code> (and its reverse for the back arrow), replacing what used to require <code>clip-path: polygon()</code>. Now borders and shadows work correctly across all states.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="gbwLQdG"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Corner-shape: Buttons &amp; Tags [forked]](https://codepen.io/smashingmag/pen/gbwLQdG) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/gbwLQdG">Corner-shape: Buttons &amp; Tags [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>

<h2 id="demo-3-testimonial-cards">Demo 3: Testimonial Cards</h2>

<p>This demo is probably my favourite one. At its foundation, these are just testimonial cards with serif typography, a sandy palette, and scooped corners on the featured card. The design language is intentionally different from the clean geometric buttons demo, and that’s the point. <code>corner-shape</code> merely adds that extra “edge”.</p>

<p>The basis is standard <code>border-radius: 16px</code> cards. The featured testimonial spans full width with a subtle gradient and a decorative open quote mark. Normal cards alternate in a two-column grid. It already looks like something from a premium marketing site.</p>

<p>The <code>corner-shape</code> layer adds character:</p>

<pre><code class="language-css">@layer demo {
  /&#42; Progressive enhancement &#42;/
  @supports (corner-shape: scoop) {
    .testimonial {
      border-radius: 20px;
      corner-shape: squircle;
    }

    .testimonial--featured {
      border-radius: 24px;
      corner-shape: scoop;
    }

    .testimonial:not(.testimonial--featured):nth-child(even) {
      corner-shape: scoop round;
    }

    .testimonial&#95;&#95;avatar {
      border-radius: 28%;
      corner-shape: squircle;
    }
  }
}
</code></pre>

<p>The featured card gets full <code>scoop</code> corners, concave on all four sides, creating an organic, almost hand-crafted feel that matches the serif typography. Even-numbered cards mix <code>scoop round</code>, giving each one a slightly different personality without any extra markup.</p>

<p>The author avatars switch from circles to <code>squircle</code>. A small touch that makes it a bit more “different”.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.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/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png"
			
			sizes="100vw"
			alt="Testimonials before"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Fallback. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/5-testimonials-before.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.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/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png"
			
			sizes="100vw"
			alt="Testimonials after"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Supported. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/6-testimonials-after.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Hot tip:</strong> <code>corner-shape: scoop</code> pairs beautifully with serif fonts and warm color palettes. The concave curves echo the organic shapes found in editorial design, calligraphy, and print layouts. For geometric sans-serif designs, stick with <code>squircle</code> or <code>bevel</code>.</p>

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

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

<h2 id="demo-4-pricing-cards">Demo 4: Pricing Cards</h2>

<p>Every SaaS site needs a pricing page, and the visual hierarchy challenge is always the same: make one plan stand out without the others feeling neglected. This demo solves it with <code>corner-shape</code>.</p>

<p>This is quite similar to the last demo in that we once again have a nice baseline for browsers that don’t yet support <code>corner-shape</code>. We have three cards in a row, where the featured plan is distinguished by a warm gradient background, a stronger border, and a “Most Popular” badge.</p>

<p>The enhancement takes it further:</p>

<pre><code class="language-css">@layer demo {
  @supports (corner-shape: squircle) {
    .plan {
      border-radius: 20px;
      corner-shape: squircle;
    }

    .plan--featured {
      border-radius: 24px;
      corner-shape: scoop;
    }

    .plan&#95;&#95;badge {
      inset-inline-start: 50%;
      translate: -50% 0;
      padding-inline: 1.2rem;
      border-radius: 10px;
      corner-shape: bevel;
    }

    .plan&#95;&#95;cta {
      border-radius: 12px;
      corner-shape: squircle;
    }
  }
}
</code></pre>

<p>Regular plans get <code>squircle</code> for that premium feel. The featured plan gets <code>scoop</code>, concave corners that immediately set it apart from its neighbors. The “Most Popular” badge centers itself and takes on <code>corner-shape: bevel</code>, creating a gem-like, faceted shape that feels like a jewel pinned to the card. The CTA buttons get <code>squircle</code> to match the card language.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.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/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png"
			
			sizes="100vw"
			alt="Pricing cards: before"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Before. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/7-pricing-before.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.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/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png"
			
			sizes="100vw"
			alt="Pricing cards: after"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      After. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/8-pricing-after.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>What I like about this demo is how the shape hierarchy mirrors the content hierarchy. The most important element (featured plan) gets the most distinctive shape (<code>scoop</code>). The badge gets the sharpest shape (<code>bevel</code>). Everything else gets a simpler upgrade (<code>squircle</code>). Shape becomes a tool for visual emphasis, not just decoration.</p>

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

<h2 id="demo-5-music-player">Demo 5: Music Player</h2>

<p>The final demo is a warm dark UI for a music player with album art, playback controls, genre tags, and a listening queue. It’s the most visually complex demo, and it shows how <code>corner-shape</code> works across many different element types within a single cohesive design.</p>

<p>This time, I went for a dark warm palette built on <code>oklch(18% 0.015 40)</code>, and standard rounded corners throughout. The album art gets <code>border-radius: 12px</code>, queue items get <code>border-radius: 12px</code>, genre tags get <code>border-radius: 5px</code>. It looks good. It’s a complete, polished player.</p>

<p>And then once again, we add some enhancements:</p>

<pre><code class="language-css">@layer demo {
  @supports (corner-shape: squircle) {
    .now-playing {
      border-radius: 20px;
      corner-shape: squircle;
    }

    .now-playing&#95;&#95;art {
      border-radius: 16px;
      corner-shape: squircle;
    }

    .now-playing&#95;&#95;swatch {
      border-radius: 26%;
      corner-shape: squircle;
    }

    .queue-item {
      border-radius: 14px;
      corner-shape: scoop round;
    }

    .tag {
      border-radius: 8px;
      corner-shape: bevel;
    }
  }
}
</code></pre>

<p>The player card and album art get <code>squircle</code>, the same curves used for app icons and album thumbnails. Album art swatches go from <code>border-radius: 22%</code> to a proper <code>squircle</code> at <code>26%</code>, which is a subtle but meaningful difference in the visual elements you stare at while listening.</p>

<p>Queue items get <code>scoop round</code>, resulting in concave corners on the top-left and bottom-left, and round on the right. It gives each row a distinctive feel without overwhelming the layout. Genre tags get <code>bevel</code> for that sharp feeling.</p>

<p>The Play button also gets <code>corner-shape: squircle</code> on its existing <code>border-radius: 50%</code> to fit the album covers. On the surface, the difference is barely noticeable, but it contributes to the overall feel of the player.</p>

<figure class="break-out">
	<p data-height="480"
	data-theme-id="light"
	data-slug-hash="ogzYQRB"
	data-user="smashingmag"
	data-default-tab="result"
	class="codepen">See the Pen [Corner-shape: Music player [forked]](https://codepen.io/smashingmag/pen/ogzYQRB) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p>
	<figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ogzYQRB">Corner-shape: Music player [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption>
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.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/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png"
			
			sizes="100vw"
			alt="Music player: before"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Before. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/9-music-before.png'>Large preview</a>)
    </figcaption>
  
</figure>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.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/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png"
			
			sizes="100vw"
			alt="Music player: after"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      After. (<a href='https://files.smashing.media/articles/beyond-border-radius-css-corner-shape-property-ui/10-music-after.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="browser-support">Browser Support</h2>

<p>As of writing, <code>corner-shape</code> is available in Chrome 139+ and Chromium-based browsers. Firefox and Safari don’t support it yet. The spec lives in <a href="https://drafts.csswg.org/css-borders/#propdef-corner-shape">CSS Borders and Box Decorations Module Level 4</a>, which is a W3C Working Draft as of this writing.</p>

<p>For practical use, that’s fine. That’s the whole point of how these demos are built. The <code>presentation</code> layer delivers a polished, complete UI to every browser. The <code>demo</code> layer is a bonus for supporting browsers, wrapped in <code>@supports (corner-shape: ...)</code>. I lived through the time when <code>border-radius</code> was only available in Firefox. Somewhere along the line, it seems like we have forgotten that not every website needs to look exactly the same in every browser. What we really want is: no “broken” layouts and no “your browser doesn’t support this” messages, but rather a beautiful experience that just works, and can progressively enhance a bit of extra joy. In other words, we’re working with two tiers of design: good and better.</p>

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

<p>The approach I keep coming back to is: don’t design for <code>corner-shape</code>, and don’t design <em>around</em> the lack of it. Design a solid baseline with <code>border-radius</code> and then enhance it. The presentation layer in every demo looks intentionally good. It’s not a degraded version waiting for a better browser. It’s a <strong>complete design</strong>. The <code>demo</code> layer adds a dimension that <code>border-radius</code> alone can’t express.</p>

<p>What surprises me most about <code>corner-shape</code> is the <em>range</em> it offers &mdash; the amazing powerhouse we have with this single property: <code>squircle</code> for that premium, superellipse feel on cards and avatars; <code>bevel</code> for directional elements and gem-like badges; <code>scoop</code> for editorial warmth and visual hierarchy; <code>notch</code> for mechanical precision on tags; and <code>superellipse()</code> for fine control between <code>round</code> and <code>squircle</code>. And the ability to mix values per corner (<code>round bevel bevel round</code>, <code>scoop round</code>) opens up shapes that would have required SVG masks or <code>clip-path</code> hacks.</p>

<p>We went from five background images to <code>border-radius</code>, to <code>corner-shape</code>. Each step removed a category of workarounds. I’m excited to see what designers do with this one.</p>

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

<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/corner-shape"><code>corner-shape</code></a> (MDN)</li>
<li>“<a href="https://css-tricks.com/what-can-we-actually-do-with-corner-shape/">What Can We Actually Do With <code>corner-shape</code>?</a>”, Daniel Schwarz</li>
<li><a href="https://drafts.csswg.org/css-borders/#propdef-corner-shape">CSS Borders and Box Decorations Module Level 4</a> (W3C specification)</li>
<li><a href="https://codepen.io/bySebastian/pen/VYjPzYo">A fun demo for “eco-labels”</a>, Sebastian on CodePen</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>Vitaly Friedman</author><title>Combobox vs. Multiselect vs. Listbox: How To Choose The Right One</title><link>https://www.smashingmagazine.com/2026/02/combobox-vs-multiselect-vs-listbox/</link><pubDate>Tue, 03 Feb 2026 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2026/02/combobox-vs-multiselect-vs-listbox/</guid><description>Combobox vs. Multi-Select vs. Listbox vs. Dual Listbox? How they are different, what purpose they serve, and how to choose the right one. Brought to you by &lt;a href="https://ai-design-patterns.com">Design Patterns For AI Interfaces&lt;/a>, &lt;strong>friendly video courses on UX&lt;/strong> and design patterns by Vitaly.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2026/02/combobox-vs-multiselect-vs-listbox/" />
              <title>Combobox vs. Multiselect vs. Listbox: How To Choose The Right One</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Combobox vs. Multiselect vs. Listbox: How To Choose The Right One</h1>
                  
                    
                    <address>Vitaly Friedman</address>
                  
                  <time datetime="2026-02-03T10:00:00&#43;00:00" class="op-published">2026-02-03T10:00:00+00:00</time>
                  <time datetime="2026-02-03T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>So what’s the difference between combobox, multiselect, listbox, and dropdown? While all these UI components might appear similar, they serve different purposes. The choice often comes down to the <strong>number of available options</strong> and their visibility.</p>

<p>Let’s see how they differ, <strong>what purpose they serve</strong>, and how to choose the right one &mdash; avoiding misunderstandings and wrong expectations along the way.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="858"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg"
			
			sizes="100vw"
			alt="A comparison of UI elements: Listbox, Combobox, Multiselect, and Dual Listbox, showcasing different selection functionalities."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      And the confusion begins: Listbox, Combobox, Multiselect, Dual Listbox. (<a href='https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/1-combobox-vs-multiselect-vs-listbox.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="not-all-list-patterns-are-the-same">Not All List Patterns Are The Same</h2>

<p>All the UI components highlighted above have exactly one thing in common: they support users’ interactions with lists. However, they do so slightly differently.</p>

<p>Let’s take a look at each, one by one:</p>

<ul>
<li><strong>Dropdown</strong> → list is hidden until it’s triggered.</li>
<li><strong>Combobox</strong> → type to filter + select 1 option.</li>
<li><strong>Multiselect</strong> → type to filter + select many options.</li>
<li><strong>Listbox</strong> → all list options visible by default (+ scroll).</li>
<li><strong>Dual listbox</strong> → move items between 2 listboxes.</li>
</ul>














<figure class="
  
  
  ">
  
    <a href="https://watson.docplanner.design/latest/watson-web/components/combobox/usage-guidelines-L68K6G51">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="825"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg"
			
			sizes="100vw"
			alt="A text input field with a dropdown list"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Watson design system with grouping inside of its <a href='https://watson.docplanner.design/latest/watson-web/components/combobox/usage-guidelines-L68K6G51'>combobox pattern</a>. (<a href='https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/2-watson-design-system.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In other words, <em>Combobox</em> combines a text input field with a dropdown list, so users can <strong>type to filter</strong> and select a single option. With <em>Multiselect</em>, users can select many options (often displayed as pills or chips).</p>

<p><em>Listboxes</em> display <strong>all list options visible</strong> by default, often with scrolling. It’s helpful when users need to see all available choices immediately. <em>Dual listbox</em> (also called <em>transfer list</em>) is a variation of a listbox that allows users to <strong>move items between two listboxes</strong> (left ↔ right), typically for bulk selection.</p>

<h2 id="never-hide-frequently-used-options">Never Hide Frequently Used Options</h2>

<p>As mentioned above, the choice of the right UI component depends on <strong>2 factors</strong>: how many list options are available, and if all these options need to be visible by default. All lists could have tree structures, nesting, and group selection, too.</p>














<figure class="
  
  
  ">
  
    <a href="https://www.mongodb.design/component/combobox/design-docs">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="776"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg"
			
			sizes="100vw"
			alt="A dropdown menu showing product selection. Compass is selected, and Atlas is selected with two sub-options: Vector Search and Atlas CLI."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://www.mongodb.design/component/combobox/live-example'>MongoDB design system</a> with nested filters and chips. (<a href='https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/3-nongodb-design-system.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>There is one principle that I’ve been following for years for any UI component: <strong>never hide frequently used options</strong>. If users rely on a particular selection frequently, there is very little value in hiding it from them.</p>

<p>We could either make it <strong>pre-selected</strong>, or (if there are only 2–3 frequently used options) show them as <a href="https://smart-interface-design-patterns.com/articles/badges-chips-tags-pills/"><strong>chips or buttons</strong></a>, and then show the rest of the list on interaction. In general, it’s a good idea to always display popular options &mdash; even if it might clutter the UI.</p>

<h2 id="how-to-choose-which">How To Choose Which?</h2>

<p>Not every list needs a complex selection method. For lists with <strong>fewer than 5 items</strong>, simple radio buttons or checkboxes usually work best. But if users need to select from a <strong>large list</strong> of options (e.g., 200+ items), combobox + multiselect are helpful because of the faster filtering (e.g., country selection).</p>














<figure class="
  
  
  ">
  
    <a href="https://www.nngroup.com/articles/listbox-dropdown/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="722"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg"
			
			sizes="100vw"
			alt="Matrix of options for multiselect and listboxes."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A <a href='https://www.nngroup.com/articles/listbox-dropdown/'>matrix of options</a>, broken down by single- or multi-selection and static or scrollable view. By Anna Kaley, from NN/g. (<a href='https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/4-matrix-options-multiselect-listboxes.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Listboxes</strong> are helpful when people need to access <strong>many options at once</strong>, especially if they need to choose many options from that list as well. They could be helpful for frequently used filters.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://v5.mantine.dev/core/transfer-list/">
    
    <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/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png"
			
			sizes="100vw"
			alt="Dual list box used to transfer items from one place to another."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Dual listbox in action: it can be very helpful when assigning tasks or permissions. That’s why it’s “Transfer List”. Example from <a href='https://v5.mantine.dev/core/transfer-list/'>Mantine</a>. (<a href='https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/5-dual-list-box.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p><strong>Dual listbox</strong> is often overlooked and ignored. But it can be very helpful for complex tasks, e..g bulk selection, or assigning roles, tasks, responsibilities. It’s the only UI component that allows users to review their full selection list side-by-side with the source list before committing (also called <em>“Transfer list”</em>).</p>

<p>In fact, dual listbox is often faster, more accurate, and more accessible than <a href="https://smart-interface-design-patterns.com/articles/drag-and-drop-ux/">drag-and-drop</a>.</p>

<h2 id="usability-considerations">Usability Considerations</h2>

<p>One important note to keep in mind is that all list types need to support <strong>keyboard navigation</strong> (e.g., ↑/↓ arrow keys) for accessibility. Some people will almost always rely uponthe  keyboard to select options once they start typing.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://watson.docplanner.design/latest/watson-web/components/combobox/usage-guidelines-L68K6G51">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="555"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png"
			
			sizes="100vw"
			alt="Keyboard navigation is often in use with any type of lists."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Keyboard navigation is often in use with any kind of lists. Example: <a href='https://watson.docplanner.design/latest/watson-web/components/combobox/usage-guidelines-L68K6G51'>Watson</a>. (<a href='https://files.smashing.media/articles/combobox-vs-multiselect-vs-listbox/7-keyboard-navigation.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Beyond that:</p>

<ul>
<li>For lists with <strong>7+ options</strong>, consider adding “Select All” and “Clear All” functionalities to streamline user interaction.</li>
<li>For lengthy lists with a combobox, <strong>expose all options</strong> to users on click/tap, as otherwise they might never be seen,</li>
<li>Most important, <strong>don’t display non-interactive elements as buttons</strong> to avoid confusion &mdash; and don&rsquo;t display interactive elements as static labels.</li>
</ul>

<h2 id="wrapping-up-not-everything-is-a-dropdown">Wrapping Up: Not Everything Is A Dropdown</h2>

<p>Names matter. A <strong>vertical list of options</strong> is typically described as a “dropdown” &mdash; but often it’s a bit too generic to be meaningful. <em>“Dropdown”</em> hints that the list is hidden by default. <em>“Multiselect”</em> implies multi-selection (checkbox) within a list. <em>“Combobox”</em> implies text input. And “Listbox” is simply a list of selectable items, visible at all times.</p>

<p>The goal isn’t to be consistent with the definitions above for the sake of it. But rather to <strong>align intentions</strong> &mdash; speak the same language when deciding on, designing, building, and then using these UI components.</p>

<p>It <strong>should work for everyone</strong> &mdash; designers, engineers, and end users &mdash; as long as static labels don’t look like interactive buttons, and radio buttons don’t act like checkboxes.</p>

<h2 id="meet-design-patterns-for-ai-interfaces">Meet “Design Patterns For AI Interfaces”</h2>

<p>Meet <a href="https://ai-design-patterns.com/"><strong>Design Patterns For AI Interfaces</strong></a>, Vitaly’s new <strong>video course</strong> with practical examples from real-life products &mdash; with a <a href="https://smashingconf.com/online-workshops/workshops/ai-interfaces-vitaly-friedman/">live UX training</a> happening soon. <a href="https://www.youtube.com/watch?v=jhZ3el3n-u0">Jump to a free preview</a>.</p>

<p><figure class="article__image" style="margin-bottom: 0"><a href="https://ai-design-patterns.com/"><img style="border-radius:11px" loading="lazy" decoding="async" fetchpriority="low" width="800" height="414" srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png 2000w" src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/product-designer-career-paths/design-patterns-ai-interfaces.png" sizes="100vw" alt="Design Patterns For AI Interfaces promo picture"></a><figcaption class="op-vertical-bottom">Meet <a href="https://ai-design-patterns.com/">Design Patterns For AI Interfaces</a>, Vitaly’s video course on interface design &amp; UX.</figcaption></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>450<sup class="sup">.00</sup></span></span> <span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>799<sup class="sup">.00</sup></span></span></span></span>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3476562?price_id=4401578" class="btn btn--full btn--medium btn--text-shadow">
Get Video + UX Training<div></div></a><p class="book-cta__desc">30 video lessons (10h) + <a href="https://smashingconf.com/online-workshops/workshops/ai-interfaces-vitaly-friedman/">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>275<sup class="sup">.00</sup></span></span><span class="book-cta__price--old"><span class="currency-sign">$</span>&nbsp;<span>395<sup class="sup">.00</sup></span></span></span></div>
<a href="https://smart-interface-design-patterns.thinkific.com/enroll/3476562?price_id=4397456" 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">30 video lessons (10h). 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></p>

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

<ul>
<li><a href="https://smart-interface-design-patterns.com/articles/autocomplete-ux/">Autocomplete: UX Guidelines</a>, by Vitaly Friedman</li>
<li><a href="https://playbook.ebay.com/design-system/components/combobox">Combobox</a>, by eBay 👍</li>
<li><a href="https://eui.elastic.co/docs/components/forms/selection/combo-box/">Combobox</a>, by Elastic</li>
<li><a href="https://designsystem.elisa.fi/9b207b2c3/p/082dd3-combobox">Combobox</a>, by Elisa</li>
<li><a href="https://www.mongodb.design/component/combobox/live-example">Combobox</a>, by MongoDB 👍</li>
<li><a href="https://design.visa.com/components/combobox/">Combobox</a>, by Visa 👍</li>
<li><a href="https://watson.docplanner.design/latest/watson-web/components/combobox/usage-guidelines-L68K6G51">Combobox</a>, by Watson (Docplanner)</li>
<li><a href="https://doc.wikimedia.org/codex/latest/components/demos/combobox.html">Combobox</a>, by Wikimedia</li>
<li><a href="https://garden.zendesk.com/components/combobox">Combobox</a>, by Zendesk</li>
<li><a href="https://www.mongodb.design/component/combobox/design-docs">Multiselect (MongoDB Combobox Design Docs)</a>, by MongoDB 👍</li>
<li><a href="https://doc.wikimedia.org/codex/latest/components/demos/multiselect-lookup.html">Multiselect Lookup</a>, by Wikimedia</li>
<li><a href="https://vaadin.com/docs/latest/components/multi-select-combo-box">Multi-select Combo Box</a>, by Vaadin</li>
<li><a href="https://design.visa.com/components/multiselect/">Multiselect</a>, by Visa</li>
<li><a href="https://ant.design/components/transfer">Transfer (Listbox example)</a>, by Ant Design</li>
<li><a href="https://hopper.workleap.design/components/Listbox">Listbox</a>, by Hopper</li>
<li><a href="https://vaadin.com/docs/latest/components/list-box">List Box</a>, by Vaadin</li>
<li><a href="https://design.visa.com/components/listbox/">Listbox</a>, by Visa</li>
<li><a href="https://www.patternfly.org/components/dual-list-selector">Dual List Selector</a>, by Red Hat (PatternFly)</li>
<li><a href="https://www.lightningdesignsystem.com/2e1ef8501/p/763763-dual-listbox">Dual Listbox</a>, by Salesforce (Lightning Design System)</li>
<li><a href="https://v5.mantine.dev/core/transfer-list/">Transfer List</a>, by Mantine</li>
<li><a href="https://dashlite.net/demo1/components/misc/dual-listbox.html">Dual Listbox</a>, by Dashlite</li>
<li><a href="https://smart-interface-design-patterns.com/articles/badges-chips-tags-pills/">Badges vs. Pills vs. Chips vs. Tags</a>, by Vitaly Friedman</li>
<li><a href="https://www.nngroup.com/articles/listbox-dropdown/">Listboxes vs. Dropdown Lists</a>, by Anna Kaley (NN/g)</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>Daniel Schwarz</author><title>How To Leverage Component Variants In Penpot</title><link>https://www.smashingmagazine.com/2025/11/how-leverage-component-variants-penpot/</link><pubDate>Tue, 04 Nov 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/11/how-leverage-component-variants-penpot/</guid><description>With component variants, design systems become more flexible, letting you reuse the same component while adapting its look or state with ease. In this article, Daniel Schwarz demonstrates how design tokens can be leveraged to manage components and their variations using &lt;a href="https://penpot.app?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=Variants">Penpot&lt;/a>, the open-source tool built for scalable, consistent design.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/11/how-leverage-component-variants-penpot/" />
              <title>How To Leverage Component Variants In Penpot</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>How To Leverage Component Variants In Penpot</h1>
                  
                    
                    <address>Daniel Schwarz</address>
                  
                  <time datetime="2025-11-04T10:00:00&#43;00:00" class="op-published">2025-11-04T10:00:00+00:00</time>
                  <time datetime="2025-11-04T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                <p>This article is sponsored by <b>Penpot</b></p>
                

<p>Since Brad Frost popularized the use of design systems in digital design <a href="https://bradfrost.com/blog/post/atomic-web-design/">way back in 2013</a>, they’ve become an invaluable resource for organizations &mdash; and even individuals &mdash; that want to craft reusable design patterns that look and feel consistent.</p>

<p>But Brad didn’t just popularize design systems; he also gave us a <strong>framework</strong> for structuring them, and while we don’t have to follow that framework exactly (most people adapt it to their needs), a particularly important part of most design systems is the <strong>variants</strong>, which are <em>variations</em> of components. Component variants allow for the design of components that are the same as other components, but different, so that they’re understood by users immediately, yet provide clarity for a unique context.</p>

<p>This makes component variants just as important as the components themselves. They ensure that we aren’t creating too many components that have to be individually managed, even if they’re only mildly different from other components, and since component variants are grouped together, they also ensure organization and visual consistency.</p>

<p>And now we can use them in <a href="https://penpot.app?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=Variants">Penpot</a>, the web-based, open-source design tool where design is expressed as code. In this article, you’ll learn about variants, their place in <a href="https://penpot.app/design/design-systems?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=Variants">design systems</a>, and how to use them effectively in Penpot.</p>

<h2 id="step-1-get-your-design-tokens-in-order">Step 1: Get Your Design Tokens In Order</h2>

<p>For the most part, what separates one variant from another is the <a href="https://penpot.app/collaboration/design-tokens?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=DesignTokens">design tokens</a> that it uses. But what is a design token exactly?</p>

<p>Imagine a brand color, let’s say a color value equal to <code>hsl(270 100 42)</code> in CSS. We save it as a “design token” called <code>color.brand.default</code> so that we can reuse it more easily without having to remember the more cumbersome <code>hsl(270 100 42)</code>.</p>

<p>From there, we might also create a second design token called <code>background.button.primary.default</code> and set it to <code>color.brand.default</code>, thereby making them equal to the same color, but with different names to establish semantic separation between the two. This referencing the value of one token from another token is often called an “alias”.</p>

<p>This setup gives us the flexibility to change the value of the color document-wide, change the color used in the component (maybe by switching to a different token alias), or create a variant of the component that uses a different color. Ultimately, the goal is to be able to make changes in many places at once rather than one-by-one, mostly by editing the design token values rather than the design itself, at specific scopes rather than limiting ourselves to all-or-nothing changes. This also enables us to scale our design system without constraints.</p>

<p>With that in mind, here’s a rough idea of just a few color-related design tokens for a primary button with hover and disabled states:</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>Token name</th>
            <th>Token value</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>color.brand.default</code></td>
            <td><code>hsl(270 100 42)</code></td>
        </tr>
        <tr>
            <td><code>color.brand.lighter</code></td>
            <td><code>hsl(270 100 52)</code></td>
        </tr>
        <tr>
            <td><code>color.brand.lightest</code></td>
            <td><code>hsl(270 100 95)</code></td>
        </tr>
    <tr>
            <td><code>color.brand.muted</code></td>
            <td><code>hsl(270 5 50)</code></td>
        </tr>
    <tr>
            <td><code>background.button.primary.default</code></td>
            <td><code>{color.brand.default}</code></td>
        </tr>
    <tr>
            <td><code>background.button.primary.hover</code></td>
            <td><code>{color.brand.lighter}</code></td>
        </tr>
    <tr>
            <td><code>background.button.primary.disabled</code></td>
            <td><code>{color.brand.muted}</code></td>
        </tr>
    <tr>
            <td><code>text.button.primary.default</code></td>
            <td><code>{color.brand.lightest}</code></td>
        </tr>
    <tr>
            <td><code>text.button.primary.hover</code></td>
            <td><code>{color.brand.lightest}</code></td>
        </tr>
     <tr>
            <td><code>text.button.primary.disabled</code></td>
            <td><code>{color.brand.lightest}</code></td>
        </tr>
    </tbody>
</table>

<p>To create a color token in Penpot, switch to the “Tokens” tab in the left panel, click on the plus (<code>+</code>) icon next to “Color”, then specify the name, value, and optional description.</p>

<p>For example:</p>

<ul>
<li><strong>Name</strong>: <code>color.brand.default</code>,</li>
<li><strong>Value</strong>: <code>hsl(270 100 42)</code> (there’s a color picker if you need it).</li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png"
			
			sizes="100vw"
			alt="Creating a color token in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Creating a color token in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/1-color-token-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It’s pretty much the same process for other types of design tokens.</p>

<p>Don’t worry, I’m not going to walk you through every design token, but I will show you how to create a design token <em>alias</em>. Simply repeat the steps above, but for the value, notice how I’ve just referenced another color token (make sure to include the curly braces):</p>

<ul>
<li><strong>Name</strong>: <code>background.button.primary.default</code>,</li>
<li><strong>Value</strong>: <code>{color.brand.default}</code></li>
</ul>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png"
			
			sizes="100vw"
			alt="Creating a design token alias in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Creating a design token alias in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/2-design-token-alias-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Now, if the value of the color changes, so will the background of the buttons. But also, if we want to decouple the color from the buttons, all we need to do is reference a different color token or value. Mikołaj Dobrucki goes into a lot more detail in another <a href="https://www.smashingmagazine.com/2025/05/integrating-design-code-native-design-tokens-penpot/">Smashing article</a>, but it’s worth noting here that Penpot design tokens are platform-agnostic. They follow the standardized <a href="https://www.w3.org/community/design-tokens/">W3C DTCG format</a>, which means that they’re compatible with other tools and easily export to all platforms, including web, iOS, and Android.</p>

<p>In the next couple of steps, we’ll create a button component and its variants while plugging different design tokens into different variants. You’ll see why doing this is so useful and how using design tokens in variants benefits design systems overall.</p>

<h2 id="step-2-create-the-component">Step 2: Create The Component</h2>

<p>You’ll need to create what’s called a “main” component, which is the one that you’ll update as needed going forward. Other components &mdash; the ones that you’ll actually insert into your designs &mdash; will be copies (or “instances”) of the main component, which is sort of the point, right? Update once, and the changes reflect everywhere.</p>

<p>Here’s one I made earlier, minus the colors:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png"
			
			sizes="100vw"
			alt="Main component"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/3-main-component-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To apply a design token, make sure that you’re on the “Tokens” tab and have the relevant layer selected, then select the design token that you want to apply to it:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png"
			
			sizes="100vw"
			alt="Applying a design token in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Applying a design token in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/4-design-token-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>It doesn’t matter which variant you create first, but you’ll probably want to go with the default one as a starting point, as I’ve done. Either way, to turn this button into a main component, select the button object via the canvas (or “Layers” tab), right-click on it, then choose the “Create component” option from the context menu (or just press <kbd>Ctrl</kbd> / <kbd>⌘</kbd> + <kbd>K</kbd> after selecting it).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png"
			
			sizes="100vw"
			alt="Creating a component in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Creating a component in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/5-create-component-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Remember to name the component as well. You can do that by double-clicking on the name (also via the canvas or “Layers” tab).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png"
			
			sizes="100vw"
			alt="Renaming a component in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Renaming a component in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/6-renaming-component-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="step-3-create-the-component-variants">Step 3: Create The Component Variants</h2>

<p>To create a variant, select the main component and either hit the <kbd>Ctrl</kbd> / <kbd>⌘</kbd> + <kbd>K</kbd> keyboard shortcut, or click on the icon that reveals the “Create variant” tooltip (located in the “Design” tab in the right panel).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png"
			
			sizes="100vw"
			alt="Creating a component variant in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Creating a component variant in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/7-creating-component-variant-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Next, while the variant is still selected, make the necessary design changes via the “Design” tab. Or, if you want to swap design tokens out for other design tokens, you can do that in the same way that you applied them to begin with, via the “Tokens” tab. Rinse and repeat until you have all of your variants on the canvas designed:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png"
			
			sizes="100vw"
			alt="Styling component variants in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Styling component variants in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/8-styling-component-variants-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>After that, as you might’ve guessed, you’ll want to name your variants. But avoid doing this via the “Layers” panel. Instead, select a variant and replace “Property 1” with a label that describes the differentiating property of each variant. Since my button variants in this example represent different states of the same button, I’ve named this “State”. This applies to all of the variants, so you only need to do this once.</p>

<p>Next to the property name, you’ll see “Value 1” or something similar. Edit that for each variant, for example, the name of the state. In my case, I’ve named them “Default”, “Hover”, and “Disabled”.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png"
			
			sizes="100vw"
			alt="Naming component variant properties in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Naming component variant properties in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/9-naming-component-variant-properties-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>And yes, you can add more properties to a component. To do this, click on the nearby plus (<code>+</code>) icon. I’ll talk more about component variants at scale in a minute, though.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png"
			
			sizes="100vw"
			alt="Managing component variant properties in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Managing component variant properties in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/10-managing-component-variant-properties-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>To see the component in action, switch to the “Assets” tab (located in the left panel) and drag the component onto the canvas to initialize one instance of it. Again, remember to choose the correct property value from the “Design” tab:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png"
			
			sizes="100vw"
			alt="Using component variants in Penpot"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Using component variants in Penpot. (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/11-using-component-variants-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>If you already have a <a href="https://penpot.app/blog/penpot-for-design-systems-101/?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=DesignTokens">Penpot design system</a>, combining multiple components into one component with variants is not only easy and error-proof, but you might be good to go already if you’re using a robust property naming system that uses forward slashes (<code>/</code>). Penpot has put together <a href="https://community.penpot.app/t/how-to-prepare-your-files-for-the-upcoming-variants-release/9804?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=DesignTokens">a very straightforward guide</a>, but the diagram below sums it up pretty well:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="463"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png"
			
			sizes="100vw"
			alt="Diagram on how to prepare your files for the upcoming Variants release"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/how-leverage-component-variants-penpot/12-diagram-sorting.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="how-component-variants-work-at-scale">How Component Variants Work At Scale</h2>

<p>Design tokens, components, and component variants &mdash; the triple-threat of design systems &mdash; work together, not just to create powerful yet flexible design systems, but sustainable design systems that scale. This is easier to accomplish when thinking ahead, starting with design tokens that separate the “what” from the “what for” using token aliases, despite how verbose that might seem at first.</p>

<p>For example, I used <code>color.brand.lightest</code> for the text color of every variant, but instead of plugging that color token in directly, I created aliases such as <code>text.button.primary.default</code>. This means that I can change the text color of any variant later without having to dive into the actual variant on the canvas, or force a change to <code>color.brand.lightest</code> that might impact a bunch of other components.</p>

<p>Because remember, while the component and its variants give us reusability of the button, <strong>the color tokens give us reusability of the colors</strong>, which might be used in dozens, if not hundreds, of other components. A design system is like a living, breathing ecosystem, where some parts of it are connected, some parts of it aren’t connected, and some parts of it are or aren’t connected but might have to be later, and we need to be ready for that.</p>

<p>The good news is that Penpot makes all of this pretty easy to manage as long as you do a little planning beforehand.</p>

<p>Consider the following:</p>

<ul>
<li>The design tokens that you’ll reuse (e.g., colors, font sizes, and so on),</li>
<li>Where design token aliases will be reused (e.g., buttons, headings, and so on),</li>
<li>Organizing the design tokens into <a href="https://help.penpot.app/user-guide/design-tokens/?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=Variants#design-tokens-sets">sets</a>,</li>
<li>Organizing the sets into <a href="https://help.penpot.app/user-guide/design-tokens/?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=Variants#design-tokens-themes">themes</a>,</li>
<li>Organizing the themes into <a href="https://help.penpot.app/user-guide/design-tokens/?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=Variants#design-tokens-themes-group">groups</a>,</li>
<li>The different components that you’ll need, and</li>
<li>The different variants and variant properties that you’ll need for each component.</li>
</ul>

<p>Even the buttons that I designed here today can be scaled far beyond what I’ve already mocked up. Think of all the possible variants that might come up, such as a secondary button color, a tertiary color, a confirmation color, a warning color, a cancelled color, different colors for light and dark mode, not to mention more properties for more states, such as active and focus states. What if we want a whole matrix of variants, like where buttons in a disabled state can be hovered and where all buttons can be focused upon? Or where some buttons have icons instead of text labels, or both?</p>

<p>Designs can get very complicated, but once you’ve organized them into design tokens, components, and component variants in Penpot, they’ll actually feel quite simple, especially once you’re able to see them on the canvas, and even more so once you’ve made a significant change in just a few seconds without breaking anything.</p>

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

<p>This is how we make component variants work at scale. We get the benefits of <strong>reusability</strong> while keeping the <strong>flexibility</strong> to fork any aspect of our design system, big or small, without breaking out of it. And design tools like Penpot make it possible to not only establish a design system, but also express its design tokens and styles as code.</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>Mikołaj Dobrucki</author><title>Integrating Design And Code With Native Design Tokens In Penpot</title><link>https://www.smashingmagazine.com/2025/05/integrating-design-code-native-design-tokens-penpot/</link><pubDate>Thu, 08 May 2025 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/05/integrating-design-code-native-design-tokens-penpot/</guid><description>The Penpot team is not slowing down on its mission to build a free design tool that not only offers powerful design features but is also well-integrated with code and modern development practices. In its latest release, Penpot, as the first design tool ever, introduces support for &lt;a href="https://www.google.com/url?q=https://penpot.app/collaboration/design-tokens?utm_source%3DSmashingMag%26utm_medium%3DArticle%26utm_campaign%3DDesignTokens&amp;amp;sa=D&amp;amp;source=docs&amp;amp;ust=1746545302543254&amp;amp;usg=AOvVaw2Smr3CZPnW7nRDz_6YMwJJ">native design tokens&lt;/a>. Let’s take a closer look at this concept and how you can employ it in your process.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/05/integrating-design-code-native-design-tokens-penpot/" />
              <title>Integrating Design And Code With Native Design Tokens In Penpot</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Integrating Design And Code With Native Design Tokens In Penpot</h1>
                  
                    
                    <address>Mikołaj Dobrucki</address>
                  
                  <time datetime="2025-05-08T10:00:00&#43;00:00" class="op-published">2025-05-08T10:00:00+00:00</time>
                  <time datetime="2025-05-08T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                <p>This article is sponsored by <b>Penpot</b></p>
                

<p>It’s already the fifth time I’m writing to you about Penpot &mdash; and what a journey it continues to be! During this time, Penpot’s presence in the design tools scene has grown strong. In a market that recently felt more turbulent than ever, I’ve always appreciated Penpot for their clear mission and values. They’ve built a design tool that not only delivers great features but is also <strong>open-source</strong> and developed in active dialogue with the community. Rather than relying on closed formats and gated solutions, Penpot embraces <strong>open web standards</strong> and commonly used technologies &mdash; ensuring it works seamlessly across platforms and integrates naturally with code.</p>

<p>Their latest release is another great example of that approach. It’s also one of the most impactful. Let me introduce you to <a href="https://www.google.com/url?q=https://penpot.app/collaboration/design-tokens?utm_source%3DSmashingMag%26utm_medium%3DArticle%26utm_campaign%3DDesignTokens&amp;sa=D&amp;source=docs&amp;ust=1746545302545630&amp;usg=AOvVaw3RHC5kveiwwmzlV6aYzNof">design tokens in Penpot</a>.</p>

<p>Design tokens are an essential building block of modern user interface design and engineering. But so far, designers and engineers have been stuck with third-party plugins and cumbersome APIs to collaborate effectively on design tokens and keep them in sync. It’s high time we had tools and processes that handle this better, and Penpot just made it happen.</p>

<h2 id="about-design-tokens">About Design Tokens</h2>

<p>Design tokens can be understood as a <strong>framework to document and organize your design decisions</strong>. They act as a single source of truth for both designers and engineers and include all the design variables, such as colors, typography, spacing, fills, borders, and shadows.</p>

<p>The concept of design tokens has grown in popularity alongside the rise of design systems and the increasing demand for broader standards and guidelines in user interface design. Design tokens emerged as a solution for managing increasingly complex systems while keeping them structured, scalable, and extensible.</p>

<p>The goal of using design tokens is not only to make design decisions more intentional and maintainable but also to make it easier to keep them in sync with code. In the case of larger systems, it is often a one-to-many relationship. Design tokens allow you to keep the values agnostic of their application and scale them across various products and environments.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aDesign%20tokens%20create%20a%20semantic%20layer%20between%20the%20values,%20the%20tools%20used%20to%20define%20them,%20and%20the%20software%20that%20implements%20them.%0a&url=https://smashingmagazine.com%2f2025%2f05%2fintegrating-design-code-native-design-tokens-penpot%2f">
      
Design tokens create a semantic layer between the values, the tools used to define them, and the software that implements them.

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














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="600"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png"
			
			sizes="100vw"
			alt="Schema of the design system"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/1-schema-design-system.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>On top of maintainability benefits, a common reason to use design tokens is <strong>theming</strong>. Keeping your design decisions decoupled means that you can easily swap the values across multiple sets. This allows you to change the appearance of the entire interface with applications ranging from simple light and dark mode implementations to more advanced use cases, such as handling multiple brands or creating fully customizable and adjustable UIs.</p>

<h2 id="implementation-challenges">Implementation Challenges</h2>

<p>Until recently, there was no standardized format for maintaining design tokens &mdash; it remained a largely theoretical concept, implemented differently across teams and tools. Every design tool or frontend framework has its own approach. Syncing code with design tools was also a major pain point, often requiring third-party plugins and unreliable synchronization solutions.</p>

<p>However, in recent years, W3C, the international organization responsible for developing open standards and protocols for the web, brought to life a dedicated Design Tokens Community Group with the goal of creating an <strong>open standard for products and design tools to handle design tokens</strong>. Once this standard gets more widely adopted, it will give us hope for a more predictable and standardized approach to design tokens across the industry.</p>

<p>To make that happen, work has to be done on two ends, both design and development. Penpot is the very first design tool to implement design tokens in adherence to the standard that the W3C is working on. It also solves the problem of third-party dependencies by offering a native API with all the values served in the official, standardized format.</p>

<h2 id="design-tokens-in-practice">Design Tokens In Practice</h2>

<p>To better understand design tokens and how to use them in practice, let’s take a look at an example together. Let’s consider the following user interface of a login screen:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="639"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png"
			
			sizes="100vw"
			alt="Acme login screen"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/2-login-screen-acme.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Imagine we want this design to work in light and dark mode, but also to be themable with several accent colors. It could be that we’re using the same authentication system for websites of several associated brands or several products. We could also want to allow the user to customize the interface to their needs.</p>

<p>If we want to build a design that works for three accent colors, each with light and dark themes, it gives us six variants in total:</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="700"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png"
			
			sizes="100vw"
			alt="Six variants of a login screen design with three accent colors and light and dark mode options"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Six variants of a login screen design with three accent colors and light and dark mode options. (<a href='https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/3-login-screen-design-grid.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Designing all of them by hand would not only be tedious but also difficult to maintain. Every change you make would have to be repeated in six places. In the case of six variants, that’s not ideal, but it’s still doable. But what if you also want to support multiple layout options or more brands? It could easily scale into hundreds of combinations, at which point designing them manually would easily get out of hand.</p>

<p>This is where design tokens come to the rescue. They allow you to effectively <strong>maintain all the variants</strong> and <strong>test all the possible combinations</strong>, even hundreds of them, while still building a single design without repetitive work.</p>

<p>You can start by creating a design in one of the variants before starting to think about the tokens. Having a design already in place might make it easier to plan your tokens’ hierarchy and structure accordingly.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="601"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png"
			
			sizes="100vw"
			alt="Different layers of the design"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/4-layers-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In this case, I created three components: 2 types of buttons and input, and combined them with text layers into several Flex layouts to build out this screen. If you’d like to first learn more about building components and layouts in Penpot, I would recommend you revisit some of my previous articles:</p>

<ul>
<li><a href="https://www.smashingmagazine.com/2024/07/build-design-systems-penpot-components/">Build Design Systems With Penpot Components</a></li>
<li><a href="https://www.smashingmagazine.com/2024/04/penpot-css-grid-layout-designing-superpowers/">Penpot’s CSS Grid Layout: Designing With Superpowers</a></li>
<li><a href="https://www.smashingmagazine.com/2023/06/penpot-flex-layout-building-css-layouts-design-tool/">Penpot’s Flex Layout: Building CSS Layouts In A Design Tool</a></li>
</ul>

<p>Now that we have the design ready, we can start creating tokens. You can create your first token by heading to the tokens tab of the left sidebar and clicking the plus button in one of the token categories. Let’s start by creating a color.</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/1081870431"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Creating your first design token in Penpot</figcaption>
	
</figure>

<p>To use design tokens effectively, it’s critical to <strong>plan their naming and structure well</strong>. You might have noticed that when I created a token, Penpot automatically created for me a new set, called Global. All design tokens have to be organized within sets.</p>

<p>I called my first set “primitives,” so I can store literal values such as “blue,” “purple,” or “grey.” To support multiple shades of color, I used numbers, so the final token names I used are, for example, “slate.1” or “slate.10”.</p>

<p>At this point, we can start thinking about handling multiple colors for various themes. To make it easy to switch between tokens, all you have to do is create multiple sets with tokens of the same names. To do that, I split the primitives into two sets, “light” and “dark.” You can nest your token sets by adding slashes into their names.</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/1081871225"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Creating design token sets in Penpot</figcaption>
	
</figure>

<p>In the video above,  you can see that I have two sets, light and dark, each with tokens of the same names but different values. At this point, you could already reference your primitive tokens to switch between light and dark values. However, in the future, you might use the same shade of grey for multiple purposes, like border, background, or text. It would be a more maintainable approach to keep these definitions independent.</p>

<p>To achieve that, we need to introduce a second abstraction layer. In this case, I created a new tokens set called “globals” that references the primitives set. All values in “globals” reference other already existing tokens, such as “primitives.”</p>

<p>For globals, I used semantic naming such as “text.muted” or “background.primary” to stress that the token names are agnostic from their literal values. In other words, the “text.muted” name works well for both light and dark modes, the same as “background.primary” works as a token name no matter what brand color is currently in use. For comparison, “text.dark” or “background.blue” would not make sense if we wanted to make them dynamic and be able to switch between different modes and brand colors.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="600"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png"
			
			sizes="100vw"
			alt="Tokens structure"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/5-tokens-structure.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>In Penpot, you can reference other tokens in token values by wrapping them in curly brackets. So, if you select “slate.1” as your text color, it will reference the “slate.1” value from any other set that is currently active. With the light set active, the text will be black. And with the dark set active, the text will be white.</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/1081872931"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Creating alias tokens in Penpot</figcaption>
	
</figure>

<p>You can apply your global tokens to any layer you want. To do that, select a layer and then right-click a token of your choice. In the context menu, you can select among the values that are compatible with a token. In the case of a color, it will be either fill or stroke.</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/1081873524"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Applying design tokens to layers in Penpot</figcaption>
	
</figure>

<p>Now, if you switch on and off the sets, you can see the design responding to the change. With the light set active, the text appears black, and with the dark set active, the text appears white.</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/1081874135"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Changing token sets in Penpot</figcaption>
	
</figure>

<p>As you probably noticed, more than one set can be active at the same time, even if they contain values of the same names, like light and dark sets. In such a case, a set lowest on the list will override the already defined values. You can think of it as defining variables in any programming language or properties in CSS. The last value of equal specificity is the one that counts.</p>

<p>However, you don’t need to switch the sets on and off manually to test your design’s appearance. To make that easier, Penpot also offers another concept called <strong>Themes</strong>. Themes are the best way to manage your sets and combine them into functional design choices.</p>

<p>In the case of light and dark mode, I created two themes: “light” and “dark,” under a group called “Mode.” This makes it much clearer how the sets should be used and makes it easier to switch between the predefined options.</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/1081874673"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Creating themes in Penpot</figcaption>
	
</figure>

<p>For each theme, I selected two sets. One that defines the values (“light” or “dark”) and one that is actually used to style the designs (“globals”). Now, you can use the Themes dropdown to quickly switch themes.</p>

<p>At this point, we have two layers of abstraction: primitives (such as basic color shades) and a semantic layer (background, text, and so on). Sometimes, you might need more than that. With this setup, you can easily switch between light and dark mode, but what if you also want to switch between the several brand colors I showed earlier, <em>while</em> still being able to change the mode? For that, we need another theme (let’s call it “Brand”) and another couple of sets under a parent set that would also be called “Brand.” For the latter, I made three options: “Slate,” “Indigo,” and “Purple.” In real-life scenarios, these could be names of brands, products, and so on.</p>

<p>To bring it all together, the brand sets need to reference primitives, while the “globals” set needs to reference “brand” sets. This way, we are creating three different brands, each with its own separate values for light and dark mode.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="600"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png"
			
			sizes="100vw"
			alt="Tokens structure with primitives and brand sets"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/integrating-design-code-native-design-tokens-penpot/6-tokens-structure.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>This allows us to switch between brands and modes and test all the possible combinations.</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/1081876394"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Switching between themes in Penpot</figcaption>
	
</figure>

<h2 id="what-s-next">What’s Next?</h2>

<p>I hope you enjoyed following this example. If you’d like to check out the file presented above before creating your own, you can <a href="https://github.com/mikolajdobrucki/penpot-files/raw/refs/heads/main/Design%20Tokens%20%E2%80%93%20Smashing%20Magazine%20Article.penpot">duplicate it here</a>.</p>

<p>Colors are only one of many types of tokens available in Penpot. You can also use design tokens to maintain values such as spacing, sizing, layout, and so on. The Penpot team is working on gradually expanding the choice of tokens you can use. All are in accordance with the upcoming design tokens standard.</p>

<p>The benefits of the <strong>native approach to design tokens</strong> implemented by Penpot go beyond ease of use and standardization. It also <strong>makes the tokens more powerful</strong>. For example, they already support math operations using the <code>calc()</code> function you might recognize from CSS. It means you can use math to add, multiply, subtract, etc., token values.</p>

<p>Once you have the design token in Penpot ready, the next step is to bring it over to your code. Already today, you can export the tokens in JSON format, and soon, an API will be available that connects and imports the tokens directly into your codebase. You can follow Penpot on <a href="https://www.linkedin.com/company/penpotdesign/posts/?feedView=all">LinkedIn</a>, <a href="https://bsky.app/profile/penpot.app">BlueSky</a>, and <a href="https://penpot.app/">other social media</a> to be the first to hear about the next updates. The team behind Penpot is also planning to make its design tokens implementation even more powerful in the near future with support for <strong>gradients</strong>, <strong>composite tokens</strong> (tokens that store multiple values), and more.</p>

<p>To learn more about design tokens and how to use them, check out the following links:</p>

<ul>
<li><a href="https://penpot.app/collaboration/design-tokens?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=DesignTokens">Design Token Overview</a>, Penpot website</li>
<li><a href="https://penpot.app/blog/what-are-design-tokens-a-complete-guide/">What are design tokens? A complete guide</a>, Penpot Blog</li>
<li><a href="https://help.penpot.app/user-guide/design-tokens/">Design Tokens</a>, Penpot Docs</li>
<li><a href="https://tr.designtokens.org/format/">Design tokens format module</a>, W3C Community Group Draft Report</li>
</ul>

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

<p>By adding support for native design tokens, Penpot is making real progress on connecting design and code in meaningful ways. Having all your design variables well documented and organized is one thing. Doing that in a scalable and maintainable way that is based on open standards and is easy to connect with code &mdahs; that’s yet another level.</p>

<p>The practical benefits are huge: better maintainability, less friction, and easier communication across the whole team. If you’re looking to bring more structure to your design system while keeping designers and engineers in sync, Penpot’s design tokens implementation is definitely worth exploring.</p>

<p><strong>Tried it already? Share your thoughts!</strong> The Penpot team is active on social media, or just share your feedback in the comments section below.</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>Paul Boag</author><title>The Digital Playbook: A Crucial Counterpart To Your Design System</title><link>https://www.smashingmagazine.com/2025/01/digital-playbook-crucial-counterpart-design-system/</link><pubDate>Thu, 30 Jan 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/01/digital-playbook-crucial-counterpart-design-system/</guid><description>Design systems play a crucial role in today’s digital landscape, providing a blueprint for consistent and user-friendly interfaces. But there’s another tool that deserves equal attention: the digital playbook.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/01/digital-playbook-crucial-counterpart-design-system/" />
              <title>The Digital Playbook: A Crucial Counterpart To Your Design System</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The Digital Playbook: A Crucial Counterpart To Your Design System</h1>
                  
                    
                    <address>Paul Boag</address>
                  
                  <time datetime="2025-01-30T08:00:00&#43;00:00" class="op-published">2025-01-30T08:00:00+00:00</time>
                  <time datetime="2025-01-30T08:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p><a href="https://www.smashingmagazine.com/2024/12/the-design-leader-dilemma/">I recently wrote for Smashing Magazine</a> about how UX leaders face increasing pressure to deliver more with limited resources. Let me show you how a digital playbook can help meet this challenge by <strong>enhancing our work’s visibility</strong> while <strong>boosting efficiency</strong>.</p>

<p>While a design system ensures visual coherence, a digital playbook lays out the <strong>strategic and operational framework</strong> for how digital projects should be executed and managed. Here’s why a digital playbook deserves a place in your organization’s toolbox and what it should include to drive meaningful impact.</p>

<h2 id="what-is-a-digital-playbook">What Is A Digital Playbook?</h2>

<p>A digital playbook is essentially your organization’s handbook for navigating the complexities of digital work. As a <a href="https://boagworld.com/boagworks/in-house/">user experience consultant</a>, I often help organizations create tools like this to streamline their processes and improve outcomes. It’s a collection of strategies, principles, and processes that provide clarity on how to handle everything from website creation to content management and beyond. Think of it as <strong>a how-to guide for all things digital</strong>.</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="573"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png"
			
			sizes="100vw"
			alt="The UK Government Digital Service Manual"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The UK Government Digital Service Manual is an excellent example of a playbook. (<a href='https://files.smashing.media/articles/digital-playbook-crucial-counterpart-design-system/1-gov-uk-service-manual.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Unlike rigid rulebooks that feel constraining, you’ll find that a playbook evolves with your organization’s unique culture and challenges. You can use it to help stakeholders learn, standardize your work, and help everybody be more effective. Let me show you how a playbook can transform the way your team works.</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-you-need-a-digital-playbook">Why You Need A Digital Playbook</h2>

<p>Have you ever faced challenges like these?</p>

<ul>
<li>Stakeholders with conflicting expectations of what the digital team should deliver.</li>
<li>Endless debates over project priorities and workflows that stall progress.</li>
<li>A patchwork of tools and inconsistent policies that create confusion.</li>
<li>Uncertainty about best practices, leading to inefficiencies and missed opportunities.</li>
</ul>

<p>Let me show you how a playbook can help you and your team in four key ways:</p>

<ul>
<li>It helps you educate your stakeholders by <strong>making digital processes transparent</strong> and building trust. I’ve found that when you explain best practices clearly, everyone gets on the same page quickly.</li>
<li>You’ll streamline your processes with <strong>clear, standardized workflows</strong>. This means less confusion and faster progress on your projects.</li>
<li>Your digital team gains more <strong>credibility</strong> as you step into a leadership role. You’ll be able to show your real value to the organization.</li>
<li>Best of all, you’ll <strong>reduce friction in your daily work</strong>. When everyone understands the policies, you’ll face fewer misunderstandings and conflicts.</li>
</ul>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aA%20digital%20playbook%20isn%e2%80%99t%20just%20a%20tool;%20it%e2%80%99s%20a%20way%20to%20transform%20challenges%20into%20opportunities%20for%20greater%20impact.%0a&url=https://smashingmagazine.com%2f2025%2f01%2fdigital-playbook-crucial-counterpart-design-system%2f">
      
A digital playbook isn’t just a tool; it’s a way to transform challenges into opportunities for greater impact.

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

<p>But, no doubt you are wondering, what exactly goes into a digital playbook?</p>

<h2 id="key-components-of-a-digital-playbook">Key Components Of A Digital Playbook</h2>

<p>Every digital playbook is unique, but if you’ve ever wondered where to start, here are some key areas to consider. Let’s walk through them together.</p>

<h3 id="engaging-with-the-digital-team">Engaging With The Digital Team</h3>

<p>Have you ever had people come to you too late in the process or approach you with solutions rather than explaining the underlying problems? A playbook can help mitigate these issues by providing clear guidance on:</p>

<ul>
<li>How to request a new website or content update at the right time;</li>
<li>What information you require to do your job;</li>
<li>What stakeholders need to consider before requesting your help.</li>
</ul>

<p>By addressing these common challenges, you’re not just reducing your frustrations &mdash; you’re educating stakeholders and encouraging better collaboration.</p>

<h3 id="digital-project-lifecycle">Digital Project Lifecycle</h3>

<p>Most digital projects can feel overwhelming without a clear structure, especially for stakeholders who may not understand the intricacies of the process. That’s why it’s essential to communicate the key phases clearly to those requesting your team’s help. For example:</p>

<ul>
<li><strong>Discovery</strong>: Explain how your team will research goals, user needs, and requirements to ensure the project starts on solid ground.</li>
<li><strong>Prototyping</strong>: Highlight the importance of testing initial concepts to validate ideas before full development.</li>
<li><strong>Build</strong>: Detail the process of developing the final product and incorporating feedback.</li>
<li><strong>Launch</strong>: Set clear expectations for rolling out the project with a structured plan.</li>
<li><strong>Management</strong>: Clarify how the team will optimize and maintain the product over time.</li>
<li><strong>Retirement</strong>: Help stakeholders understand when and how to phase out outdated tools or content effectively.</li>
</ul>

<p>I’ve structured the lifecycle this way to help stakeholders understand what to expect. When they know what’s happening at each stage, it builds trust and helps the working relationship. Stakeholders will see exactly what role you play and how your team adds value throughout the process.</p>

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

<h3 id="publishing-best-practices">Publishing Best Practices</h3>

<p>Writing for the web isn’t the same as traditional writing, and it’s critical for your team to help stakeholders understand the differences. Your playbook can include practical advice to guide them, such as:</p>

<ul>
<li>Planning and organizing content to align with user needs and business goals.</li>
<li>Crafting content that’s user-friendly, SEO-optimized, and designed for clarity.</li>
<li>Maintaining accessible and high-quality standards to ensure inclusivity.</li>
</ul>

<p>By providing this guidance, you empower stakeholders to create content that’s not only effective but also reflects your team’s standards.</p>

<h3 id="understanding-your-users">Understanding Your Users</h3>

<p>Helping stakeholders understand your audience is essential for creating user-centered experiences. Your digital playbook can support this by including:</p>

<ul>
<li>Detailed user personas that highlight specific needs and behaviors.</li>
<li>Recommendations for tools and methods to gather and analyze user data.</li>
<li>Practical tips for ensuring digital experiences are inclusive and accessible to all.</li>
</ul>

<p>By sharing this knowledge, your team helps stakeholders make decisions that prioritize users, ultimately leading to more successful outcomes.</p>

<h3 id="recommended-resources">Recommended Resources</h3>

<p>Stakeholders often are unaware of the wealth of resources that can help them improve their digital deliverables. Your playbook can help by recommending trusted solutions, such as:</p>

<ul>
<li>Tools that enable stakeholders to carry out their own user research and testing.</li>
<li>Analytics tools that allow stakeholders to track the performance of their websites.</li>
<li>A list of preferred suppliers in case stakeholders need to bring in external experts.</li>
</ul>

<p>These recommendations ensure stakeholders are equipped with reliable resources that align with your team’s processes.</p>

<h3 id="policies-and-governance">Policies And Governance</h3>

<p>Uncertainty about organizational policies can lead to confusion and missteps. Your playbook should provide clarity by outlining:</p>

<ul>
<li><strong>Accessibility and inclusivity standards</strong> to ensure compliance and user satisfaction.</li>
<li><strong>Data privacy and security protocols</strong> to safeguard user information.</li>
<li>Clear processes for prioritizing and governing projects to maintain focus and consistency.</li>
</ul>

<p>By setting these expectations, your team establishes a foundation of trust and accountability that stakeholders can rely on.</p>

<p>Of course, you can have the best digital playbook in the world, but if people don’t reference it, then it is a wasted opportunity.</p>

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

<h3 id="making-your-digital-playbook-stick">Making Your Digital Playbook Stick</h3>

<p>It falls to you and your team to ensure as many stakeholders as possible engage with your playbook. Try the following:</p>

<ul>
<li><strong>Make It Easy to Find</strong><br />
How often do stakeholders struggle to find important resources? Avoid hosting the playbook in a forgotten corner of your intranet. Instead, place it front and center on a well-maintained, user-friendly site that’s accessible to everyone.</li>
<li><strong>Keep It Engaging</strong><br />
Let’s face it &mdash; nobody wants to sift through walls of text. Use visuals like infographics, short explainer videos, and clear headings to make your playbook not only digestible but also enjoyable to use. Think of it as creating a resource your stakeholders will actually want to refer back to.</li>
<li><strong>Frame It as a Resource</strong><br />
A common pitfall is presenting the playbook as a rigid set of rules. Instead, position it as a helpful guide designed to make everyone’s work easier. Highlight how it can simplify workflows, improve outcomes, and solve real-world problems your stakeholders face daily.</li>
<li><strong>Share at Relevant Moments</strong><br />
Don’t wait for stakeholders to find the playbook themselves. Instead, proactively share relevant sections when they’re most needed. For example, send the discovery phase documentation when starting a new project or share content guidelines when someone is preparing to write for the website. This just-in-time approach ensures the playbook’s guidance is applied when it matters most.</li>
</ul>

<h2 id="start-small-then-scale">Start Small, Then Scale</h2>

<p>Creating a digital playbook might sound like a daunting task, but it doesn’t have to be. Begin with a few core sections and expand over time. Assign ownership to a specific team or individual to ensure it remains updated and relevant.</p>

<p>In the end, <strong>a digital playbook is an investment</strong>. It saves time, reduces conflicts, and elevates your organization’s digital maturity.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aJust%20as%20a%20design%20system%20is%20critical%20for%20visual%20harmony,%20a%20digital%20playbook%20is%20essential%20for%20operational%20excellence.%0a&url=https://smashingmagazine.com%2f2025%2f01%2fdigital-playbook-crucial-counterpart-design-system%2f">
      
Just as a design system is critical for visual harmony, a digital playbook is essential for operational excellence.

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

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

<ul>
<li>“<a href="https://www.smashingmagazine.com/2023/05/design-patterns-collaborate-design-system/">Design Patterns Are A Better Way To Collaborate On Your Design System</a>,” Ben Clemens</li>
<li>“<a href="https://www.smashingmagazine.com/2022/11/design-systems-inspiration-resources-case-studies/">Design Systems: Useful Examples and Resources</a>,” Cosima Mielke</li>
<li>“<a href="https://www.smashingmagazine.com/2023/12/building-components-consumption-not-complexity-part1/">Building Components For Consumption, Not Complexity (Part 1)</a>,” Luis Ouriach</li>
<li>“<a href="https://www.smashingmagazine.com/2022/12/taking-stress-out-design-system-management/">Taking The Stress Out Of Design System Management</a>,” Masha Shaposhnikova</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>Alvaro Saburido</author><title>Navigating The Challenges Of Modern Open-Source Authoring: Lessons Learned</title><link>https://www.smashingmagazine.com/2025/01/navigating-challenges-modern-open-source-authoring/</link><pubDate>Tue, 21 Jan 2025 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2025/01/navigating-challenges-modern-open-source-authoring/</guid><description>Alvaro Saburido delves into the current state and challenges of Open-Source authoring, sharing lessons learned from both community- and company-driven initiatives.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2025/01/navigating-challenges-modern-open-source-authoring/" />
              <title>Navigating The Challenges Of Modern Open-Source Authoring: Lessons Learned</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Navigating The Challenges Of Modern Open-Source Authoring: Lessons Learned</h1>
                  
                    
                    <address>Alvaro Saburido</address>
                  
                  <time datetime="2025-01-21T08:00:00&#43;00:00" class="op-published">2025-01-21T08:00:00+00:00</time>
                  <time datetime="2025-01-21T08:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                <p>This article is sponsored by <b>Storyblok</b></p>
                

<p>Open source is the backbone of modern software development. As someone deeply involved in both community-driven and company-driven open source, I’ve had the privilege of experiencing its diverse approaches firsthand. This article dives into what modern OSS (Open Source) authoring looks like, focusing on front-end JavaScript libraries such as <a href="https://tresjs.org/">TresJS</a> and tools I’ve contributed to at <a href="https://github.com/storyblok">Storyblok</a>.</p>

<p>But let me be clear:</p>

<blockquote>There’s no universal playbook for OSS. Every language, framework, and project has its own workflows, rules, and culture &mdash; and that’s okay. These variations are what make open source so adaptable and diverse.</blockquote>

<h3 id="the-art-of-oss-authoring">The Art Of OSS Authoring</h3>

<p>Authoring an open-source project often begins with scratching your own itch &mdash; solving a problem you face as a developer. But as your “experiment” gains traction, the challenge shifts to addressing diverse use cases while maintaining the simplicity and focus of the original idea.</p>

<p>Take TresJS as an example. All I wanted was to add 3D to my personal Nuxt portfolio, but at that time, there wasn’t a maintained, feature-rich alternative to React Three Fiber in VueJS. So, I decided to create one. Funny enough, after two years after the library’s launch, my portfolio remains unfinished.</p>

<h3 id="community-driven-oss-authoring-lessons-from-tresjs">Community-driven OSS Authoring: Lessons From TresJS</h3>

<p>Continuing with TresJS as an example of a community-driven OSS project, the community has been an integral part of its growth, offering ideas, filing issues (around 531 in total), and submitting pull requests (around 936 PRs) of which 90% eventually made it to production. As an author, this is the best thing that can happen &mdash; it’s probably one of the biggest reasons I fell in love with open source. The continuous collaboration creates an environment where new ideas can evolve into meaningful contributions.</p>

<p>However, it also comes with its own challenges. The more ideas come in, the harder it becomes to maintain the project’s focus on its original purpose.</p>

<blockquote>As authors, it’s our responsibility to keep the vision of the library clear &mdash; even if that means saying no to great ideas from the community.</blockquote>

<p>Over time, some of the most consistent collaborators became part of a core team, helping to share the responsibility of maintaining the library and ensuring it stays aligned with its original goals.</p>

<p>Another crucial aspect of scaling a project, especially one like TresJS, which has grown into an ecosystem of packages, is the <strong>ability to delegate</strong>. The more the project expands, the more essential it becomes to distribute responsibilities among contributors. Delegation helps in reducing the burden of the massive workload and empowers contributors to <strong>take ownership</strong> of specific areas. As a core author, it’s equally important to provide the necessary tools, CI workflows, and clear conventions to make the process of contributing as simple and efficient as possible. A well-prepared foundation ensures that new and existing collaborators can focus on what truly matters &mdash; pushing the project forward.</p>

<h3 id="company-driven-oss-authoring-the-storyblok-perspective">Company-driven OSS Authoring: The Storyblok Perspective</h3>

<p>Now that we’ve explored the bright spots and challenges of community-driven OSS let’s jump into a different realm: company-driven OSS.</p>

<p>I had experience with inner-source and open-source in previous companies, so I already had a grasp of how OSS works in the context of a company environment. However, my most meaningful experience would come later, specifically earlier this year, when I switched my role from DevRel to a full-time Developer Experience Engineer, and I say “full-time” because before taking the role, I was already contributing to Storyblok’s SDK ecosystem.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.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/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/navigating-challenges-modern-open-source-authoring/storyblok-to-smashing-magazine-surviving-modern-open-source-authoring-inline-image.png"
			
			sizes="100vw"
			alt="Visualisation for open source authoring"
		/>
    
    </a>
  

  
</figure>

<p>At <a href="https://www.storyblok.com/lp/developers?utm_source=smashing&amp;utm_medium=sponsor&amp;utm_campaign=DGM_DEV_SMA_TRA&amp;utm_content=smashing-OSS">Storyblok</a>, open source plays a crucial role in how we engage with developers and how they seamlessly use our product with their favorite framework. Our goal is to provide the same developer experience regardless of the flavor, making the experience of using Storyblok as simple, effective, and enjoyable as possible.</p>

<p>To achieve this, it’s crucial to <strong>balance the needs</strong> of the developer community &mdash; which often reflect the needs of the clients they work for &mdash; with the company’s broader goals. One of the things I find more challenging is <strong>managing expectations</strong>. For instance, while the community may want feature requests and bug fixes to be implemented quickly, the company’s priorities might dictate focusing on stability, scalability, and often strategic integrations. <strong>Clear communication</strong> and <strong>prioritization</strong> are key to maintaining healthy alignment and trust between both sides.</p>

<p>One of the unique advantages of company-driven open source is the <strong>availability of resources</strong>:</p>

<ul>
<li>Dedicated engineering time,</li>
<li>Infrastructure (which many OSS authors often cannot afford),</li>
<li>Access to knowledge from internal teams like design, QA, and product management.</li>
</ul>

<p>However, this setup often comes with the challenge of dealing with legacy codebases &mdash; typically written by developers who may not be familiar with OSS principles. This can lead to inconsistencies in structure, testing, and documentation that require significant refactoring before the project can align with open-source best practices.</p>

<h3 id="navigating-the-spectrum-community-vs-company">Navigating The Spectrum: Community vs. Company</h3>

<p>I like to think of community-driven OSS as being like jazz music—freeform, improvised, and deeply collaborative. In contrast, company-driven OSS resembles an orchestra, with a conductor guiding the performance and ensuring all the pieces fit together seamlessly.</p>

<p>The truth is that most OSS projects &mdash; if not the vast majority &mdash; exist somewhere along this spectrum. For example, TresJS began as a purely community-driven project, but as it matured and gained traction, elements of structured decision-making &mdash; more typical of company-driven projects &mdash; became necessary to maintain focus and scalability. Together with the core team, we defined a vision and goals for the project to ensure it continued to grow without losing sight of its original purpose.</p>

<p>Interestingly, the reverse is also true: Company-driven OSS can benefit significantly from the fast-paced innovation seen in community-driven projects.</p>

<p>Many of the improvements I’ve introduced to the <a href="https://www.storyblok.com/lp/developers?utm_source=smashing&amp;utm_medium=sponsor&amp;utm_campaign=DGM_DEV_SMA_TRA&amp;utm_content=smashing-OSS">Storyblok ecosystem</a> since joining were inspired by ideas first explored in TresJS. For instance, migrating the TresJS ecosystem to <strong><code>pnpm workspaces</code></strong> demonstrated how streamlined dependency management could improve development workflows like playgrounds and e2e &mdash; an approach we gradually adapted later for Storyblok’s ecosystem.</p>

<p>Similarly, transitioning Storyblok testing from Jest to Vitest, with its improved performance and developer experience, was influenced by how testing is approached in community-driven projects. Likewise, our switch from Prettier to ESLint’s v9 flat configuration with auto-fix helped consolidate linting and formatting into a single workflow, streamlining developer productivity.</p>

<p>Even more granular processes, such as modernizing CI workflows, found their way into Storyblok. TresJS’s evolution from a single monolithic release action to granular steps for linting, testing, and building provided a blueprint for enhancing our pipelines at Storyblok. We also adopted continuous release practices inspired by <strong><code>pkg.pr.new</code></strong>, enabling faster delivery of incremental changes and testing package releases in real client projects to gather immediate feedback before merging the PRs.</p>

<p>That said, TresJS also benefited from my experiences at Storyblok, which had a more mature and battle-tested ecosystem, particularly in adopting automated processes. For example, we integrated Dependabot to keep dependencies up to date and used auto-merge to reduce manual intervention for minor updates, freeing up contributors’ time for more meaningful work. We also implemented an automatic release pipeline using GitHub Actions, inspired by Storyblok’s workflows, ensuring smoother and more reliable releases for the TresJS ecosystem.</p>

<h3 id="the-challenges-of-modern-oss-authoring">The Challenges of Modern OSS Authoring</h3>

<p>Throughout this article, we’ve touched on several modern OSS challenges, but if one deserves the crown, it’s <strong>managing breaking changes and maintaining compatibility</strong>. We know how fast the pace of technology is, especially on the web, and users expect libraries and tools to keep up with the latest trends. I’m not the first person to say that hype-driven development can be fun, but it is inherently risky and not your best ally when building reliable, high-performance software &mdash; especially in enterprise contexts.</p>

<p>Breaking changes exist. That’s why semantic versioning comes into play to make our lives easier. However, it is equally important to <strong>balance innovation with stability</strong>. This becomes more crucial when introducing new features or refactoring for better performance, breaking existing APIs. One key lesson I’ve learned &mdash; particularly during my time at Storyblok &mdash; is the importance of <strong>clear communication</strong>. Changelogs, migration guides, and deprecation warnings are invaluable tools to smoothen the transition for users.</p>

<blockquote><strong>A practical example:</strong><br /><br />My first project as a Developer Experience Engineer was introducing <code>@storyblok/richtext</code>, a library for rich-text processing that (at the time of writing) sees around 172k downloads per month. The library was crafted during my time as a DevRel, but transitioning users to it from the previous rich-text implementation across the ecosystem required careful planning. Since the library would become a dependency of the fundamental JS SDK &mdash; and from there propagate to all the framework SDKs &mdash; together with my manager, we planned a multi-month transition with a retro-compatible period before the major release. This included communication campaigns, thorough documentation, and gradual adoption to minimize disruption.<br /><br />Despite these efforts, mistakes happened &mdash; and that’s okay. During the rich-text transition, there were instances where updates didn’t arrive on time or where communication and documentation were temporarily out of sync. This led to confusion within the community, which we addressed by providing timely support on GitHub issues and Discord. These moments served as reminders that <strong>even with semantic versioning, modular architectures, and meticulous planning, OSS authoring is never perfect</strong>. Mistakes are part of the process.</blockquote>

<p>And that takes us to the following point.</p>

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

<p>Open-source authoring is a journey of continuous learning. Each misstep offers a chance to improve, and each success reinforces the value of collaboration and experimentation.</p>

<p>There’s no “perfect” way to do OSS, and that’s the beauty of it. Every project has its own set of workflows, challenges, and quirks shaped by the community and its contributors. These differences make open source adaptable, dynamic, fun, and, above all, impactful. No matter if you’re building something entirely new or contributing to an existing project, remember that progress, not perfection, is the goal.</p>

<p>So, keep contributing, experimenting, and sharing your work. Every pull request, issue, and idea you put forward brings value &amp;mdashp not just to your project but to the broader ecosystem.</p>

<p>Happy coding!</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>Atila Fassina</author><title>The Hype Around Signals</title><link>https://www.smashingmagazine.com/2024/11/the-hype-around-signals/</link><pubDate>Wed, 27 Nov 2024 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/11/the-hype-around-signals/</guid><description>From KnockoutJS to modern UI libraries like SolidJS, Vue.js, and Svelte, signals revolutionized how we think about reactivity in UIs. Here’s a deep dive into their history and impact by Atila Fassina.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/11/the-hype-around-signals/" />
              <title>The Hype Around Signals</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The Hype Around Signals</h1>
                  
                    
                    <address>Atila Fassina</address>
                  
                  <time datetime="2024-11-27T10:00:00&#43;00:00" class="op-published">2024-11-27T10:00:00+00:00</time>
                  <time datetime="2024-11-27T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>The groundwork for what we call today “signals” dates as early as the 1970s. Based on this work, they became popular with different fields of computer science, defining them more specifically around the 90s and the early 2000s.</p>

<p>In Web Development, they first made a run for it with <a href="https://knockoutjs.com">KnockoutJS</a>, and shortly after, signals took a backseat in (most of) our brains. Some years ago, multiple similar implementations came to be.</p>

<ul>
<li><a href="https://mobx.js.org/observable-state.html">MobX observable states</a></li>
<li><a href="https://vuejs.org/api/reactivity-advanced.html">Vue.js refs and shallow refs</a></li>
<li><a href="https://docs.solidjs.com/concepts/intro-to-reactivity">SolidJS signals</a></li>
</ul>

<p>With different names and implementation details, those approaches are similar enough to be wrapped in a category we know today as <strong>Fine-Grained Reactivity</strong>, even if they have different levels of “fine” x “coarse” updates &mdash; we’ll get more into what this means soon enough.</p>

<p><strong>To summarize the history</strong>: Even being an older technology, signals started a revolution in how we thought about interactivity and data in our UIs at the time. And since then, every UI library (SolidJS, Marko, Vue.js, Qwik, Angular, Svelte, Wiz, Preact, etc) has adopted some kind of implementation of them (except for React).</p>

<p>Typically, a <strong>signal</strong> is composed of an accessor (getter) and a setter. The setter establishes an update to the value held by the signal and triggers all dependent effects. While an accessor pulls the value from the source and is run by effects every time a change happens upstream.</p>

<pre><code class="language-javascript">const [signal, setSignal] = createSignal("initial value");

setSignal("new value");

console.log(signal()); // "new value"
</code></pre>

<p>In order to understand the reason for that, we need to dig a little deeper into what <strong>API Architectures</strong> and <strong>Fine-Grained Reactivity</strong> actually mean.</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="/printed-books/image-optimization/">Image Optimization</a></strong>, Addy Osmani’s new practical guide to optimizing and delivering <strong>high-quality images</strong> on the web. Everything in one single <strong>528-pages</strong> book.</p>
<a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/image-optimization/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2c669cf1-c6ef-4c87-9901-018b04f7871f/image-optimization-shop-cover-opt.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/87fd0cfa-692e-459c-b2f3-15209a1f6aa7/image-optimization-shop-cover-opt.png"
    alt="Feature Panel"
    width="480"
    height="697"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="api-architectures">API Architectures</h2>

<p>There are two basic ways of defining systems based on how they handle their data. Each of these approaches has its pros and cons.</p>

<ul>
<li><strong>Pull</strong>: The consumer pings the source for updates.</li>
<li><strong>Push</strong>: The source sends the updates as soon as they are available.</li>
</ul>

<p><strong>Pull</strong> systems need to handle polling or some other way of maintaining their data up-to-date. They also need to guarantee that all consumers of the data get torn down and recreated once new data arrives to avoid <strong>state tearing</strong>.</p>

<blockquote><strong>State Tearing</strong> occurs when different parts of the same UI are at different stages of the state. For example, when your header shows 8 posts available, but the list has 10.</blockquote>

<p><strong>Push</strong> systems don’t need to worry about maintaining their data up-to-date. Nevertheless, the source is unaware of whether the consumer is ready to receive the updates. This can cause <strong>backpressure</strong>. A data source may send too many updates in a shorter amount of time than the consumer is capable of handling. If the update flux is too intense for the receiver, it can cause loss of data packages (leading to <strong>state tearing</strong> once again) and, in more serious cases, even crash the client.</p>

<p>In <strong>pull</strong> systems, the accepted tradeoff is that data is unaware of where it’s being used; this causes the receiving end to create precautions around maintaining all their components up-to-date. That’s how systems like <a href="https://react.dev">React</a> work with their teardown/re-render mechanism around updates and reconciliation.</p>

<p>In <strong>push</strong> systems, the accepted tradeoff is that the receiving end needs to be able to deal with the update stream in a way that won’t cause it to crash while maintaining all consuming nodes in a synchronized state. In web development, <a href="https://rxjs.dev">RxJS</a> is the most popular example of a push system.</p>

<p>The attentive reader may have noticed the tradeoffs on each system are at the opposite ends of the spectrum: while pull systems are good at scheduling the updates efficiently, in push architectures, the data knows where it’s being used &mdash; allows for more granular control. That’s what makes a great opportunity for a <strong>hybrid</strong> model.</p>

<h2 id="push-pull-architectures">Push-Pull Architectures</h2>

<p>In Push-Pull systems, the state has a list of subscribers, which can then trigger for re-fetching data once there is an update. The way it differs from traditional push is that the update itself isn’t sent to the subscribers &mdash; just a notification that they’re now stale.</p>

<p>Once the subscriber is aware its current state has become stale, it will then fetch the new data at a proper time, avoiding any kind of backpressure and behaving similarly to the pull mechanism. The difference is that this only happens when the subscriber is certain there is new data to be fetched.</p>

<p>We call these data <strong>signals</strong>, and the way those subscribers are triggered to update are called <strong>effects</strong>. Not to confuse with <code>useEffect</code>, which is a similar name for a completely different thing.</p>

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

<h2 id="fine-grained-reactivity">Fine-Grained Reactivity</h2>

<p>Once we establish the two-way interaction between the data source and data consumer, we will have a reactive system.</p>

<blockquote>A <strong>reactive system</strong> only exists when data can notify the consumer it has changed, and the consumer can apply those changes.</blockquote>

<p>Now, to make it <strong>fine-grained</strong> there are two fundamental requirements that need to be met:</p>

<ol>
<li><strong>Efficiency</strong>: The system only executes the minimum amount of computations necessary.</li>
<li><strong>Glitch-Free</strong>: No intermediary states are shown in the process of updating a state.</li>
</ol>

<h3 id="efficiency-in-uis">Efficiency In UIs</h3>

<p>To really understand how signals can achieve high levels of efficiency, one needs to understand what it means to have an <strong>accessor</strong>. In broad strokes, they behave as getter functions. Having an accessor means the value does not exist within the boundaries of our component &mdash; what our templates receive is a getter for that value, and every time their effects run, they will bring an up-to-date new value. This is why signals are functions and not simple variables. For example, in Solid:</p>

<pre><code class="language-javascript">import { createSignal } from 'solid-js'

function ReactiveComponent() {
  const [signal, setSignal] = createSignal()
  
  return (
    &lt;h1&gt;Hello, {signal()}&lt;/h1&gt;
  )
}
</code></pre>

<p>The part that is relevant to performance (and efficiency) in the snippet above is that considering <code>signal()</code> is a getter, it does not need to re-run the whole <code>ReactiveComponent()</code> function to update the rendered artifact; only the signal is re-run, guaranteeing no extra computation will run.</p>

<h2 id="glitch-free-uis">Glitch-Free UIs</h2>

<p>Non-reactive systems avoid intermediary states by having a teardown/re-render mechanism. They toss away the artifacts with possibly stale data and recreate everything from scratch. That works well and consistently but at the expense of efficiency.</p>

<p>In order to understand how reactive systems handle this problem, we need to talk about the <strong>Diamond Challenge</strong>. This is a quick problem to describe but a tough one to solve. Take a look at the diagram below:</p>














<figure class="
  
  
  ">
  
    <a href="https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="679"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg"
			
			sizes="100vw"
			alt="Diamond diagram"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/the-hype-around-signals/1-diamond-diagram.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Pay attention to the <code>E</code> node. It depends on <code>D</code> and <code>B</code>, but only <code>D</code> depends on <code>C</code>.</p>

<p>If your reactive system is too eager to update, it can receive the update from <code>B</code> while <code>D</code> is still stale. That will cause <code>E</code> to show an intermediary state that should not exist.</p>

<p>It’s easy and intuitive to have <code>A</code> trigger its children for updates as soon as new data arrives and let it cascade down. But if this happens, <code>E</code> receives the data from <code>B</code> while <code>D</code> is stale. If <code>B</code> is able to trigger an update from <code>E</code>, <code>E</code> will show an intermediate state.</p>

<p>Each implementation adopts different update strategies to solve this challenge. They can be grouped into two categories:</p>

<ol>
<li><strong>Lazy Signals</strong><br />
Where a scheduler defines the order within which the updates will occur. (<code>A</code>, then <code>B</code> and <code>C</code>, then <code>D</code>, and finally <code>E</code>).</li>
<li><strong>Eager Signals</strong><br />
When signals are aware if their parents are <strong>stale</strong>, <strong>checking</strong>, or <strong>clean</strong>. In this approach, when <code>E</code> receives the update from <code>B</code>, it will trigger a check/update on <code>D</code>, which will climb up until it can ensure to be back in a <strong>clean</strong> state, allowing <code>E</code> to finally update.</li>
</ol>

<h2 id="back-to-our-uis">Back To Our UIs</h2>

<p>After this dive into what <strong>fine-grained reactivity</strong> means, it’s time to take a step back and look at our websites and apps. Let’s analyze what it means to our daily work.</p>

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

<h2 id="dx-and-ux">DX And UX</h2>

<p>When the code we wrote is easier to reason about, we can then focus on the things that really matter: the features we deliver to our users. Naturally, tools that require less work to operate will deliver less maintenance and overhead for the craftsperson.</p>

<p>A system that is glitch-free and efficient by default will get out of the developer’s way when it’s time to build with it. It will also enforce a higher connection to the platform via a thinner abstraction layer.</p>

<p>When it comes to Developer Experience, there is also something to be said about known territory. People are more productive within the mental models and paradigms they are used to. Naturally, solutions that have been around for longer and have solved a larger quantity of challenges are easier to work with, but that is <strong>at odds with innovation</strong>. It was a cognitive exercise when JSX came around and replaced imperative DOM updates with jQuery. In the same way, a new paradigm to handle rendering will cause a similar discomfort until it becomes common.</p>

<h2 id="going-deeper">Going Deeper</h2>

<p>We will talk further about this in the next article, where we’re looking more closely into different implementations of signals (lazy, eager, hybrid), scheduled updates, interacting with the DOM, and debugging your own code!</p>

<p>Meanwhile, you can find me in the comments section below, on <a href="https://atila.io/x">𝕏 (Twitter)</a>, <a href="https://atila.io/linkedin">LinkedIn</a>, <a href="https://atila.io/bsky">BlueSky</a>, or even <a href="https://atila.io/youtube">youtube</a>. I’m always happy to chat, and if you tell me what you want to know, I’ll make sure to include it in the next article! See ya!</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>Atila Fassina</author><title>Open-Source Meets Design Tooling With Penpot</title><link>https://www.smashingmagazine.com/2024/11/open-source-meets-design-tooling-penpot/</link><pubDate>Thu, 14 Nov 2024 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/11/open-source-meets-design-tooling-penpot/</guid><description>Penpot helps designers and developers work better together by offering a free, open-source design tool based on open web standards. Today, let’s explore its newly released Penpot Plugin System. So now, if there’s a functionality missing, you don’t need to jump into the code base straight away; you can create a plugin to achieve what you need. And you can even serve it from localhost!</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/11/open-source-meets-design-tooling-penpot/" />
              <title>Open-Source Meets Design Tooling With Penpot</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Open-Source Meets Design Tooling With Penpot</h1>
                  
                    
                    <address>Atila Fassina</address>
                  
                  <time datetime="2024-11-14T10:00:00&#43;00:00" class="op-published">2024-11-14T10:00:00+00:00</time>
                  <time datetime="2024-11-14T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                <p>This article is sponsored by <b>Penpot</b></p>
                

<p>Penpot is a free, open-source design tool that allows true collaboration between designers and developers. Designers can create interactive prototypes and design systems at scale, while developers enjoy ready-to-use code and make their workflow easy and fast because it&rsquo;s built with web technologies, works in the browser, and has already passed <a href="https://github.com/penpot/penpot">33K starts on GitHub</a>.</p>

<p>The UI feels intuitive and makes it easy to get things done, even for someone who’s not a designer (guilty as charged!). You can get things done in the same way and with the same quality as with other more popular and closed-source tools like Figma.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="532"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png"
			
			sizes="100vw"
			alt="Penpot tool"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/1-penpot.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="why-open-source-is-important">Why Open-Source Is Important</h2>

<p>As someone who works with commercial open-source on my day-to-day, I strongly believe in it as a way to be closer to your users and unlock the next level of delivery. Being open-source creates a whole new level of accountability and flexibility for a tool.</p>

<p>Developers are a different breed of user. When we hit a quirk or a gap in the UX, our first instinct is to play detective and figure out why that pattern stuck out as a sore thumb to what we’ve been doing. When the code is open-source, it’s not unusual for us to jump into the source and create an issue with a proposal on how to solve it already. At least, that’s the dream.</p>

<p>On top of that, being open-source allows you and your team to <strong>self-host</strong>, giving you that extra layer of privacy and control, or at least a more cost-effective solution if you have the time and skills to DYI it all.</p>

<p>When the cards are played right, and the team is able to afford the long-term benefits, commercial open-source is a win-win strategy.</p>

<h2 id="introducing-penpot-plugin-system">Introducing: Penpot Plugin System</h2>

<p>Talking about the extensibility of open-source, Penpot has the <a href="https://penpot.app/penpothub?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=PluginsContest">PenpotHub</a> the home for open-source <strong>templates</strong> and the newly released <strong>plugin</strong> gallery. So now, if there’s a functionality missing, you don’t need to jump into the code-base straightaway &mdash; you can create a plugin to achieve what you need. And you can even serve it from localhost!</p>

<h3 id="creating-penpot-plugins">Creating Penpot Plugins</h3>

<p>When it comes to the plugins, creating one is extremely ergonomic. First, there are already set <a href="https://penpot.app/penpothub/libraries-templates?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=PluginsContest">templates</a> for a few frameworks, and I created one for <a href="https://github.com/penpot/plugin-examples/pull/2">SolidJS in this PR</a> &mdash; the power of open-source!</p>

<p>When using <a href="https://vite.dev/">Vite</a>, plugins are Single-Page Applications; if you have ever built a Hello World app with Vite, you have what it takes to create a plugin. On top of that, the Penpot team has a few packages that can give you a headstart in the process:</p>

<pre><code class="language-bash">npm install @penpot/plugin-styles
</code></pre>

<p>That will allow you to import with a CSS loader or a CSS import from <code>@penpot/plugin-styles/styles.css</code>. The JavaScript API is available through the window object, but if your plugin is in TypeScript, you need to teach it:</p>

<pre><code class="language-bash">npm add -D @penpot/plugin-types
</code></pre>

<p>With those types in your <code>node_modules</code>, you can pop-up the <code>tsconfig.json</code> and add the <code>types</code> to the <code>compilerOptions</code>.</p>

<pre><code class="language-javascript">{
  "compilerOptions": {
    "types": ["@penpot/plugin-types"]
  }
}
</code></pre>

<p>And there you are, now, the Language Service Provider in your editor and the TypeScript Compiler will accept that penpot is a valid namespace, and you’ll have auto-completion for the Penpot APIs throughout your entire project. For example, defining your plugin will look like the following:</p>

<pre><code class="language-javascript">penpot.ui.open("Your Plugin Name", "", {
  width: 500,
  height: 600
})
</code></pre>

<p>The last step is to define a plugin manifest in a <code>manifest.json</code> file and make sure it’s in the outpot directory from Vite. The manifest will indicate where each asset is and what permissions your plugin requires to work:</p>

<div class="break-out">
<pre><code class="language-javascript">{
  "name": "Your Plugin Name",
  "description": "A Super plugin that will win Penpot Plugin Contest",
  "code": "/plugin.js",
  "icon": "/icon.png",
  "permissions": [
    "content:read",
    "content:write",
    "library:read",
    "library:write",
    "user:read",
    "comment:read",
    "comment:write",
    "allow:downloads"
  ]
}
</code></pre>
</div>

<p>Once the initial setup is done, the communication between the Penpot API and the plugin interface is done with a bidirectional messaging system, not so different than what you’d do with a Web-Worker.</p>

<p>So, to send a message from your plugin to the Penpot API, you can do the following:</p>

<pre><code class="language-javascript">penpot.ui.sendMessage("Hello from my Plugin");
</code></pre>

<p>And to receive it back, you need to add an event listener to the <code>window</code> object (the top-level scope) of your plugin:</p>

<div class="break-out">
<pre><code class="language-javascript">window.addEventListener("message", event =&gt; {
  console.log("Received from Pendpot::: ", event.data);
})
</code></pre>
</div>

<p><strong>A quick performance tip</strong>: <em>If you’re creating a more complex plugin with different views and perhaps even routes, you need to have a cleanup logic. Most frameworks provide decent ergonomics to do that; for example, React does it via their return statements.</em></p>

<div class="break-out">
<pre><code class="language-javascript">useEffect(() =&gt; {
  function handleMessage(e) {
    console.log("Received from Pendpot::: ", event.data);
  }
  window.addEventListener('message', handleMessage);
  
  return () =&gt; window.removeEventListener('message', handleMessage);
}, []);
</code></pre>
</div>

<p>And Solid has <code>onMount</code> and <code>onCleanup</code> helpers for it:</p>

<pre><code class="language-javascript">onMount(() =&gt; {
  function handleMessage(e) {
    console.log("Received from Penpot::: ", event.data);
  }
  window.addEventListener('message', handleMessage);
})

onCleanup(() =&gt; {
  window.removeEventListener('message', handleMessage);
})
</code></pre>

<p>Or with the <a href="https://primitives.solidjs.community/package/event-listener#createeventlistener"><code>@solid-primitive/event-listener</code></a> helper library, so it will be automatically disposed:</p>

<div class="break-out">
<pre><code class="language-javascript">import { makeEventListener } from "@solid-primitives/event-listener";

function Component() {
  
  const clear = makeEventListener(window, "message", handleMessage);
  
  // ...
  return (&lt;span&gt;Hello!&lt;/span&gt;)
}
</code></pre>
</div>

<p>In the official documentation, there’s a <a href="https://help.penpot.app/plugins/create-a-plugin?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=PluginsContest">step-by-step guide</a> that will walk you through the process of creating, testing, and publishing your plugin. It will even help you out.</p>

<p>So, what are you waiting for?</p>

<h2 id="plugin-contest-imagine-build-win">Plugin Contest: Imagine, Build, Win</h2>

<p>Well, maybe you’re waiting for a push of motivation. The <a href="https://penpot.app/?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=PluginsContest">Penpot</a> team thought of that, which is why they’re starting a <a href="https://penpot.app/plugins-contest?utm_source=SmashingMag&amp;utm_medium=Article&amp;utm_campaign=PluginsContest">Plugin Contest</a>!</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg">
    
    <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/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg"
			
			sizes="100vw"
			alt="Penpot plugin contest poster"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/open-source-meets-design-tooling-penpot/penpot-plugin-contest.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>For this contest, they want a fully functional plugin; it must be open-source and include comprehensive documentation. Detailing its features, installation, and usage. The first prize is US$ 1000, and the criteria are innovation, functionality, usability, performance, and code quality. The contest will run from November 15th to December 15th.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>If you decide to build a plugin, I’d love to know what you’re building and what stack you chose. Please let me know in the comments below or on <a href="https://atila.io/bsky">BlueSky</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>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Mikołaj Dobrucki</author><title>Build Design Systems With Penpot Components</title><link>https://www.smashingmagazine.com/2024/07/build-design-systems-penpot-components/</link><pubDate>Thu, 18 Jul 2024 10:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/07/build-design-systems-penpot-components/</guid><description>In today’s turbulent landscape of design, Penpot stands out with its commitment to open-source, free unlimited access, and its unique, robust features. An example could be its new components system that takes another leap forward in aligning design with code. Let&amp;rsquo;s dive into how it empowers both designers and developers to create more maintainable and scalable design systems.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/07/build-design-systems-penpot-components/" />
              <title>Build Design Systems With Penpot Components</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Build Design Systems With Penpot Components</h1>
                  
                    
                    <address>Mikołaj Dobrucki</address>
                  
                  <time datetime="2024-07-18T10:00:00&#43;00:00" class="op-published">2024-07-18T10:00:00+00:00</time>
                  <time datetime="2024-07-18T10:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                <p>This article is sponsored by <b>Penpot</b></p>
                

<p>If you’ve been following along with our Penpot series, you’re already familiar with this exciting open-source design tool and how it is changing the game for designer-developer collaboration. Previously, we’ve explored Penpot’s <a href="https://www.smashingmagazine.com/2023/06/penpot-flex-layout-building-css-layouts-design-tool/">Flex Layout</a> and <a href="https://www.smashingmagazine.com/2024/04/penpot-css-grid-layout-designing-superpowers/">Grid Layout</a> features, which bring the power of CSS directly into the hands of designers.</p>

<p>Today, we’re diving into another crucial aspect of modern web design and development: <strong>components</strong>. This feature is a part of Penpot’s major 2.0 release, which introduces a host of new capabilities to bridge the gap between design and code further. Let’s explore how Penpot’s implementation of components can supercharge your design workflow and foster even better collaboration across teams.</p>

<h2 id="about-components">About Components</h2>

<p>Components are reusable building blocks that form the foundation of modern user interfaces. They encapsulate a piece of UI or functionality that can be reused across your application. This concept of composability &mdash; building complex systems from smaller, reusable parts &mdash; is a cornerstone of modern web development.</p>

<p>Why does composability matter? There are several key benefits:</p>

<ul>
<li><strong>Single source of truth</strong><br />
Changes to a component are reflected everywhere it’s used, ensuring consistency.</li>
<li><strong>Flexibility with simpler dependencies</strong><br />
Components can be easily swapped or updated without affecting the entire system.</li>
<li><strong>Easier maintenance and scalability</strong><br />
As your system grows, components help manage complexity.</li>
</ul>

<p>In the realm of design, this philosophy is best expressed in the concept of design systems. When done right, design systems help to bring your design and code together, reducing ambiguity and streamlining the processes.</p>

<p>However, that’s not so easy to achieve when your designs are built using logic and standards that are much different from the code they’re related to. Penpot works to solve this challenge through its unique approach. Instead of building visual artifacts that only mimic real-world interfaces, UIs in Penpots are built using the same technologies and standards as real working products.</p>

<p>This gives us much better parity between the media and allows designers to build interfaces that are already expressed as code. It <strong>fosters easier collaboration</strong> as designers and developers can speak the same language when discussing their components. The final result is <strong>more maintainable</strong>, too. Changes created by designers can propagate consistently, making it easier to manage large-scale systems.</p>

<p>Now, let’s take a look at how components in Penpot work in practice! As an example, I’m going to use the following fictional product page and recreate it in Penpot:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="500"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png"
			
			sizes="100vw"
			alt="A fictional product page"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/build-design-systems-penpot-components/1-final-design.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="components-in-penpot">Components In Penpot</h2>

<h3 id="creating-components">Creating Components</h3>

<p>To create a component in Penpot, simply select the objects you want to include and select “Create component” from the context menu. This transforms your selection into a reusable element.</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/984161118"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Create components</figcaption>
	
</figure>

<h3 id="creating-component-variants">Creating Component Variants</h3>

<p>Penpot allows you to create variants of your components. These are alternative versions that share the same basic structure but differ in specific aspects like color, size, or state.</p>

<p>You can create variants by using slashes (<code>/</code>) in the components name, for example, by naming your buttons <code>Button/primary</code> and <code>Button/secondary</code>. This will allow you to easily switch between types of a <code>Button</code> component later.</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/984161706"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Create variants</figcaption>
	
</figure>

<h3 id="nesting-components-and-using-external-libraries">Nesting Components And Using External Libraries</h3>

<p>Components in Penpot can be nested, allowing you to build complex UI elements from simpler parts. This mirrors how developers often structure their code. In other words, you can place components inside one another.</p>

<p>Moreover, the components you use don’t have to come from the same file or even from the same organization. You can easily share libraries of components across projects just as you would import code from various dependencies into your codebase. You can also import components from external libraries, such as UI kits and icon sets. Penpot maintains <a href="https://www.google.com/url?q=https://penpot.app/libraries-templates?utm_source%3DSmashingMag%26utm_medium%3DArticle%26utm_campaign%3DComponents&amp;sa=D&amp;source=docs&amp;ust=1720687336178543&amp;usg=AOvVaw2Esn2Ec0u36wp4ngcc0IPk">a growing list of such resources</a> for you to choose from, including everything from the large design systems like Material Design to the most popular icon libraries.</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/984170510"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Nesting components</figcaption>
	
</figure>

<h3 id="organizing-your-design-system">Organizing Your Design System</h3>

<p>The new major release of Penpot comes with a redesigned <strong>Assets panel</strong>, which is where your components live. In the Assets panel, you can easily access your components and drag and drop them into designs.</p>

<p>For the better maintenance of design systems, Penpot allows you to <strong>store your colors and typography as reusable styles</strong>. Same as components, you can name your styles and organize them into hierarchical structures.</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/984171780"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Assets panel and styles</figcaption>
	
</figure>

<h3 id="configuring-components">Configuring Components</h3>

<p>One of the main benefits of using composable components in front-end libraries such as React is their support of props. Component props (short for properties) allow you a great deal of flexibility in how you configure and customize your components, depending on how, where, and when they are used.</p>

<p>Penpot offers similar capabilities in a design tool with variants and overrides. You can switch variants, hide elements, change styles, swap nested components within instances, or even change the whole layout of a component, providing flexibility while maintaining the link to the original component.</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/984173685"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Configure components</figcaption>
	
</figure>

<h3 id="creating-flexible-scalable-systems">Creating Flexible, Scalable Systems</h3>

<p>Allowing you to <strong>modify Flex and Grid layouts in component instances</strong> is where Penpot really shines. However, the power of these layout features goes beyond the components themselves.</p>

<p>With Flex Layout and Grid Layout, you can build components that are much more faithful to their code and easier to modify and maintain. But having those powerful features at your fingertips means that you can also place your components in other Grid and Flex layouts. That’s a big deal as it allows you to test your components in scenarios much closer to their real environment. Directly in a design tool, you can see how your component would behave if you put it in various places on your website or app. This allows you to fine-tune how your components fit into a larger system. It can dramatically reduce friction between design and code and streamline the handoff process.</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/984174520"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Adjust and create overrides</figcaption>
	
</figure>

<h3 id="generating-components-code">Generating Components Code</h3>

<p>As Penpot’s components are just web-ready code, one of the greatest benefits of using it is how easily you can export code for your components. This feature, like all of Penpot’s capabilities, is completely free.</p>

<p>Using Penpot’s Inspect panel, you can quickly grab all the layout properties and styles as well as the full code snippets for all components.</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/984175216"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Code inspect</figcaption>
	
</figure>

<h3 id="documentation-and-annotations">Documentation And Annotations</h3>

<p>To make design systems in Penpot even more maintainable, it includes annotation features to help you document your components. This is crucial for maintaining a <strong>clear design system</strong> and ensuring a <strong>smooth handoff to developers</strong>.</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/984175899"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
		<figcaption>Annotations</figcaption>
	
</figure>

<h2 id="summary">Summary</h2>

<p>Penpot’s implementation of components and its support for real CSS layouts make it a standout tool for designers who want to work closely with developers. By embracing web standards and providing powerful, flexible components, Penpot enables designers to create <strong>more developer-friendly designs without sacrificing creativity or control</strong>.</p>

<p>All of Penpot’s features are completely free for both designers and developers. As open-source software, Penpot lets you fully own your design tool experience and makes it accessible for everyone, regardless of team size and budget.</p>

<p>Ready to dive in? You can explore <a href="https://github.com/mikolajdobrucki/penpot-files/raw/89612cbbd00bea5eedcee4bf625ffb07085c8973/Build%20design%20systems%20with%20Penpot%20components.penpot">the file used in this article</a> by downloading it and importing into your Penpot account.</p>

<p>As the design tool landscape continues to evolve, Penpot is taking charge of bringing designers and developers closer together. Whether you’re a designer looking to understand the development process or a developer seeking to streamline your workflow with designers, Penpot’s component system is worth exploring.</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>Vitaly Friedman</author><title>Decision Trees For UI Components</title><link>https://www.smashingmagazine.com/2024/05/decision-trees-ui-components/</link><pubDate>Wed, 29 May 2024 18:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/05/decision-trees-ui-components/</guid><description>Imagine finally resolving never-ending discussions about UI decisions for good. Here are some practical examples of decision trees for UI components and how to use them effectively. An upcoming part of &lt;a href="https://smart-interface-design-patterns.com">Smart Interface Design Patterns&lt;/a>.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/05/decision-trees-ui-components/" />
              <title>Decision Trees For UI Components</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Decision Trees For UI Components</h1>
                  
                    
                    <address>Vitaly Friedman</address>
                  
                  <time datetime="2024-05-29T18:00:00&#43;00:00" class="op-published">2024-05-29T18:00:00+00:00</time>
                  <time datetime="2024-05-29T18:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>How do you know what UI component to choose? <strong>Decision trees</strong> offer a systematic approach for design teams to document their design decisions. Once we’ve decided what UI components we use and when, we can avoid never-ending discussions, confusion, and misunderstanding.</p>

<p>Let’s explore a few examples of <strong>decision trees for UI components</strong> and how we can get the most out of them.</p>

<style>.course-intro{--shadow-color:206deg 31% 60%;background-color:#eaf6ff;border:1px solid #ecf4ff;box-shadow:0 .5px .6px hsl(var(--shadow-color) / .36),0 1.7px 1.9px -.8px hsl(var(--shadow-color) / .36),0 4.2px 4.7px -1.7px hsl(var(--shadow-color) / .36),.1px 10.3px 11.6px -2.5px hsl(var(--shadow-color) / .36);border-radius:11px;padding:1.35rem 1.65rem}@media (prefers-color-scheme:dark){.course-intro{--shadow-color:199deg 63% 6%;border-color:var(--block-separator-color,#244654);background-color:var(--accent-box-color,#19313c)}}</style>

<p class="course-intro">This article is <strong>part of our ongoing series</strong> on <a href="/category/design-patterns">design patterns</a>. It’s also an upcoming part of the 10h-video library on <a style="font-weight:700" href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns</a>&nbsp;🍣 and the <a href="https://smashingconf.com/online-workshops/workshops/interface-design-course-vitaly-friedman/">upcoming live UX training</a> as well. Use code <a style="text-decoration:none !important" href="https://smart-interface-design-patterns.com/"><kbd>BIRDIE</kbd></a> to save 15% off.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://oxygen.doctolib.design/60b411768/p/70925f-choosing-form-components">
    
    <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/decision-trees-ui-components/decision-trees-ui-components.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components.jpeg"
			
			sizes="100vw"
			alt="Decision trees for UI components"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A fantastic example of a form design component decision trees comes from the <a href='https://canvas.workday.com/'>Doctolib team</a>. (<a href='https://oxygen.doctolib.design/60b411768/p/70925f-choosing-form-components'>Image source</a>) (<a href='https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="b2b-navigation-and-help-components-doctolib">B2B Navigation and Help Components: Doctolib</h2>

<p><a href="https://oxygen.doctolib.design/60b411768/p/77fd2d-oxygen">Doctolib Design System</a> is a very impressive design system with decision trees, B2B navigation paths, photography, PIN input, UX writing, and SMS notifications &mdash; and thorough guides on <strong>how to choose UI components</strong>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://oxygen.doctolib.design/60b411768/p/962135-b2b-navigation-patterns">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="581"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png"
			
			sizes="100vw"
			alt="Onboarding selection UI toolkit"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      One of the many <a href='https://oxygen.doctolib.design/60b411768/p/962135-b2b-navigation-patterns'>decision trees on Doctolib</a>: from B2B navigation to help components. (<a href='https://files.smashing.media/articles/decision-trees-ui-components/onboarding-selection-ui-toolkit.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ul>
<li><a href="https://oxygen.doctolib.design/60b411768/p/962135-b2b-navigation-patterns">B2B Navigation Patterns Decision Tree</a></li>
<li><a href="https://oxygen.doctolib.design/60b411768/p/70925f-choosing-form-components">Form Components Decision Tree</a></li>
<li><a href="https://oxygen.doctolib.design/60b411768/p/7334c9-choosing-actions">Actions and Calls To Actions Decision Tree</a></li>
<li><a href="https://oxygen.doctolib.design/60b411768/p/8918c1-designing-better-errors/b/20a65e">Error Design Decision Tree</a></li>
<li><a href="https://oxygen.doctolib.design/60b411768/p/704279-choosing-a-help-component">Help Component Decision Tree</a></li>
</ul>

<p>I love how practical these decision trees are. Each shows an example of what a component looks like, but I would also add <strong>references to real-life UI examples and flows</strong> of where and how these components are used. A fantastic starting point that documents design decisions better than any guide would.</p>

<h2 id="decision-trees-for-ui-components-workday">Decision Trees For UI Components: Workday</h2>

<p>The team behind <a href="https://canvas.workday.com/">Workday’s Canvas design system</a> created a fantastic set of design decision trees for notifications, errors and alerts, loading patterns, calls to action, truncation, and overflow &mdash; with <strong>guidelines, examples, and use cases</strong>, which can now only be retrieved from the archive:</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://web.archive.org/web/20230629075654im_/https://canvas.workday.com/static/ed85f63d5aa360f3db4328c1c586519b/ae94e/button-decision-tree.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="422"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png"
			
			sizes="100vw"
			alt="A decision tree for notifications"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A decision tree for <a href='https://web.archive.org/web/20240118144833/https://canvas.workday.com/patterns/notifications/#tab=usage'>notifications</a>: it’s not as straightforward as one might think. (<a href='https://files.smashing.media/articles/decision-trees-ui-components/decision-trees-ui-components-workday.png'>Large preview</a>)
    </figcaption>
  
</figure>

<ul>
<li><a href="https://web.archive.org/web/20240118144833/https://canvas.workday.com/patterns/notifications/#tab=usage">Notifications Decision Tree</a></li>
<li><a href="https://web.archive.org/web/20240229153559/https://canvas.workday.com/patterns/errors-and-alerts/#tab=usage">Errors and Alerts Decision Tree</a></li>
<li><a href="https://web.archive.org/web/20240118144613/https://canvas.workday.com/patterns/loading/#tab=usage">Loading UX Decision Tree</a></li>
<li><a href="https://web.archive.org/web/20240118144311/https://canvas.workday.com/patterns/calls-to-action/#tab=usage">Calls to Action Decision Tree</a></li>
<li><a href="https://web.archive.org/web/20240118144316/https://canvas.workday.com/patterns/overflow/#tab=usage">Truncation and Overflow Decision Tree</a></li>
</ul>

<p>For each decision tree, the Workday team has put together a few <strong>context-related questions</strong> to consider first when making a decision before even jumping into the decision tree. Plus, there are thorough examples for each option available, as well as a very detailed alternative text for every image.</p>

<h2 id="form-components-decision-tree-lyft">Form Components Decision Tree: Lyft</h2>

<p>A choice of a <strong>form component</strong> can often be daunting. When should you use radio buttons, checkboxes, or dropdowns? Runi Goswami from Lyft has <a href="https://medium.com/tap-to-dismiss/a-better-segmented-control-9e662de2ef57">shared</a> a detailed form components decision tree that helps their team choose between form controls.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="535"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png"
			
			sizes="100vw"
			alt="A detailed form components decision tree"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A decision tree for <a href='https://medium.com/tap-to-dismiss/a-better-segmented-control-9e662de2ef57'>form controls</a>: notably, use dropdown as a method of last resort, with many long options. (<a href='https://files.smashing.media/articles/decision-trees-ui-components/decision-tree-form-controls.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>We start by exploring whether a user can select more than one option in our UI. If it’s indeed multi-select, we use toggles for short options and checkboxes for longer ones.</p>

<p>If only one option can be selected, then we use tabs for filtering, <strong>radios for shorter options</strong>, a switch for immediately applicable options, and a checkbox if only one option can be selected. Dropdowns are used as a last resort.</p>

<h2 id="choosing-onboarding-components-newskit">Choosing Onboarding Components: NewsKit</h2>

<p>Onboarding comes in various forms and shapes. Depending on how <strong>subtle or prominent</strong> we want to highlight a particular feature, we can use popovers, badges, hints, flags, toasts, feature cards, or design a better empty state. The Newskit team has put together an <a href="https://www.figma.com/community/file/1154730991789332817/choosing-onboarding-methods-and-components">Onboarding Selection Prototype in Figma</a>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://www.figma.com/community/file/1154728777780695374">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="564"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png"
			
			sizes="100vw"
			alt="A decision toolkit for onboarding UX"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      <a href='https://www.figma.com/community/file/1154728777780695374'>A decision toolkit for onboarding UX</a>: the more integrated the onboarding is, the more effective it is. (<a href='https://files.smashing.media/articles/decision-trees-ui-components/decision-toolkit-onboarding-ux.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The choice depends on whether we want to <strong>interrupt</strong> the users to display details (usually isn’t very effective), <strong>show</strong> a feature subtly during the experience (more effective), or <strong>enable discovery</strong> by highlighting a feature within the context of a task a user tries to accomplish.</p>

<p>The toolkit asks a designer a couple of questions about the <strong>intent of onboarding</strong>, and then suggests options that are likely to perform best &mdash; a fantastic little helper for streamlined onboarding decisions.</p>

<h2 id="design-system-process-flowcharts-nucleus">Design System Process Flowcharts: Nucleus</h2>

<p>How do you decide to add a new component to a design system or rather extend an existing one? What’s the process for contributions, maintenance, and the overall design process? Some design teams <strong>codify their design decisions</strong> as design system process flowcharts, as shown below.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://britishgas.design/guidelines/process/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="476"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg"
			
			sizes="100vw"
			alt="A design system maintenance process flowchart"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The design system maintenance process by <a href='https://britishgas.design/guidelines/process/'>British Gas design system</a>. (<a href='https://files.smashing.media/articles/decision-trees-ui-components/design-system-maintenance-process.jpeg'>Large preview</a>)
    </figcaption>
  
</figure>

<ul>
<li><a href="https://britishgas.design/guidelines/process/">Contribution Process at British Gas</a></li>
<li><a href="https://nordhealth.design/contributing/">Contributing Guidelines at Nordhealth</a></li>
<li><a href="https://standards.aviva.com/ion/start-your-project/processes.html">Processes at Aviva</a></li>
<li><a href="https://docs.opencollective.com/help/contributing/design/contribution-guidelines">Contribution Process at OpenCollective</a></li>
<li><a href="https://medium.com/zalando-design/zalandos-design-system-contribution-model-73ab36f8591e">Contribution Process at Zalando</a></li>
</ul>

<p>And here are helpful decision trees for <strong>adding new components</strong> to a design system:</p>

<ul>
<li><a href="https://miro.com/app/board/uXjVNZBdYuA=/">New Component Decision Tree at Boston Scientific</a></li>
<li><a href="https://primer.style/guides/contribute/handling-new-patterns">Handling New Patterns at GitHub</a></li>
<li><a href="https://bradfrost.com/blog/post/a-design-system-governance-process/">Design System Governance Process</a> by Brad Frost</li>
<li><a href="https://www.figma.com/community/file/989121626983620108/new-component-decision-tree">New Component Decision Tree</a> by Louis Ouriach</li>
<li><a href="https://www.figma.com/community/file/1026922247130382011">Design System Contribution Template</a> by Chad Bergman</li>
<li><a href="https://medium.com/razorpay-design/behind-the-scenes-of-designing-a-design-system-component-7969636fabf4">How To Launch A New Component</a> + <a href="https://www.figma.com/community/file/1042406919054488163">Figma template</a> by Rama Krushna Behera</li>
</ul>

<h2 id="make-decision-trees-visible">Make Decision Trees Visible</h2>

<p>What I absolutely love about the decision tree approach is not only that it beautifully visualizes design decisions but that it also serves as a <strong>documentation</strong>. It establishes shared standards across teams and includes examples to follow, with incredible value for new hires.</p>

<p>Of course, exceptions happen. But once you have codified the ways of working for design teams as a decision tree and made it front and center of your design work, it <strong>resolves never-ending discussions</strong> about UI decisions for good.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://primer.style/guides/contribute/handling-new-patterns/">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="640"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg"
			
			sizes="100vw"
			alt="Handling new patterns at Github"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The entire process at GitHub summarized as a flowchart. (<a href='https://files.smashing.media/articles/decision-trees-ui-components/handling-new-patterns-github.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>So whenever a debate comes up, document your decisions in a decision tree. Turn them into posters. Place them in kitchen areas and developer’s and QA workspaces. Put them in design critique rooms. <strong>Make them visible</strong> where design work happens and where code is being written.</p>

<p>It’s worth mentioning that every project will need its own custom trees, so please see the examples above as an <strong>idea to build upon</strong> and customize away for your needs.</p>

<h2 id="meet-smart-interface-design-patterns">Meet Smart Interface Design Patterns</h2>

<p>If you are interested in similar insights around UX, take a look at <a href="https://smart-interface-design-patterns.com/"><strong>Smart Interface Design Patterns</strong></a>, our <strong>10h-video course</strong> with 100s of practical examples from real-life projects &mdash; with a live UX training later this year. Everything from mega-dropdowns to complex enterprise tables &mdash; with 5 new segments added every year. <a href="https://www.youtube.com/watch?v=aSP5oR9g-ss">Jump to a free preview</a>.</p>

<figure style="margin-bottom: 0"><a href="https://smart-interface-design-patterns.com/"><img style="border-radius: 11px" decoding="async" fetchpriority="low" width="950" height="492" srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 400w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 800w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 1200w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 1600w,
https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg 2000w" src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/7cc4e1de-6921-474e-a3fb-db4789fc13dd/b4024b60-e627-177d-8bff-28441f810462.jpeg" sizes="100vw" alt="Smart Interface Design Patterns"></a><figcaption class="op-vertical-bottom">Meet <a href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns</a>, our video course on interface design &amp; UX.</figcaption></figure>

<div class="btn--lined btn--lined--white-border" style="margin-top: 0.75em; margin-bottom: 0.75em"><a class="btn btn--large btn--green btn--text-shadow" href="https://smart-interface-design-patterns.com/">Jump to the video course&nbsp;&rarr;</a></div>

<p><p class="ticket-price__desc" style="font-size: .8em!important; text-align: center; line-height: 1.5; margin: 0; display: block;">100 design patterns &amp; real-life
examples.<br>10h-video course + live UX training. <a href="https://www.youtube.com/watch?v=aSP5oR9g-ss">Free preview</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>(yk)</span>
</div>


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Mikołaj Dobrucki</author><title>Penpot’s CSS Grid Layout: Designing With Superpowers</title><link>https://www.smashingmagazine.com/2024/04/penpot-css-grid-layout-designing-superpowers/</link><pubDate>Thu, 11 Apr 2024 08:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/04/penpot-css-grid-layout-designing-superpowers/</guid><description>Penpot helps designers and developers work better together by offering a free, open-source design tool based on open web standards. Today, let&amp;rsquo;s explore Penpot’s latest feature, CSS Grid Layout. Penpot’s latest release is about efficiency and so much more. It gives designers superpowers and a better place at the table. Excited? Let’s take a look at it together.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/04/penpot-css-grid-layout-designing-superpowers/" />
              <title>Penpot’s CSS Grid Layout: Designing With Superpowers</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>Penpot’s CSS Grid Layout: Designing With Superpowers</h1>
                  
                    
                    <address>Mikołaj Dobrucki</address>
                  
                  <time datetime="2024-04-11T08:00:00&#43;00:00" class="op-published">2024-04-11T08:00:00+00:00</time>
                  <time datetime="2024-04-11T08:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                <p>This article is sponsored by <b>Penpot</b></p>
                

<p>It was less than a year ago when <a href="https://www.smashingmagazine.com/2023/02/meet-penpot-open-source-design-platform-designers-developers/">I first had a chance to use Penpot</a> and instantly got excited about it. They managed to build something that designers haven’t yet seen before &mdash; a modern, open-source tool for everyone. In the world of technology, that might not sound groundbreaking. After all, open-source tools and software are being taken for granted as a cornerstone of modern web development. But for some reason, not for design &mdash; until now. Penpot’s approach to building design software comes with a lot of good arguments. And it gathered a strong community.</p>

<p>One of the reasons why Penpot is so exciting is that it <strong>allows creators to build user interfaces in a visual environment</strong>, but using the same standards and technologies as the end product. It makes a design workflow easier on many levels. Today, we are going to focus on just one of them, building layouts.</p>

<p>Design tools went a long way trying to make it easier to design complex, responsive layouts and flexible, customizable components. Some of them tried to mimic the mechanisms used in web technologies and others tried to mimic these imitations. But such an approach will take you only so far.</p>

<h2 id="short-history-of-web-layouts">Short History Of Web Layouts</h2>

<p>So how are the layouts for the web built in practice?</p>

<p>If you’ve been around the industry long enough, you might remember the times when you used frames, tables, and floats to build layouts. And if you haven’t, you didn’t miss much. Just to give you a taste of how bad it was: same as exporting tiny images of rounded corners from ever-crashing Photoshop, just to meticulously position them in every corner of a rectangle so you could make a dull, rounded button, it was just a pain. Far too often, it was a pleasure to craft yet another amazing design &mdash; but so much tears and sorrow to actually implement it.</p>

<p>Then Flexbox came in and changed everything. And soon after it, Grid. Two powerful yet amazingly simple engines to build layouts that changed web developers’ lives forever.</p>

<p>Ironically, design tools never caught up. Flexbox and Grid opened an ocean of possibilities, yet gated behind a barrier of knowing how to code. None of the design tools ever implemented them so a larger audience of designers could leverage them in their workflows. Not until now.</p>

<h2 id="creating-layouts-with-penpot">Creating Layouts With Penpot</h2>

<p><a href="https://penpot.app/?utm_source=Article&amp;utm_medium=SmashingMag&amp;utm_id=Penpot2.0">Penpot</a> is becoming the first design tool to support both Flexbox and Grid in their toolkit. And by support, I don’t mean a layout feature that tries to copy what Flexbox or Grid has to offer. We’re talking about an actual implementation of Flexbox and Grid inside the design tool.</p>

<p>Penpot’s Flexbox implementation went public earlier this year. If you’d like to give it a try, last year, <a href="https://www.smashingmagazine.com/2023/06/penpot-flex-layout-building-css-layouts-design-tool/">I wrote a separate article just about it</a>. Now, <strong>Penpot is fully implementing both Flexbox and Grid</strong>.</p>

<p>You might be wondering why we need both. Couldn’t you just use Flexbox for everything, same as you use the same simple layout features in a design tool? Technically, yes, you could. In fact, most people do. (At the time of writing, only a quarter of websites worldwide use CSS Grid, but its adoption is steadily increasing; <a href="https://chromestatus.com/metrics/feature/timeline/popularity/1693">source</a>)</p>

<p>So if you want to build simple, mostly linear layouts, Flexbox is probably all you’ll ever need. But if you want to gain some design superpowers? Learn Grid.</p>

<p>Penpot’s CSS Grid Layout is out now, so you can already <a href="https://penpot.app/penpot-2.0?utm_source=Article&amp;utm_medium=SmashingMag&amp;utm_id=Penpot2.0">give it a try</a>. It’s a part of their major 2.0 release, bringing a bunch of long-awaited features and improvements. I’d strongly encourage you to give it a go and see how it works for yourself. And if you need some inspiration, keep reading!</p>

<h2 id="css-grid-layout-in-practice">CSS Grid Layout In Practice</h2>

<p>As an example, let’s build a portfolio page that consists of a sidebar and a grid of pictures.</p>

<h3 id="creating-a-layout">Creating A Layout</h3>

<p>Our first step will be to create a simple two-dimensional grid. In this case using a Grid Layout makes more sense than Flex Layout as we want to have more granular control over how elements are laid out on multiple axes.</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/933631600"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>You can notice that each row and column of the layout has a value of “1FR”. FR stands for fraction, which means that the available space will be distributed evenly across rows and columns.</p>

<p>FRs are extremely useful. For example, the same as other units, FRs can take different values. So you could create 3 columns, one taking 2FRs and two counting 1FR each. As a result, the first column would occupy half of the layout’s width and the other two would take a quarter of available space each.</p>

<h3 id="adding-elements-to-the-layout">Adding Elements To The Layout</h3>

<p>To add elements to CSS Grid Layout, you can simply drag and drop them onto the canvas.</p>

<p>For each element you can either assign it to a dedicated cell of the grid or allow it to find its place on its own and fill the first available blank.</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/933631629"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<h3 id="spacing-and-alignment">Spacing And Alignment</h3>

<p>CSS Grid Layout gives you full control over how the elements are aligned and how they adjust to available space. A plethora of highly granular options allows you to create very precise, responsive layouts that work seamlessly with elements of any shape or form.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/ 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/ 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/ 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/ 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/ 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/"
			
			sizes="100vw"
			alt=""
		/>
    

  
    <figcaption class="op-vertical-bottom">
      (Image credit: <a href=''></a>) (<a href=''>Large preview</a>)
    </figcaption>
  
</figure>


<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/933631648"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>You can make cells and rows adjust to the size of the elements but you can also make the elements adjust to the grid.  In this case, we are going to add a sidebar on the left side of the layout and place it in a column with a fixed width of 320px.</p>

<p>The sidebar itself has its own layout. But in this case, for the simple vertical alignment, the Flex Layout is all we need.</p>

<h3 id="creating-grid-areas">Creating Grid Areas</h3>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/ 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/ 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/ 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/ 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/ 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/"
			
			sizes="100vw"
			alt=""
		/>
    

  
    <figcaption class="op-vertical-bottom">
      (Image credit: <a href=''></a>) (<a href=''>Large preview</a>)
    </figcaption>
  
</figure>


<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/933631684"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>To make the grid even more powerful, you can merge cells, group them into functional areas, and name them. Here, we are going to create a dedicated area for the sidebar.</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/933631711"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>As you adjust the layout later, you can see that the sidebar always keeps the same width and full height of the design while other cells get adjusted to the available space.</p>

<h3 id="building-even-more-complex-grids">Building Even More Complex Grids</h3>

<p>That’s not all. Apart from merging cells of the grid, you can tell elements inside it to take multiple cells. On our portfolio page, we are going to use this to make the featured picture bigger than others and take four cells instead of one.</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/933631747"
        frameborder="0"
        allow="autoplay; fullscreen; picture-in-picture"
        allowfullscreen>
    </iframe>
	</div>
	
</figure>

<p>As a result, we created a complex, responsive layout that would be a breeze to turn it into a functional website but at the same time would be completely impossible to build in any other design tool out there. And that’s just a fraction of what Grid Layout can do.</p>

<h2 id="next-steps">Next Steps</h2>

<p>I hope you liked this demo of Penpot&rsquo;s Grid Layout. If you’d like to play around with the examples used in this article, go ahead and <a href="https://github.com/penpot/penpot-files/raw/main/Grid%20layout%20playground.penpot">duplicate this Penpot file</a>. It’s a great template that explains all the ins and outs of using Grid in your designs!</p>

<p>In case you’re more of a video-learning type, there’s a great tutorial on Grid Layout you can <a href="https://www.youtube.com/watch?v=4wFwffbFb44">watch now on YouTube</a>. And if you need help at any point, the Penpot community will be more than happy to answer your questions.</p>

<h2 id="summary">Summary</h2>

<p>Flexbox and Grid in Penpot open up opportunities to craft layouts like never before. Today, anyone can combine the power of Flex Layout and Grid Layout to create complex, sophisticated structures that are flexible, responsive, and ready to deploy out-of-the-box—all without writing a single line of code.</p>

<p>Working with the right technologies not only makes things easier, but it also just feels right. That&rsquo;s something I&rsquo;ve always longed for in design tools. Adopting CSS as a standard for both designers and developers facilitates smoother collaboration and helps them both feel more at home in their workflows.</p>

<p>For designers, that’s also a chance to strengthen their skill set, which matters today more than ever. The design industry is a competitive space that keeps changing rapidly, and staying competitive is hard work. However, learning the less obvious aspects and gaining a better understanding of the technologies you work with might help you do that.</p>

<h2 id="try-css-grid-layout-and-share-your-thoughts">Try CSS Grid Layout And Share Your Thoughts!</h2>

<p>If you decide to <a href="https://penpot.app/penpot-2.0?utm_source=Article&amp;utm_medium=SmashingMag&amp;utm_id=Penpot2.0">give CSS Grid Layout a try</a>, don’t hesitate to share your experience! The team behind Penpot would love to hear your feedback. Being a completely <strong>free and open-source tool</strong>, Penpot’s development thrives thanks to its community and people like you.</p>

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


              </article>
            </body>
          </html>
        ]]></content:encoded></item><item><author>Preethi Sam</author><title>The Things Users Would Appreciate In Mobile Apps</title><link>https://www.smashingmagazine.com/2024/04/things-users-would-appreciate-mobile-apps/</link><pubDate>Fri, 05 Apr 2024 12:00:00 +0000</pubDate><guid>https://www.smashingmagazine.com/2024/04/things-users-would-appreciate-mobile-apps/</guid><description>What can we do to make a mobile app better? What subsidiary features are worth providing for our users? I have some ideas. You might, too. So, let’s compare our notes. Without any prescriptions attached, here are seven features I believe can palpably improve a user’s experience with a mobile app.</description><content:encoded><![CDATA[
          <html>
            <head>
              <meta charset="utf-8">
              <link rel="canonical" href="https://www.smashingmagazine.com/2024/04/things-users-would-appreciate-mobile-apps/" />
              <title>The Things Users Would Appreciate In Mobile Apps</title>
            </head>
            <body>
              <article>
                <header>
                  <h1>The Things Users Would Appreciate In Mobile Apps</h1>
                  
                    
                    <address>Preethi Sam</address>
                  
                  <time datetime="2024-04-05T12:00:00&#43;00:00" class="op-published">2024-04-05T12:00:00+00:00</time>
                  <time datetime="2024-04-05T12:00:00&#43;00:00" class="op-modified">2026-05-30T06:46:31+00:00</time>
                </header>
                
                

<p>Remember the “mobile first” mantra? The idea was born out of the early days of responsive web design. Rather than design and build for the “desktop” up front, a “mobile-first” approach treats small screens as first-class citizens. There’s a reduced amount of real estate, certainly less than the number of pixels we get from the viewport of Firefox expanded fullscreen on a 27-inch studio monitor.</p>

<p>The constraint is a challenge to make sure that <strong>whatever is sent to mobile devices is directly relevant to what users should need</strong>; nothing more, nothing less. Anything more additive to the UI can be reserved for wider screens where we’re allowed to stretch out and make more liberal use of space.</p>

<div class="break-out">
<pre><code class="language-css">/&#42; A sample CSS snippet for a responsive main content &#42;/

/&#42; Base Styles &#42;/
.main-content {
  container: main / inline-size;
}

.gallery {
  display: grid;
  gap: 1rem;
}

/&#42; Container is wider than or equal to 700px &#42;/
@container main (width &gt;= 700px) {
  .gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/&#42; Container is wider than or equal to 1200px &#42;/
@container main (width &gt;= 1200px) {
  .gallery {
    grid-template-columns: repeat(4, 1fr);
  }
}
</code></pre>
</div>

<p>Now, I’m not here to admonish anyone who isn’t using a mobile-first approach when designing and building web interfaces. If anything, the last five or so years <a href="https://www.youtube.com/watch?v=aHUtMbJw8iA">have shown us just how unpredictable of a medium the web is</a>, including what sort of device is displaying our work all the way down to a user’s individual preferences.</p>

<p>Even so, there are things that any designer and developer should consider when working with mobile interfaces. Now that we’re nearly 15 years into responsive web design as a practice and craft, users are beginning to form opinions about what to expect when trying to do certain things in a mobile interface. I know that I have expectations. I am sure you have them as well.</p>

<p>I’ve been keeping track of the mobile interfaces I use and have started finding common patterns that <em>feel</em> mobile in nature but are more desktop-focused in practice. While keeping track of the mobile interfaces I use, I’ve found common patterns that are unsuitable for small screens and thus could use some updates. Here are some reworked features that are worth considering for mobile interfaces.</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="/printed-books/typescript-in-50-lessons/">“TypeScript in 50 Lessons”</a></strong>, our shiny new guide to TypeScript. With detailed <strong>code walkthroughs</strong>, hands-on examples and common gotchas. For developers who know enough <strong>JavaScript</strong> to be dangerous.</p>
<a data-instant href="/printed-books/typescript-in-50-lessons/" class="btn btn--green btn--large" style="">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="/printed-books/typescript-in-50-lessons/" class="feature-panel-image-link">
<div class="feature-panel-image"><picture><source type="image/avif" srcSet="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/2732dfe9-e1ee-41c3-871a-6252aeda741c/typescript-panel.avif" />
<img
    loading="lazy"
    decoding="async"
    class="feature-panel-image-img"
    src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/c2f2c6d6-4e85-449a-99f5-58bd053bc846/typescript-shop-cover-opt.png"
    alt="Feature Panel"
    width="481"
    height="698"
/>
</picture>
</div>
</a>
</div>
</aside>
</div>

<h2 id="economically-sized-forms">Economically-Sized Forms</h2>

<p>There are myriad problems that come up while completing mobile forms &mdash; e.g., small tap targets, lack of offline support, and incorrect virtual keyboards, to name a few &mdash; but it’s how a mobile form interacts with the device’s virtual keyboard that draws my ire the most.</p>

<p>The keyboard obscures the form more times than not. You tap a form field, and the keyboard slides up, and &mdash; <em>poof!</em> &mdash; it’s as though half the form is chopped out of view. If you’re thinking, <em>Meh, all that means is a little extra scrolling</em>, consider that scrolling isn’t always a choice. If the page is a short one with only the form on it, it’s highly possible what you see on the screen is what you get.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="448"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png"
			
			sizes="100vw"
			alt="Mobile form on an iPhone with a text field in focus and the virtual keyboard obscuring the next form fields."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Tap a field, and things get both zoomed in and chopped off. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/1-mobile-form-iphone-text-field.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>A more delightful user experience for mobile forms is to take a “less is more” approach. <strong>Display one form field at a time for an economical layout</strong> that allows the field and virtual keyboard to co-exist in harmony without any visual obstructions. Focusing the design on the top half of the viewport with room for navigation controls and microcopy creates a seamless flow from one form input to the next.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg">
    
    <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/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg"
			
			sizes="100vw"
			alt="A whole page for entering email details alone."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A whole page for entering email details alone. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/2-whole-page-entering-email-details.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="more-room-for-searching">More Room For Searching</h2>

<p>Search presents a dichotomy: It is incredibly useful, yet is treated as an afterthought, likely tucked in the upper-right corner of a global header, out of view and often further buried by hiding the form input until the user clicks some icon, typically a magnifying glass. (It’s ironic we minimize the UI with a magnifying glass, isn’t it?)</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="284"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg"
			
			sizes="100vw"
			alt="Mobile header of a site with a hamburger menu labeled Menu on the left and an icon that combines a magnifying glass with a hamburger menu labeled Resources."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      In this example, combining iconography convolutes the meaning of what the component is and does. The label also fails to accurately identify the feature. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/3-mobile-header-site.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The problem with burying search in a mobile context is two-fold:</p>

<ol>
<li>The feature is less apparent, and</li>
<li>The space to enter a search query, add any filters, and display results is minimized.</li>
</ol>

<p>That may very well be acceptable if the site has only a handful of pages to navigate. However, if the search allows a user to surface relevant content and freely move about an app, then you’re going to want to give it higher prominence.</p>

<blockquote class="pull-quote">
  <p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aAny%20service-oriented%20mobile%20app%20can%20improve%20user%20experience%20by%20providing%20a%20search%20box%20that%e2%80%99s%20immediately%20recognizable%20and%20with%20enough%20breathing%20room%20for%20tapping%20a%20virtual%20keyboard.%0a&url=https://smashingmagazine.com%2f2024%2f04%2fthings-users-would-appreciate-mobile-apps%2f">
      
Any service-oriented mobile app can improve user experience by providing a search box that’s immediately recognizable and with enough breathing room for tapping a virtual keyboard.

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














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="458"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg"
			
			sizes="100vw"
			alt="Smashing Magazine homepage in mobile view. The left side captures the site’s search form in its initial state. The right side shows the search form in its focused state, revealing the virtual keyboard."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Smashing Magazine homepage displayed at a mobile breakpoint. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/4-smashing-magazine-homepage-mobile.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Some sites even have search forms that occupy the full screen without surrounding components, offering a “distraction-free” interface for typing queries.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="284"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg"
			
			sizes="100vw"
			alt="Mobile header of a site with the search feature expanded to a fullscreen view, allowing the search box to use more space without clutter."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Even though the search feature in the first example was convoluted in its initial state, it is allowed to go fullscreen when it is opened, providing users with ample space for completing a search query. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/5-mobile-header-site-search-feature-expanded.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

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

<h2 id="no-drop-downs-if-possible">No Drop-Downs, If Possible</h2>

<p>The <code>&lt;select&gt;</code> element can negatively impact mobile UX in two glaring ways:</p>

<ol>
<li>An expanding <code>&lt;select&gt;</code> with so many options that it produces excessive scrolling.</li>
<li>Scrolling excessively, particularly on long pages, produces an awkward situation where the page continues scrolling after already scrolling through the list of <code>&lt;option&gt;</code>s.</li>
</ol>

<p>I’ll come across these situations, stare at my phone, and ask:</p>

<blockquote>Do I really need a <code>&lt;select&gt;</code> populated with one hundred years to submit my birthdate?</blockquote>

<p>Seems like an awful lot of trouble to sort through one hundred years compared to manually typing in a four-digit value myself.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg">
    
    <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/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg"
			
			sizes="100vw"
			alt="Mockup of a mobile form with name, date, and email fields. The date field is expanded with options running vertically off the screen"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      A <code>&lt;select&gt;</code> menu that contains a large list of options can lead to awkward scrolling situations. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/6-mockup-mobile-form.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>A better pattern for making list selections, if absolutely needed, in a space-constrained mobile layout could be something closer to the ones used by iOS and Android devices by default. Sure, there’s scrolling involved, but within a confined space that leaves the rest of the UI alone.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg">
    
    <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/things-users-would-appreciate-mobile-apps/7-datepicker.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg"
			
			sizes="100vw"
			alt="Datepicker following a similar pattern to iOS with scrolling fields instead of a dropdown."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/7-datepicker.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="a-restrained-overview">A Restrained Overview</h2>

<p>A dashboard overview in a mobile app is something that displays key data to users right off the bat, surfacing common information that the user would otherwise have to seek out. There are great use cases for dashboards, many of which you likely interact with daily, like your banking app, a project management system, or even a page showing today’s baseball scores. The WordPress dashboard is a perfect example, showing site activity, security checks, traffic, and more.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png">
    
    <img
      loading="lazy"
      decoding="async"
      fetchpriority="low"
			width="800"
			height="551"
			
			srcset="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png"
			
			sizes="100vw"
			alt="WordPress administrative dashboard with two columns of widgets displaying site activity."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The WordPress administrative dashboard displays a summary of various site activities at a glance. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/8-wordpress-administrative-dashboard.png'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Dashboards are just fine. But the sensitive information they might contain is what worries me when I’m viewing a dashboard on a mobile device.</p>

<p>Let’s say I’m having dinner with friends at a restaurant. We split the check. To pay my fair share, I take out my phone and log into my banking app, and… the home screen displays my bank balance in big, bold numbers to the friends sitting right next to me, one of whom is the gossipiest of the bunch. There goes a bit of my pride.</p>

<p>That’s an extreme illustration because not all apps convey that level of sensitive information. But many do, and the care we put into protecting a user’s information from peeping eyeballs is only becoming more important as entire industries, like health care and education, lean more heavily into online experiences.</p>

<p><strong>My advice</strong>: <em>Hide sensitive information until prompted by the user to display it.</em></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%20generally%20a%20good%20practice%20to%20obscure%20sensitive%20information%20and%20have%20a%20liberal%20definition%20of%20what%20constitutes%20sensitive%20information.%0a&url=https://smashingmagazine.com%2f2024%2f04%2fthings-users-would-appreciate-mobile-apps%2f">
      
It’s generally a good practice to obscure sensitive information and have a liberal definition of what constitutes sensitive information.

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














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg">
    
    <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/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg"
			
			sizes="100vw"
			alt="Bank app mockup showing an account overview with a bank balance hidden behind a ‘Tap here to view’ prompt."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Hiding sensitive information behind a user prompt is an effective approach to protect it from onlookers while continuing to provide convenient access. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/9-bank-app-mockup.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="shortcuts-provided-in-the-login-flow">Shortcuts Provided In The Login Flow</h2>

<p>There’s a natural order to things, particularly when logging into a web app. We log in, see a welcome message, and are taken to a dashboard before we tap on some item that triggers some sort of action to perform. In other words, it takes a few steps and walking through a couple of doors to get to accomplish what we came to do.</p>

<p>What if we put actions earlier in the login flow? As in, they are displayed right along with the login form. This is what we call a <strong>shortcut</strong>.</p>

<p>Let’s take the same restaurant example from earlier, where I’m back at dinner and ready to pay. This time, however, I will open a different bank app. This one has shortcuts next to the login form, one of which is a “Scan to Pay” option. I tap it, log in, and am taken straight to the scanning feature that opens the camera on my device, completely bypassing any superfluous welcome messaging or dashboard. This spares the user both time and effort (and prevents sensitive information from leaking into view to boot).</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg">
    
    <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/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg"
			
			sizes="100vw"
			alt="Bank screen mockup with a row of shortcut actions represented by icons below a login form."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Signing in can take a more personalized path by offering shortcuts. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/10-mobile-ux-bank-mockup.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>Mobile operating systems also provide options for shortcuts when long-pressing an app’s icon on the home screen, which also can be used to an app’s advantage.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.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/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png"
			
			sizes="100vw"
			alt="WhatsApp icon next to an open contextual menu with options to search, chat, scan codes, and share."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      It may be possible to leverage an operating system’s default controls, like the long-press feature in iOS. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/11-whatsapp-icon-open-contextual-menu.png'>Large preview</a>)
    </figcaption>
  
</figure>

<h2 id="the-right-keyboard-configuration">The Right Keyboard Configuration</h2>

<p>All modern mobile operating systems are smart enough to tailor the virtual keyboard for specialized form inputs. A form field markup with <code>type=&quot;email&quot;</code>, for instance, triggers an onscreen keyboard that shows the “<code>@</code>&rdquo; key in the primary view, preventing users from having to tap <code>Shift</code> to reveal it in a subsequent view. The same is true with URLs, where a “<code>.com</code>” key surfaces for inputs with <code>type=&quot;url&quot;</code>.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg">
    
    <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/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg"
			
			sizes="100vw"
			alt="Onscreen keyboard for an email field, with ‘@’ key shown upfront"
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      Onscreen keyboard for an email field, with <code>@</code> key shown upfront. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/12-onscreen-keyboard-email-field.jpg'>Large preview</a>)
    </figcaption>
  
</figure>

<p>The right keyboard saves the effort of hunting down relevant special characters and numbers for specific fields. All we need to do is to use the right attributes and semantics for those fields, like, <code>type=email</code>, <code>type=url</code>, <code>type=tel</code>, and such.</p>

<div class="break-out">
<pre><code class="language-html">&lt;!-- Input Types for Virtual Keyboards --&gt;
&lt;input type="text"&gt;   &lt;!-- default --&gt;
&lt;input type="tel"&gt;    &lt;!-- numeric keyboard --&gt;
&lt;input type="number"&gt; &lt;!-- numeric keyboard --&gt;
&lt;input type="email"&gt;  &lt;!-- displays @ key --&gt;
&lt;input type="url"&gt;    &lt;!-- displays .com key --&gt;
&lt;input type="search"&gt; &lt;!-- displays search button --&gt;
&lt;input type="date"&gt;   &lt;!-- displays date picker or wheel controls --&gt;
</code></pre>
</div>

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

<h2 id="bigger-fonts-with-higher-contrast">Bigger Fonts With Higher Contrast</h2>

<p>This may have been one of the first things that came to your mind when reading the article title. That’s because small text is prevalent in mobile interfaces. It’s not wrong to scale text in response to smaller screens, but where you set the lower end of the range may be too small for many users, even those with great vision.</p>

<p>The default size of body text is <code>16px</code> on the web. That’s thanks to user agent styling that’s consistent across all browsers, including those on mobile platforms. But what exactly is the ideal size for mobile? The answer is not entirely clear. For example, Apple’s <a href="https://developer.apple.com/design/human-interface-guidelines/typography">Human Interface Guidelines</a> do not specify exact font sizes but rather focus on the use of Dynamic Text that adjusts the size of content to the user’s device-level preferences. Google’s Material Design guidelines are more concrete but are based on three scales: small, medium, and large. The following table shows the minimum font sizes for each scale based on the system’s <a href="https://m3.material.io/styles/typography/type-scale-tokens">typography tokens</a>.</p>

<table class="tablesaw break-out">
    <thead>
        <tr>
            <th>Scale</th>
            <th>Body Text (pt)</th>
      <th>Body Text (px)</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Small</td>
            <td><code>12pt</code></td>
      <td><code>16px</code></td>
        </tr>
        <tr>
            <td>Medium</td>
            <td><code>14pt</code></td>
      <td><code>18.66px</code></td>
        </tr>
        <tr>
            <td>Large</td>
            <td><code>16pt</code></td>
      <td><code>21.33px</code></td>
        </tr>
    </tbody>
</table>

<p>The real standard we ought to be looking at is the current WCAG 2.2, and here’s <a href="https://www.w3.org/TR/WCAG22/#dfn-large-scale">what it says</a> on the topic:</p>

<blockquote>“When using text without specifying the font size, the smallest font size used on major browsers for unspecified text would be a reasonable size to assume for the font.”</blockquote>

<p>So, bottom line is that the bottom end of a font’s scale matches the web’s default <code>16px</code> if we accept Android’s “Small” defaults. But even then, there are caveats because WCAG is more focused on <strong>contrast</strong> than <strong>size</strong>. Like, if the font in use is thin by default, WCAG suggests bumping up the font size to produce a higher contrast ratio between the text and the background it sits against.</p>

<p>There are many, many articles that can give you summaries of various typographical guidelines and how to adhere to them for optimal font sizing. The best advice I’ve seen, however, is Eric Bailey rallying us to <a href="https://css-tricks.com/reader-mode-the-button-to-beat/">“beat the “Reader Mode” button</a>. Eric is talking more specifically about preventing clutter in an interface, but the same holds for font sizing. If the text is tiny or thin, I’m going to bash that button on your site with no hesitation.</p>














<figure class="
  
    break-out article__image
  
  
  ">
  
    <a href="https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.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/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png 400w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_800/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png 800w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1200/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png 1200w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_1600/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png 1600w,
			        https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_2000/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png 2000w"
			src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png"
			
			sizes="100vw"
			alt="The Kindle reading app on a mobile device with font sizing and style controls."
		/>
    
    </a>
  

  
    <figcaption class="op-vertical-bottom">
      The Kindle reading app on a mobile device with font sizing and style controls. (<a href='https://files.smashing.media/articles/things-users-would-appreciate-mobile-apps/13-kindle-reading-app-mobile-device.png'>Large preview</a>)
    </figcaption>
  
</figure>

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

<p>Everything we’ve covered here in this article is personal irritations I feel when interacting with different mobile interfaces. I’m sure you have your own set of annoyances, and if you do, I’d love to read them in the comments if you’re willing to share. And someone else is likely to have even more examples.</p>

<p>The point is that we’re in some kind of <strong>“post-responsive” era of web design</strong>, one that looks beyond how elements stack in response to the viewport to consider user preferences, privacy, and providing optimal experiences at any breakpoint regardless of whatever device is used.</p>

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


              </article>
            </body>
          </html>
        ]]></content:encoded></item></channel></rss>