The Modern Guide For Making CSS Shapes

You have for sure googled “how to create [shape_name] with CSS” at least once in your front-end career if it’s not something you already have bookmarked. And the number of articles and demos you will find out there is endless.

Good, right? Copy that code and drop it into the ol’ stylesheet. Ship it!

The problem is that you don’t understand how the copied code works. Sure, it got the job done, but many of the most widely used CSS shape snippets are often dated and rely on things like magic numbers to get the shapes just right. So, the next time you go into the code needing to make a change to it, it either makes little sense or is inflexible to the point that you need an entirely new solution.

So, here it is, your one-stop modern guide for how to create shapes in CSS! We are going to explore the most common CSS shapes while highlighting different CSS tricks and techniques that you can easily re-purpose for any kind of shape. The goal is not to learn how to create specific shapes but rather to understand the modern tricks that allow you to create any kind of shape you want.

Table of Contents

You can jump directly to the topic you’re interested in to find relevant shapes or browse the complete list. Enjoy!

Why Not SVG?

I get asked this question often, and my answer is always the same: Use SVG if you can! I have nothing against SVG. It’s just another approach for creating shapes using another syntax with another set of considerations. If SVG was my expertise, then I would be writing about that instead!

CSS is my field of expertise, so that’s the approach we’re covering for drawing shapes with code. Choosing CSS or SVG is typically a matter of choice. There may very well be a good reason why SVG is a better fit for your specific needs.

Many times, CSS will be your best bet for decorative things or when you’re working with a specific element in the markup that contains real content to be styled. Ultimately, though, you will need to consider what your project’s requirements are and decide whether a CSS shape is really what you are looking for.

Your First Resource

Before we start digging into code, please spend a few minutes over at my CSS Shape website. You will find many examples of CSS-only shapes. This is an ever-growing collection that I regularly maintain with new shapes and techniques. Bookmark it and use it as a reference as we make our way through this guide.

Is it fairly easy to modify and tweak the CSS for those shapes?

Yes! The CSS for each and every shape is optimized to be as flexible and efficient as possible. The CSS typically targets a single HTML element to prevent you from having to touch too much markup besides dropping the element on the page. Additionally, I make liberal use of CSS variables that allow you to modify things easily for your needs.

Most of you don't have time to grasp all the techniques and tricks to create different shapes, so an online resource with ready-to-use snippets of code can be a lifesaver!

Clipping Shapes In CSS

The CSS clip-path property — and its polygon() function — is what we commonly reach for when creating CSS Shapes. Through the creation of common CSS shapes, we will learn a few tricks that can help you create other shapes easily.

Hexagons

Let’s start with one of the easiest shapes; the hexagon. We first define the shape’s dimensions, then provide the coordinates for the six points and we are done.

.hexagon {
  width: 200px;
  aspect-ratio: 0.866; 
  clip-path: polygon(
    0% 25%,
    0% 75%,
    50% 100%, 
    100% 75%, 
    100% 25%, 
    50% 0%);
}

We’re basically drawing the shape of a diamond where two of the points are set way outside the bounds of the hexagon we’re trying to make. This is perhaps the very first lesson for drawing CSS shapes: Allow yourself to think outside the box — or at least the shape’s boundaries.

Look how much simpler the code already looks:

.hexagon {
  width: 200px;
  aspect-ratio: cos(30deg); 
  clip-path: polygon(
    -50% 50%,
    50% 100%,
    150% 50%,
    50% 0
  );
}

Did you notice that I updated the aspect-ratio property in there? I’m using a trigonometric function, cos(), to replace the magic number 0.866. The exact value of the ratio is equal to cos(30deg) (or sin(60deg)). Besides, cos(30deg) is a lot easier to remember than 0.866.

Here’s something fun we can do: swap the X and Y coordinate values. In other words, let’s change the polygon() coordinates from this pattern:

clip-path: polygon(X1 Y1, X2 Y2, ..., Xn Yn)

…to this, where the Y values come before the X values:

clip-path: polygon(Y1 X1, Y2 X2, ..., Yn Xn)

What we get is a new variation of the hexagon:

I know that visualizing the shape with outside points can be somewhat difficult because we’re practically turning the concept of clipping on its head. But with some practice, you get used to this mental model and develop muscle memory for it.

Notice that the CSS is remarkably similar to what we used to create a hexagon:

.octagon {
  width: 200px;  
  aspect-ratio: 1;  
  --o: calc(50% * tan(-22.5deg));
  clip-path: polygon(
    var(--o) 50%,
    50% var(--o),
    calc(100% - var(--o)) 50%,
    50% calc(100% - var(--o))
  );
}

Except for the small trigonometric formula, the structure of the code is identical to the last hexagon shape — set the shape’s dimensions, then clip the points. And notice how I saved the math calculation as a CSS variable to avoid repeating that code.

If math isn’t really your thing — and that’s totally fine! — remember that the formulas are simply one part of the puzzle. There’s no need to go back to your high school geometry textbooks. You can always find the formulas you need for specific shapes in my online collection. Again, that collection is your first resource for creating CSS shapes!

And, of course, we can apply this shape to an <img> element as easily as we can a <div>:

It may sound impossible to make a star out of only five points, but it’s perfectly possible, and the trick is how the points inside polygon() are ordered. If we were to draw a star with pencil on paper in a single continuous line, we would follow the following order:

It’s the same way we used to draw stars as kids — and it fits perfectly in CSS with polygon()! This is another hidden trick about clip-path with polygon(), and it leads to another key lesson for drawing CSS shapes: the lines we establish can intersect. Again, we’re sort of turning a concept on its head, even if it’s a pattern we all grew up making by hand.

Here’s how those five points translate to CSS:

.star {
  width: 200px;
aspect-ratio: 1; clip-path: polygon(50% 0, /* (1) */ calc(50%*(1 + sin(.4turn))) calc(50%*(1 - cos(.4turn))), /* (2) */ calc(50%*(1 - sin(.2turn))) calc(50%*(1 - cos(.2turn))), /* (3) */ calc(50%*(1 + sin(.2turn))) calc(50%*(1 - cos(.2turn))), /* (4) */ calc(50%*(1 - sin(.4turn))) calc(50%*(1 - cos(.4turn))) /* (5) */ ); }

The funny thing is that starbursts are basically the exact same thing as polygons, just with half the points that we can move inward.

Figure 6.

I often advise people to use my online generators for shapes like these because the clip-path coordinates can get tricky to write and calculate by hand.

That said, I really believe it’s still a very good idea to understand how the coordinates are calculated and how they affect the overall shape. I have an entire article on the topic for you to learn the nuances of calculating coordinates.

Parallelograms & Trapezoids

Another common shape we always build is a rectangle shape where we have one or two slanted sides. They have a lot of names depending on the final result (e.g., parallelogram, trapezoid, skewed rectangle, and so on), but all of them are built using the same CSS technique.

First, we start by creating a basic rectangle by linking the four corner points together:

clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%)

This code produces nothing because our element is already a rectangle. Also, note that 0 and 100% are the only values we’re using.

Next, offset some values to get the shape you want. Let’s say our offset needs to be equal to 10px. If the value is 0, we update it with 10px, and if it’s 100% we update it with calc(100% - 10px). As simple as that!

But which value do I need to update and when?

Try and see! Open your browser’s developer tools and update the values in real-time to see how the shape changes, and you will understand what points you need to update. I would lie if I told you that I write all the shapes from memory without making any mistakes. In most cases, I start with the basic rectangle, and I add or update points until I get the shape I want. Try this as a small homework exercise and create the shapes in Figure 11 by yourself. You can still find all the correct code in my online collection for reference.

If you want more CSS tricks around the clip-path property, check my article “CSS Tricks To Master The clip-path Property” which is a good follow-up to this section.

Masking Shapes In CSS

We just worked with a number of shapes that required us to figure out a number of points and clip-path by plotting their coordinates in a polygon(). In this section, we will cover circular and curvy shapes while introducing the other property you will use the most when creating CSS shapes: the mask property.

Like the previous section, we will create some shapes while highlighting the main tricks you need to know. Don’t forget that the goal is not to learn how to create specific shapes but to learn the tricks that allow you to create any kind of shape.

Circles & Holes

When talking about the mask property, gradients are certain to come up. We can, for example, “cut” (but really “mask”) a circular hole out of an element with a radial-gradient:

mask: radial-gradient(50px, #0000 98%, #000);

Why aren’t we using a simple background instead? The mask property allows us more flexibility, like using any color we want and applying the effect on a variety of other elements, such as <img>. If the color and flexible utility aren’t a big deal, then you can certainly reach for the background property instead of cutting a hole.

Here’s the mask working on both a <div> and <img>:

Once again, it’s all about CSS masks and gradients. In the following articles, I provide you with examples and recipes for many different possibilities:

Be sure to make it to the end of the second article to see how this technique can be used as decorative background patterns.

This time, we are going to introduce another technique which is “composition”. It’s an operation we perform between two gradient layers. We either use mask-composite to define it, or we declare the values on the mask property.

The figure below illustrates the gradient configuration and the composition between each layer.

We start with a radial-gradient to create a full circle shape. Then we use a conic-gradient to create the shape below it. Between the two gradients, we perform an “intersect” composition to get the unclosed circle. Then we tack on two more radial gradients to the mask to get those nice rounded endpoints on the unclosed circle. This time we consider the default composition, “add”.

Gradients aren’t something new as we use them a lot with the background property but “composition” is the new concept I want you to keep in mind. It’s a very handy one that unlocks a lot of possibilities.

Ready for the CSS?

.arc {
  --b: 40px; /* border thickness */
  --a: 240deg; /* progression */
--_g:/var(--b) var(--b) radial-gradient(50% 50%,#000 98%,#0000) no-repeat; mask: top var(--_g), calc(50% + 50% * sin(var(--a))) calc(50% - 50% * cos(var(--a))) var(--_g), conic-gradient(#000 var(--a), #0000 0) intersect, radial-gradient(50% 50%, #0000 calc(100% - var(--b)), #000 0 98%, #0000) }

We could get clever and use a pseudo-element for the shape that’s positioned behind the set of panels, but that introduces more complexity and fixed values than we ought to have. Instead, we can continue using CSS masks to get the perfect shape with a minimal amount of reusable code.

It’s not really the rounded top edges that are difficult to pull off, but the bottom portion that curves inwards instead of rounding in like the top. And even then, we already know the secret sauce: using CSS masks by combining gradients that reveal just the parts we want.

We start by adding a border around the element — excluding the bottom edge — and applying a border-radius on the top-left and top-right corners.

.tab {
  --r: 40px; /* radius size */

  border: var(--r) solid #0000; /* transparent black */
  border-bottom: 0;
  border-radius: calc(2 * var(--r)) calc(2 * var(--r)) 0 0;
}

Next, we add the first mask layer. We only want to show the padding area (i.e., the red area highlighted in Figure 10).

mask: linear-gradient(#000 0 0) padding-box;

Let’s add two more gradients, both radial, to show those bottom curves.

mask: 
  radial-gradient(100% 100% at 0 0, #0000 98%, #000) 0 100% / var(--r) var(--r), 
  radial-gradient(100% 100% at 100% 0, #0000 98%, #000) 100% 100% / var(--r) var(--r), 
  linear-gradient(#000 0 0) padding-box;

Here is how the full code comes together:

.tab {
  --r: 40px; /* control the radius */

  border: var(--r) solid #0000;
  border-bottom: 0;
  border-radius: calc(2 * var(--r)) calc(2 * var(--r)) 0 0;
  mask: 
    radial-gradient(100% 100% at 0 0, #0000 98%, #000) 0 100% / var(--r) var(--r), 
    radial-gradient(100% 100% at 100% 0, #0000 98%, #000) 100% 100% / var(--r) var(--r), 
    linear-gradient(#000 0 0) padding-box;
  mask-repeat: no-repeat;
  background: linear-gradient(60deg, #BD5532, #601848) border-box;
}

As usual, all it takes is one variable to control the shape. Let’s zero-in on the border-radius declaration for a moment:

border-radius: calc(2 * var(--r)) calc(2 * var(--r)) 0 0;

Notice that the shape’s rounded top edges are equal to two times the radius (--r) value. If you’re wondering why we need a calculation here at all, it’s because we have a transparent border hanging out there, and we need to double the radius to account for it. The radius of the blue areas highlighted in Figure 13 is equal to 2 * R while the red area highlighted in the same figure is equal to 2 * R - R, or simply R.

We can actually optimize the code so that we only need two gradients — one linear and one radial — instead of three. I’ll drop that into the following demo for you to pick apart. Can you figure out how we were able to eliminate one of the gradients?

I’ll throw in two additional variations for you to investigate:

These aren’t tabs at all but tooltips! We can absolutely use the exact same masking technique we used to create the tabs for these shapes. Notice how the curves that go inward are consistent in each shape, no matter if they are positioned on the left, right, or both.

You can always find the code over at my online collection if you want to reference it.

More CSS Shapes

At this point, we’ve seen the main tricks to create CSS shapes. You will rely on mask and gradients if you have curves and rounded parts or clip-path when there are no curves. It sounds simple but there’s still more to learn, so I am going to provide a few more common shapes for you to explore.

Instead of going into a detailed explanation of the shapes in this section, I’m going to give you the recipes for how to make them and all of the ingredients you need to make it happen. In fact, I have written other articles that are directly related to everything we are about to cover and will link them up so that you have guides you can reference in your work.

Triangles

A triangle is likely the first shape that you will ever need. They’re used in lots of places, from play buttons for videos, to decorative icons in links, to active state indicators, to open/close toggles in accordions, to… the list goes on.

Creating a triangle shape is as simple as using a 3-point polygon in addition to defining the size:

.triangle {
  width: 200px;
  aspect-ratio: 1;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

But we can get even further by adding more points to have border-only variations:

We can cut all the corners or just specific ones. We can make circular cuts or sharp ones. We can even create an outline of the overall shape. Take a look at my online generator to play with the code, and check out my full article on the topic where I am detailing all the different cases.

Section Dividers

Speaking of visual transitions between sections, what if both sections have decorative borders that fit together like a puzzle?

I hope you see the pattern now: sometimes, we’re clipping an element or masking portions of it. The fact that we can sort of “carve” into things this way using polygon() coordinates and gradients opens up so many possibilities that would have required clever workarounds and super-specific code in years past.

See my article “How to Create a Section Divider Using CSS” on the freeCodeCamp blog for a deep dive into the concepts, which we’ve also covered here quite extensively already in earlier sections.

Floral Shapes

We’ve created circles. We’ve made wave shapes. Let’s combine those two ideas together to create floral shapes.

These shapes are pretty cool on their own. But like a few of the other shapes we’ve covered, this one works extremely well with images. If you need something fancier than the typical box, then masking the edges can come off like a custom-framed photo.

Here is a demo where I am using such shapes to create a fancy hover effect:

See the Pen Fancy Pop Out hover effect! by Temani Afif.

There’s a lot of math involved with this, specifically trigonometric functions. I have a two-part series that gets into the weeds if you’re interested in that side of things:

As always, remember that my online collection is your Number One resource for all things related to CSS shapes. The math has already been worked out for your convenience, but you also have the references you need to understand how it works under the hood.

Conclusion

I hope you see CSS Shapes differently now as a result of reading this comprehensive guide. We covered a few shapes, but really, it’s hundreds upon hundreds of shapes because you see how flexible they are to configure into a slew of variations.

At the end of the day, all of the shapes use some combination of different CSS concepts such as clipping, masking, composition, gradients, CSS variables, and so on. Not to mention a few hidden tricks like the one related to the polygon() function:

  • It accepts points outside the [0% 100%] range.
  • Switching axes is a solid approach for creating shape variations.
  • The lines we establish can intersect.

It’s not that many things, right? We looked at each of these in great detail and then whipped through the shapes to demonstrate how the concepts come together. It’s not so much about memorizing snippets than it is thoroughly understanding how CSS works and leveraging its features to produce any number of things, like shapes.

Don’t forget to bookmark my CSS Shape website and use it as a reference as well as a quick stop to get a specific shape you need for a project. I avoid re-inventing the wheel in my work, and the online collection is your wheel for snagging shapes made with pure CSS.

Please also use it as inspiration for your own shape-shifting experiments. And post a comment if you think of a shape that would be a nice addition to the collection.

References

The Forensics Of React Server Components (RSCs)

This article is a sponsored by Sentry.io

In this article, we’re going to look deeply at React Server Components (RSCs). They are the latest innovation in React’s ecosystem, leveraging both server-side and client-side rendering as well as streaming HTML to deliver content as fast as possible.

We will get really nerdy to get a full understanding of how RFCs fit into the React picture, the level of control they offer over the rendering lifecycle of components, and what page loads look like with RFCs in place.

But before we dive into all of that, I think it’s worth looking back at how React has rendered websites up until this point to set the context for why we need RFCs in the first place.

The Early Days: React Client-Side Rendering

The first React apps were rendered on the client side, i.e., in the browser. As developers, we wrote apps with JavaScript classes as components and packaged everything up using bundlers, like Webpack, in a nicely compiled and tree-shaken heap of code ready to ship in a production environment.

The HTML that returned from the server contained a few things, including:

  • An HTML document with metadata in the <head> and a blank <div> in the <body> used as a hook to inject the app into the DOM;
  • JavaScript resources containing React’s core code and the actual code for the web app, which would generate the user interface and populate the app inside of the empty <div>.

A web app under this process is only fully interactive once JavaScript has fully completed its operations. You can probably already see the tension here that comes with an improved developer experience (DX) that negatively impacts the user experience (UX).

The truth is that there were (and are) pros and cons to CSR in React. Looking at the positives, web applications delivered smooth, quick transitions that reduced the overall time it took to load a page, thanks to reactive components that update with user interactions without triggering page refreshes. CSR lightens the server load and allows us to serve assets from speedy content delivery networks (CDNs) capable of delivering content to users from a server location geographically closer to the user for even more optimized page loads.

There are also not-so-great consequences that come with CSR, most notably perhaps that components could fetch data independently, leading to waterfall network requests that dramatically slow things down. This may sound like a minor nuisance on the UX side of things, but the damage can actually be quite large on a human level. Eric Bailey’s “Modern Health, frameworks, performance, and harm” should be a cautionary tale for all CSR work.

Other negative CSR consequences are not quite as severe but still lead to damage. For example, it used to be that an HTML document containing nothing but metadata and an empty <div> was illegible to search engine crawlers that never get the fully-rendered experience. While that’s solved today, the SEO hit at the time was an anchor on company sites that rely on search engine traffic to generate revenue.

The Shift: Server-Side Rendering (SSR)

Something needed to change. CSR presented developers with a powerful new approach for constructing speedy, interactive interfaces, but users everywhere were inundated with blank screens and loading indicators to get there. The solution was to move the rendering experience from the client to the server. I know it sounds funny that we needed to improve something by going back to the way it was before.

So, yes, React gained server-side rendering (SSR) capabilities. At one point, SSR was such a topic in the React community that it had a moment in the spotlight. The move to SSR brought significant changes to app development, specifically in how it influenced React behavior and how content could be delivered by way of servers instead of browsers.

Addressing CSR Limitations

Instead of sending a blank HTML document with SSR, we rendered the initial HTML on the server and sent it to the browser. The browser was able to immediately start displaying the content without needing to show a loading indicator. This significantly improves the First Contentful Paint (FCP) performance metric in Web Vitals.

Server-side rendering also fixed the SEO issues that came with CSR. Since the crawlers received the content of our websites directly, they were then able to index it right away. The data fetching that happens initially also takes place on the server, which is a plus because it’s closer to the data source and can eliminate fetch waterfalls if done properly.

Hydration

SSR has its own complexities. For React to make the static HTML received from the server interactive, it needs to hydrate it. Hydration is the process that happens when React reconstructs its Virtual Document Object Model (DOM) on the client side based on what was in the DOM of the initial HTML.

Note: React maintains its own Virtual DOM because it’s faster to figure out updates on it instead of the actual DOM. It synchronizes the actual DOM with the Virtual DOM when it needs to update the UI but performs the diffing algorithm on the Virtual DOM.

We now have two flavors of Reacts:

  1. A server-side flavor that knows how to render static HTML from our component tree,
  2. A client-side flavor that knows how to make the page interactive.

We’re still shipping React and code for the app to the browser because — in order to hydrate the initial HTML — React needs the same components on the client side that were used on the server. During hydration, React performs a process called reconciliation in which it compares the server-rendered DOM with the client-rendered DOM and tries to identify differences between the two. If there are differences between the two DOMs, React attempts to fix them by rehydrating the component tree and updating the component hierarchy to match the server-rendered structure. And if there are still inconsistencies that cannot be resolved, React will throw errors to indicate the problem. This problem is commonly known as a hydration error.

SSR Drawbacks

SSR is not a silver bullet solution that addresses CSR limitations. SSR comes with its own drawbacks. Since we moved the initial HTML rendering and data fetching to the server, those servers are now experiencing a much greater load than when we loaded everything on the client.

Remember when I mentioned that SSR generally improves the FCP performance metric? That may be true, but the Time to First Byte (TTFB) performance metric took a negative hit with SSR. The browser literally has to wait for the server to fetch the data it needs, generate the initial HTML, and send the first byte. And while TTFB is not a Core Web Vital metric in itself, it influences the metrics. A negative TTFB leads to negative Core Web Vitals metrics.

Another drawback of SSR is that the entire page is unresponsive until client-side React has finished hydrating it. Interactive elements cannot listen and “react” to user interactions before React hydrates them, i.e., React attaches the intended event listeners to them. The hydration process is typically fast, but the internet connection and hardware capabilities of the device in use can slow down rendering by a noticeable amount.

The Present: A Hybrid Approach

So far, we have covered two different flavors of React rendering: CSR and SSR. While the two were attempts to improve one another, we now get the best of both worlds, so to speak, as SSR has branched into three additional React flavors that offer a hybrid approach in hopes of reducing the limitations that come with CSR and SSR.

We’ll look at the first two — static site generation and incremental static regeneration — before jumping into an entire discussion on React Server Components, the third flavor.

Static Site Generation (SSG)

Instead of regenerating the same HTML code on every request, we came up with SSG. This React flavor compiles and builds the entire app at build time, generating static (as in vanilla HTML and CSS) files that are, in turn, hosted on a speedy CDN.

As you might suspect, this hybrid approach to rendering is a nice fit for smaller projects where the content doesn’t change much, like a marketing site or a personal blog, as opposed to larger projects where content may change with user interactions, like an e-commerce site.

SSG reduces the burden on the server while improving performance metrics related to TTFB because the server no longer has to perform heavy, expensive tasks for re-rendering the page.

Incremental Static Regeneration (ISR)

One SSG drawback is having to rebuild all of the app’s code when a content change is needed. The content is set in stone — being static and all — and there’s no way to change just one part of it without rebuilding the whole thing.

The Next.js team created the second hybrid flavor of React that addresses the drawback of complete SSG rebuilds: incremental static regeneration (ISR). The name says a lot about the approach in that ISR only rebuilds what’s needed instead of the entire thing. We generate the “initial version” of the page statically during build time but are also able to rebuild any page containing stale data after a user lands on it (i.e., the server request triggers the data check).

From that point on, the server will serve new versions of that page statically in increments when needed. That makes ISR a hybrid approach that is neatly positioned between SSG and traditional SSR.

At the same time, ISR does not address the “stale content” symptom, where users may visit a page before it has finished being generated. Unlike SSG, ISR needs an actual server to regenerate individual pages in response to a user’s browser making a server request. That means we lose the valuable ability to deploy ISR-based apps on a CDN for optimized asset delivery.

The Future: React Server Components

Up until this point, we’ve juggled between CSR, SSR, SSG, and ISR approaches, where all make some sort of trade-off, negatively affecting performance, development complexity, and user experience. Newly introduced React Server Components (RSC) aim to address most of these drawbacks by allowing us — the developer — to choose the right rendering strategy for each individual React component.

RSCs can significantly reduce the amount of JavaScript shipped to the client since we can selectively decide which ones to serve statically on the server and which render on the client side. There’s a lot more control and flexibility for striking the right balance for your particular project.

Note: It’s important to keep in mind that as we adopt more advanced architectures, like RSCs, monitoring solutions become invaluable. Sentry offers robust performance monitoring and error-tracking capabilities that help you keep an eye on the real-world performance of your RSC-powered application. Sentry also helps you gain insights into how your releases are performing and how stable they are, which is yet another crucial feature to have while migrating your existing applications to RSCs. Implementing Sentry in an RSC-enabled framework like Next.js is as easy as running a single terminal command.

But what exactly is an RSC? Let’s pick one apart to see how it works under the hood.

The Anatomy of React Server Components

This new approach introduces two types of rendering components: Server Components and Client Components. The differences between these two are not how they function but where they execute and the environments they’re designed for. At the time of this writing, the only way to use RSCs is through React frameworks. And at the moment, there are only three frameworks that support them: Next.js, Gatsby, and RedwoodJS.

Server Components

Server Components are designed to be executed on the server, and their code is never shipped to the browser. The HTML output and any props they might be accepting are the only pieces that are served. This approach has multiple performance benefits and user experience enhancements:

  • Server Components allow for large dependencies to remain on the server side.
    Imagine using a large library for a component. If you’re executing the component on the client side, it means that you’re also shipping the full library to the browser. With Server Components, you’re only taking the static HTML output and avoiding having to ship any JavaScript to the browser. Server Components are truly static, and they remove the whole hydration step.
  • Server Components are located much closer to the data sources — e.g., databases or file systems — they need to generate code.
    They also leverage the server’s computational power to speed up compute-intensive rendering tasks and send only the generated results back to the client. They are also generated in a single pass, which avoids request waterfalls and HTTP round trips.
  • Server Components safely keep sensitive data and logic away from the browser.
    That’s thanks to the fact that personal tokens and API keys are executed on a secure server rather than the client.
  • The rendering results can be cached and reused between subsequent requests and even across different sessions.
    This significantly reduces rendering time, as well as the overall amount of data that is fetched for each request.

This architecture also makes use of HTML streaming, which means the server defers generating HTML for specific components and instead renders a fallback element in their place while it works on sending back the generated HTML. Streaming Server Components wrap components in <Suspense> tags that provide a fallback value. The implementing framework uses the fallback initially but streams the newly generated content when it‘s ready. We’ll talk more about streaming, but let’s first look at Client Components and compare them to Server Components.

Client Components

Client Components are the components we already know and love. They’re executed on the client side. Because of this, Client Components are capable of handling user interactions and have access to the browser APIs like localStorage and geolocation.

The term “Client Component” doesn’t describe anything new; they merely are given the label to help distinguish the “old” CSR components from Server Components. Client Components are defined by a "use client" directive at the top of their files.

"use client"
export default function LikeButton() {
  const likePost = () => {
    // ...
  }
  return (
    <button onClick={likePost}>Like</button>
  )
}

In Next.js, all components are Server Components by default. That’s why we need to explicitly define our Client Components with "use client". There’s also a "use server" directive, but it’s used for Server Actions (which are RPC-like actions that invoked from the client, but executed on the server). You don’t use it to define your Server Components.

You might (rightfully) assume that Client Components are only rendered on the client, but Next.js renders Client Components on the server to generate the initial HTML. As a result, browsers can immediately start rendering them and then perform hydration later.

The Relationship Between Server Components and Client Components

Client Components can only explicitly import other Client Components. In other words, we’re unable to import a Server Component into a Client Component because of re-rendering issues. But we can have Server Components in a Client Component’s subtree — only passed through the children prop. Since Client Components live in the browser and they handle user interactions or define their own state, they get to re-render often. When a Client Component re-renders, so will its subtree. But if its subtree contains Server Components, how would they re-render? They don’t live on the client side. That’s why the React team put that limitation in place.

But hold on! We actually can import Server Components into Client Components. It’s just not a direct one-to-one relationship because the Server Component will be converted into a Client Component. If you’re using server APIs that you can’t use in the browser, you’ll get an error; if not — you’ll have a Server Component whose code gets “leaked” to the browser.

This is an incredibly important nuance to keep in mind as you work with RSCs.

The Rendering Lifecycle

Here’s the order of operations that Next.js takes to stream contents:

  1. The app router matches the page’s URL to a Server Component, builds the component tree, and instructs the server-side React to render that Server Component and all of its children components.
  2. During render, React generates an “RSC Payload”. The RSC Payload informs Next.js about the page and what to expect in return, as well as what to fall back to during a <Suspense>.
  3. If React encounters a suspended component, it pauses rendering that subtree and uses the suspended component’s fallback value.
  4. When React loops through the last static component, Next.js prepares the generated HTML and the RSC Payload before streaming it back to the client through one or multiple chunks.
  5. The client-side React then uses the instructions it has for the RSC Payload and client-side components to render the UI. It also hydrates each Client Component as they load.
  6. The server streams in the suspended Server Components as they become available as an RSC Payload. Children of Client Components are also hydrated at this time if the suspended component contains any.

We will look at the RSC rendering lifecycle from the browser’s perspective momentarily. For now, the following figure illustrates the outlined steps we covered.

We’ll see this operation flow from the browser’s perspective in just a bit.

RSC Payload

The RSC payload is a special data format that the server generates as it renders the component tree, and it includes the following:

  • The rendered HTML,
  • Placeholders where the Client Components should be rendered,
  • References to the Client Components’ JavaScript files,
  • Instructions on which JavaScript files it should invoke,
  • Any props passed from a Server Component to a Client Component.

There’s no reason to worry much about the RSC payload, but it’s worth understanding what exactly the RSC payload contains. Let’s examine an example (truncated for brevity) from a demo app I created:

1:HL["/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
2:HL["/_next/static/css/app/layout.css?v=1711137019097","style"]
0:"$L3"
4:HL["/_next/static/css/app/page.css?v=1711137019097","style"]
5:I["(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js",["app-pages-internals","static/chunks/app-pages-internals.js"],""]
8:"$Sreact.suspense"
a:I["(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js",["app-pages-internals","static/chunks/app-pages-internals.js"],""]
b:I["(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js",["app-pages-internals","static/chunks/app-pages-internals.js"],""]
d:I["(app-pages-browser)/./src/app/global-error.jsx",["app/global-error","static/chunks/app/global-error.js"],""]
f:I["(app-pages-browser)/./src/components/clearCart.js",["app/page","static/chunks/app/page.js"],"ClearCart"]
7:["$","main",null,{"className":"page_main__GlU4n","children":[["$","$Lf",null,{}],["$","$8",null,{"fallback":["$","p",null,{"children":"🌀 loading products..."}],"children":"$L10"}]]}]
c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}]...
9:["$","p",null,{"children":["🛍️ ",3]}]
11:I["(app-pages-browser)/./src/components/addToCart.js",["app/page","static/chunks/app/page.js"],"AddToCart"]
10:["$","ul",null,{"children":[["$","li","1",{"children":["Gloves"," - $",20,["$...

To find this code in the demo app, open your browser’s developer tools at the Elements tab and look at the <script> tags at the bottom of the page. They’ll contain lines like:

self.__next_f.push([1,"PAYLOAD_STRING_HERE"]).

Every line from the snippet above is an individual RSC payload. You can see that each line starts with a number or a letter, followed by a colon, and then an array that’s sometimes prefixed with letters. We won’t get into too deep in detail as to what they mean, but in general:

  • HL payloads are called “hints” and link to specific resources like CSS and fonts.
  • I payloads are called “modules,” and they invoke specific scripts. This is how Client Components are being loaded as well. If the Client Component is part of the main bundle, it’ll execute. If it’s not (meaning it’s lazy-loaded), a fetcher script is added to the main bundle that fetches the component’s CSS and JavaScript files when it needs to be rendered. There’s going to be an I payload sent from the server that invokes the fetcher script when needed.
  • "$" payloads are DOM definitions generated for a certain Server Component. They are usually accompanied by actual static HTML streamed from the server. That’s what happens when a suspended component becomes ready to be rendered: the server generates its static HTML and RSC Payload and then streams both to the browser.
Streaming

Streaming allows us to progressively render the UI from the server. With RSCs, each component is capable of fetching its own data. Some components are fully static and ready to be sent immediately to the client, while others require more work before loading. Based on this, Next.js splits that work into multiple chunks and streams them to the browser as they become ready. So, when a user visits a page, the server invokes all Server Components, generates the initial HTML for the page (i.e., the page shell), replaces the “suspended” components’ contents with their fallbacks, and streams all of that through one or multiple chunks back to the client.

The server returns a Transfer-Encoding: chunked header that lets the browser know to expect streaming HTML. This prepares the browser for receiving multiple chunks of the document, rendering them as it receives them. We can actually see the header when opening Developer Tools at the Network tab. Trigger a refresh and click on the document request.

We can also debug the way Next.js sends the chunks in a terminal with the curl command:

curl -D - --raw localhost:3000 > chunked-response.txt

You probably see the pattern. For each chunk, the server responds with the chunk’s size before sending the chunk’s contents. Looking at the output, we can see that the server streamed the entire page in 16 different chunks. At the end, the server sends back a zero-sized chunk, indicating the end of the stream.

The first chunk starts with the <!DOCTYPE html> declaration. The second-to-last chunk, meanwhile, contains the closing </body> and </html> tags. So, we can see that the server streams the entire document from top to bottom, then pauses to wait for the suspended components, and finally, at the end, closes the body and HTML before it stops streaming.

Even though the server hasn’t completely finished streaming the document, the browser’s fault tolerance features allow it to draw and invoke whatever it has at the moment without waiting for the closing </body> and </html> tags.

Suspending Components

We learned from the render lifecycle that when a page is visited, Next.js matches the RSC component for that page and asks React to render its subtree in HTML. When React stumbles upon a suspended component (i.e., async function component), it grabs its fallback value from the <Suspense> component (or the loading.js file if it’s a Next.js route), renders that instead, then continues loading the other components. Meanwhile, the RSC invokes the async component in the background, which is streamed later as it finishes loading.

At this point, Next.js has returned a full page of static HTML that includes either the components themselves (rendered in static HTML) or their fallback values (if they’re suspended). It takes the static HTML and RSC payload and streams them back to the browser through one or multiple chunks.

As the suspended components finish loading, React generates HTML recursively while looking for other nested <Suspense> boundaries, generates their RSC payloads and then lets Next.js stream the HTML and RSC Payload back to the browser as new chunks. When the browser receives the new chunks, it has the HTML and RSC payload it needs and is ready to replace the fallback element from the DOM with the newly-streamed HTML. And so on.

In Figures 7 and 8, notice how the fallback elements have a unique ID in the form of B:0, B:1, and so on, while the actual components have a similar ID in a similar form: S:0 and S:1, and so on.

Along with the first chunk that contains a suspended component’s HTML, the server also ships an $RC function (i.e., completeBoundary from React’s source code) that knows how to find the B:0 fallback element in the DOM and replace it with the S:0 template it received from the server. That’s the “replacer” function that lets us see the component contents when they arrive in the browser.

The entire page eventually finishes loading, chunk by chunk.

Lazy-Loading Components

If a suspended Server Component contains a lazy-loaded Client Component, Next.js will also send an RSC payload chunk containing instructions on how to fetch and load the lazy-loaded component’s code. This represents a significant performance improvement because the page load isn’t dragged out by JavaScript, which might not even be loaded during that session.

At the time I’m writing this, the dynamic method to lazy-load a Client Component in a Server Component in Next.js does not work as you might expect. To effectively lazy-load a Client Component, put it in a “wrapper” Client Component that uses the dynamic method itself to lazy-load the actual Client Component. The wrapper will be turned into a script that fetches and loads the Client Component’s JavaScript and CSS files at the time they’re needed.

TL;DR

I know that’s a lot of plates spinning and pieces moving around at various times. What it boils down to, however, is that a page visit triggers Next.js to render as much HTML as it can, using the fallback values for any suspended components, and then sends that to the browser. Meanwhile, Next.js triggers the suspended async components and gets them formatted in HTML and contained in RSC Payloads that are streamed to the browser, one by one, along with an $RC script that knows how to swap things out.

The Page Load Timeline

By now, we should have a solid understanding of how RSCs work, how Next.js handles their rendering, and how all the pieces fit together. In this section, we’ll zoom in on what exactly happens when we visit an RSC page in the browser.

The Initial Load

As we mentioned in the TL;DR section above, when visiting a page, Next.js will render the initial HTML minus the suspended component and stream it to the browser as part of the first streaming chunks.

To see everything that happens during the page load, we’ll visit the “Performance” tab in Chrome DevTools and click on the “reload” button to reload the page and capture a profile. Here’s what that looks like:

When we zoom in at the very beginning, we can see the first “Parse HTML” span. That’s the server streaming the first chunks of the document to the browser. The browser has just received the initial HTML, which contains the page shell and a few links to resources like fonts, CSS files, and JavaScript. The browser starts to invoke the scripts.

After some time, we start to see the page’s first frames appear, along with the initial JavaScript scripts being loaded and hydration taking place. If you look at the frame closely, you’ll see that the whole page shell is rendered, and “loading” components are used in the place where there are suspended Server Components. You might notice that this takes place around 800ms, while the browser started to get the first HTML at 100ms. During those 700ms, the browser is continuously receiving chunks from the server.

Bear in mind that this is a Next.js demo app running locally in development mode, so it’s going to be slower than when it’s running in production mode.

The Suspended Component

Fast forward few seconds and we see another “Parse HTML” span in the page load timeline, but this one it indicates that a suspended Server Component finished loading and is being streamed to the browser.

We can also see that a lazy-loaded Client Component is discovered at the same time, and it contains CSS and JavaScript files that need to be fetched. These files weren’t part of the initial bundle because the component isn’t needed until later on; the code is split into their own files.

This way of code-splitting certainly improves the performance of the initial page load. It also makes sure that the Client Component’s code is shipped only if it’s needed. If the Server Component (which acts as the Client Component’s parent component) throws an error, then the Client Component does not load. It doesn’t make sense to load all of its code before we know whether it will load or not.

Figure 12 shows the DOMContentLoaded event is reported at the end of the page load timeline. And, just before that, we can see that the localhost HTTP request comes to an end. That means the server has likely sent the last zero-sized chunk, indicating to the client that the data is fully transferred and that the streaming communication can be closed.

The End Result

The main localhost HTTP request took around five seconds, but thanks to streaming, we began seeing page contents load much earlier than that. If this was a traditional SSR setup, we would likely be staring at a blank screen for those five seconds before anything arrives. On the other hand, if this was a traditional CSR setup, we would likely have shipped a lot more of JavaScript and put a heavy burden on both the browser and network.

This way, however, the app was fully interactive in those five seconds. We were able to navigate between pages and interact with Client Components that have loaded as part of the initial main bundle. This is a pure win from a user experience standpoint.

Conclusion

RSCs mark a significant evolution in the React ecosystem. They leverage the strengths of server-side and client-side rendering while embracing HTML streaming to speed up content delivery. This approach not only addresses the SEO and loading time issues we experience with CSR but also improves SSR by reducing server load, thus enhancing performance.

I’ve refactored the same RSC app I shared earlier so that it uses the Next.js Page router with SSR. The improvements in RSCs are significant:

Looking at these two reports I pulled from Sentry, we can see that streaming allows the page to start loading its resources before the actual request finishes. This significantly improves the Web Vitals metrics, which we see when comparing the two reports.

The conclusion: Users enjoy faster, more reactive interfaces with an architecture that relies on RSCs.

The RSC architecture introduces two new component types: Server Components and Client Components. This division helps React and the frameworks that rely on it — like Next.js — streamline content delivery while maintaining interactivity.

However, this setup also introduces new challenges in areas like state management, authentication, and component architecture. Exploring those challenges is a great topic for another blog post!

Despite these challenges, the benefits of RSCs present a compelling case for their adoption. We definitely will see guides published on how to address RSC’s challenges as they mature, but, in my opinion, they already look like the future of rendering practices in modern web development.

How To Harness Mouse Interaction Data For Practical Machine Learning Solutions

Mouse data is a subcategory of interaction data, a broad family of data about users generated as the immediate result of human interaction with computers. Its siblings from the same data family include logs of key presses or page visits. Businesses commonly rely on interaction data, including the mouse, to gather insights about their target audience. Unlike data that you could obtain more explicitly, let’s say via a survey, the advantage of interaction data is that it describes the actual behavior of actual people.

Collecting interaction data is completely unobtrusive since it can be obtained even as users go about their daily lives as usual, meaning it is a quantitative data source that scales very well. Once you start collecting it continuously as part of regular operation, you do not even need to do anything, and you’ll still have fresh, up-to-date data about users at your fingertips — potentially from your entire user base, without them even needing to know about it. Having data on specific users means that you can cater to their needs more accurately.

Of course, mouse data has its limitations. It simply cannot be obtained from people using touchscreens or those who rely on assistive tech. But if anything, that should not discourage us from using mouse data. It just illustrates that we should look for alternative methods that cater to the different ways that people interact with software. Among these, the mouse just happens to be very common.

When using the mouse, the mouse pointer is the de facto conduit for the user’s intent in a visual user interface. The mouse pointer is basically an extension of your arm that lets you interact with things in a virtual space that you cannot directly touch. Because of this, mouse interactions tend to be data-intensive. Even the simple mouse action of moving the pointer to an area and clicking it can yield a significant amount of data.

Mouse data is granular, even when compared with other sources of interaction data, such as the history of visited pages. However, with machine learning, it is possible to investigate jumbles of complicated data and uncover a variety of complex behavioral patterns. It can reveal more about the user holding the mouse without needing to provide any more information explicitly than normal.

For starters, let us venture into what kind of information can be obtained by processing mouse interaction data.

What Are Mouse Dynamics?

Mouse dynamics refer to the features that can be extracted from raw mouse data to describe the user’s operation of a mouse. Mouse data by itself corresponds with the simple mechanics of mouse controls. It consists of mouse events: the X and Y coordinates of the cursor on the screen, mouse button presses, and scrolling, each dated with a timestamp. Despite the innate simplicity of the mouse events themselves, the mouse dynamics using them as building blocks can capture user’s behavior from a diverse and emergently complex variety of perspectives.

If you are concerned about user privacy, as well you should be, mouse dynamics are also your friend. For the calculation of mouse dynamics to work, raw mouse data does not need to inherently contain any details about the actual meaning of the interaction. Without the context of what the user saw as they moved their pointer around and clicked, the data is quite safe and harmless.

Some examples of mouse dynamics include measuring the velocity and the acceleration at which the mouse cursor is moving or describing how direct or jittery the mouse trajectories are. Another example is whether the user presses and lets go of the primary mouse button quickly or whether there is a longer pause before they release their press. Four categories of over twenty base measures can be identified: temporal, spatial, spatial-temporal, and performance. Features do not need to be just metrics either, with other approaches using a time series of mouse events.

Temporal mouse dynamics:

  • Movement duration: The time between two clicks;
  • Response time: The time it takes to click something in response to a stimulus (e.g., from the moment when a page is displayed);
  • Initiation time: The time it takes from an initial stimulus for the cursor to start moving;
  • Pause time: The time measuring the cursor’s period of idleness.

Spatial mouse dynamics:

  • Distance: Length of the path traversed on the screen;
  • Straightness: The ratio between the traversed path and the optimal direct path;
  • Path deviation: Perpendicular distance of the traversed path from the optimal path;
  • Path crossing: Counted instances of the traversed and optimal path intersecting;
  • Jitter: The ratio of the traversed path length to its smoothed version;
  • Angle: The direction of movement;
  • Flips: Counted instances of change in direction;
  • Curvature: Change in angle over distance;
  • Inflection points: Counted instances of change in curvature.

Spatial-temporal mouse dynamics:

  • Velocity: Change of distance over time;
  • Acceleration: Change of velocity over time;
  • Jerk: Change of acceleration over time;
  • Snap: Change in jerk over time;
  • Angular velocity: Change in angle over time.

Performance mouse dynamics:

  • Clicks: The number of mouse button events pressing down or up;
  • Hold time: Time between mouse down and up events;
  • Click error: Length of the distance between the clicked point and the correct user task solution;
  • Time to click: Time between the hover event on the clicked point and the click event;
  • Scroll: Distance scrolled on the screen.

Note: For detailed coverage of varied mouse dynamics and their extraction, see the paper “Is mouse dynamics information credible for user behavior research? An empirical investigation.”

The spatial angular measures cited above are a good example of how the calculation of specific mouse dynamics can work. The direction angle of the movements between points A and B is the angle between the vector AB and the horizontal X axis. Then, the curvature angle in a sequence of points ABC is the angle between vectors AB and BC. Curvature distance can be defined as the ratio of the distance between points A and C and the perpendicular distance between point B and line AC. (Definitions sourced from the paper “An efficient user verification system via mouse movements.”)

Even individual features (e.g., mouse velocity by itself) can be delved into deeper. For example, on pages with a lot of scrolling, horizontal mouse velocity along the X-axis may be more indicative of something capturing the user’s attention than velocity calculated from direct point-to-point (Euclidean) distance in the screen's 2D space. The maximum velocity may be a good indicator of anomalies, such as user frustration, while the mean or median may tell you more about the user as a person.

From Data To Tangible Value

The introduction of mouse dynamics above, of course, is an oversimplification for illustrative purposes. Just by looking at the physical and geometrical measurements of users’ mouse trajectories, you cannot yet tell much about the user. That is the job of the machine learning algorithm. Even features that may seem intuitively useful to you as a human (see examples cited at the end of the previous section) can prove to be of low or zero value for a machine-learning algorithm.

Meanwhile, a deceptively generic or simplistic feature may turn out unexpectedly quite useful. This is why it is important to couple broad feature generation with a good feature selection method, narrowing the dimensionality of the model down to the mouse dynamics that help you achieve good accuracy without overfitting. Some feature selection techniques are embedded directly into machine learning methods (e.g., LASSO, decision trees) while others can be used as a preliminary filter (e.g., ranking features by significance assessed via a statistical test).

As we can see, there is a sequential process to transforming mouse data into mouse dynamics, into a well-tuned machine learning model to field its predictions, and into an applicable solution that generates value for you and your organization. This can be visualized as the pipeline below.

Machine Learning Applications Of Mouse Dynamics

To set the stage, we must realize that companies aren’t really known for letting go of their competitive advantage by divulging the ins and outs of what they do with the data available to them. This is especially true when it comes to tech giants with access to potentially some of the most interesting datasets on the planet (including mouse interaction data), such as Google, Amazon, Apple, Meta, or Microsoft. Still, recording mouse data is known to be a common practice.

With a bit of grit, you can find some striking examples of the use of mouse dynamics, not to mention a surprising versatility in techniques. For instance, have you ever visited an e-commerce site just to see it recommend something specific to you, such as a gendered line of cosmetics — all the while, you never submitted any information about your sex or gender anywhere explicitly?

Mouse data transcends its obvious applications, as is replaying the user’s session and highlighting which visual elements people interact with. A surprising amount of internal and external factors that shape our behavior are reflected in data as subtle indicators and can thus be predicted.

Let’s take a look at some further applications. Starting some simple categorization of users.

Example 1: Biological Sex Prediction

For businesses, knowing users well allows them to provide accurate recommendations and personalization in all sorts of ways, opening the gates for higher customer satisfaction, retention, and average order value. By itself, the prediction of user characteristics, such as gender, isn’t anything new. The reason for basing it on mouse dynamics, however, is that mouse data is generated virtually by the truckload. With that, you will have enough data to start making accurate predictions very early.

If you waited for higher-level interactions, such as which products the user visited or what they typed into the search bar, by the time you’d have enough data, the user may have already placed an order or, even worse, left unsatisfied.

The selection of the machine learning algorithm matters for a problem. In one published scientific paper, six various models have been compared for the prediction of biological gender using mouse dynamics. The dataset for the development and evaluation of the models provides mouse dynamics from participants moving the cursor in a broad range of trajectory lengths and directions. Among the evaluated models — Logistic regression, Support vector machine, Random forest, XGBoost, CatBoost, and LightGBM — CatBoost achieved the best F1 score.

Putting people into boxes is far from everything that can be done with mouse dynamics, though. Let’s take a look at a potentially more exciting use case — trying to predict the future.

Example 2: Purchase Prediction

Another e-commerce application predicts whether the user has the intent to make a purchase or even whether they are likely to become a repeat customer. Utilizing such predictions, businesses can adapt personalized sales and marketing tactics to be more effective and efficient, for example, by catering more to likely purchasers to increase their value — or the opposite, which is investigating unlikely purchasers to find ways to turn them into likely ones.

Interestingly, a paper dedicated to the prediction of repeat customership reports that when a gradient boosting model is validated on data obtained from a completely different online store than where it was trained and tuned, it still achieves respectable performance in the prediction of repeat purchases with a combination of mouse dynamics and other interaction and non-interaction features.

It is plausible that though machine-learning applications tend to be highly domain-specific, some models could be used as a starting seed, carried over between domains, especially while still waiting for user data to materialize.

Additional Examples

Applications of mouse dynamics are a lot more far-reaching than just the domain of e-commerce. To give you some ideas, here are a couple of other variables that have been predicted with mouse dynamics:

The Mouse-Shaped Caveat

When you think about mouse dynamics in-depth, some questions will invariably start to emerge. The user isn’t the only variable that could determine what mouse data looks like. What about the mouse itself?

Many brands and models are available for purchase to people worldwide. Their technical specifications deviate in attributes such as resolution (measured in DPI or, more accurately, CPI), weight, polling rate, and tracking speed. Some mouse devices have multiple profile settings that can be swapped between at will. For instance, the common CPI of an office mouse is around 800-1,600, while a gaming mouse can go to extremes, from 100 to 42,000. To complicate things further, the operating system has its own mouse settings, such as sensitivity and acceleration. Even the surface beneath the mouse can differ in its friction and optical properties.

Can we be sure that mouse data is reliable, given that basically everyone potentially works under different mouse conditions?

For the sake of argument, let’s say that as a part of a web app you’re developing, you implement biometric authentication with mouse dynamics as a security feature. You sell it by telling customers that this form of auth is capable of catching attackers who try to meddle in a tab that somebody in the customer’s organization left open on an unlocked computer. Recognizing the intruder, the app can sign the user out of the account and trigger a warning sent to the company. Kicking out the real authorized user and sounding the alarm just because somebody bought a new mouse would not be a good look. Recalibration to the new mouse would also produce friction. Some people like to change their mouse sensitivity or use different computers quite often, so frequent calibration could potentially present a critical flaw.

We found that up until now, there was barely anything written about whether or how mouse configuration affects mouse dynamics. By mouse configuration, we refer to all properties of the environment that could impact mouse behavior, including both hardware and software.

From the authors of papers and articles about mouse dynamics, there is barely a mention of mouse devices and settings involved in development and testing. This could be seen as concerning. Though hypothetically, there might not be an actual reason for concern, that is exactly the problem. There was just not even enough information to make a judgment on whether mouse configuration matters or not. This question is what drove the study conducted by UXtweak Research (as covered in the peer-reviewed paper in Computer Standards & Interfaces).

The quick answer? Mouse configuration does detrimentally affect mouse dynamics. How?

  1. It may cause the majority of mouse dynamics values to change in a statistically significant way between different mouse configurations.
  2. It may lower the prediction performance of a machine learning model if it was trained on a different set of mouse configurations than it was tested on.

It is not automatically guaranteed that prediction based on mouse dynamics will work equally well for people on different devices. Even the same person making the exact same mouse movements does not necessarily produce the same mouse dynamics if you give them a different mouse or change their settings.

We cannot say for certain how big an impact mouse configuration can have in a specific instance. For the problem that you are trying to solve (specific domain, machine learning model, audience), the impact could be big, or it could be negligible. But to be sure, it should definitely receive attention. After all, even a deceptively small percentage of improvement in prediction performance can translate to thousands of satisfied users.

Tackling Mouse Device Variability

Knowledge is half the battle, and so it is also with the realization that mouse configuration is not something that can be just ignored when working with mouse dynamics. You can perform tests to evaluate the size of the effect that mouse configuration has on your model’s performance. If, in some configurations, the number of false positives and false negatives rises above levels that you are willing to tolerate, you can start looking for potential solutions by tweaking your prediction model.

Because of the potential variability in real-world conditions, differences between mouse configurations can be seen as a concern. Of course, if you can rely on controlled conditions (such as in apps only accessible via standardized kiosks or company-issued computers and mouse devices where all system mouse settings are locked), you can avoid the concern altogether. Given that the training dataset uses the same mouse configuration as the configuration used in production, that is. Otherwise, that may be something new for you to optimize.

Some predicted variables can be observed repeatedly from the same user (e.g., emotional state or intent to make a purchase). In the case of these variables, to mitigate the problem of different users utilizing different mouse configurations, it would be possible to build personalized models trained and tuned on the data from the individual user and the mouse configurations they normally use. You also could try to normalize mouse dynamics by adjusting them to the specific user’s “normal” mouse behavior. The challenge is how to accurately establish normality. Note that this still doesn’t address situations when the user changes their mouse or settings.

Where To Take It From Here

So, we arrive at the point where we discuss the next steps for anyone who can’t wait to apply mouse dynamics to machine learning purposes of their own. For web-based solutions, you can start by looking at MouseEvents in JavaScript, which is how you’ll obtain the elementary mouse data necessary.

Mouse events will serve as the base for calculating mouse dynamics and the features in your model. Pick any that you think could be relevant to the problem you are trying to solve (see our list above, but don’t be afraid to design your own features). Don’t forget that you can also combine mouse dynamics with domain and application-specific features.

Problem awareness is key to designing the right solutions. Is your prediction problem within-subject or between-subject? A classification or a regression? Should you use the same model for your whole audience, or could it be more effective to tailor separate models to the specifics of different user segments?

For example, the mouse behavior of freshly registered users may differ from that of regular users, so you may want to divide them up. From there, you can consider the suitable machine/deep learning algorithm. For binary classification, a Support vector machine, Logistic regression, or a Random Forest could do the job. To delve into more complex patterns, you may wish to reach for a Neural network.

Of course, the best way to uncover which machine/deep learning algorithm works best for your problem is to experiment. Most importantly, don’t give up if you don’t succeed at first. You may need to go back to the drawing board a few times to reconsider your feature engineering, expand your dataset, validate your data, or tune the hyperparameters.

Conclusion

With the ongoing trend of more and more online traffic coming from mobile devices, some futurist voices in tech might have you believe that “the computer mouse is dead”. Nevertheless, those voices have been greatly exaggerated. One look at statistics reveals that while mobile devices are excessively popular, the desktop computer and the computer mouse are not going anywhere anytime soon.

Classifying users as either mobile or desktop is a false dichotomy. Some people prefer the desktop computer for tasks that call for exact controls while interacting with complex information. Working, trading, shopping, or managing finances — all, coincidentally, are tasks with a good amount of importance in people’s lives.

To wrap things up, mouse data can be a powerful information source for improving digital products and services and getting yourself a headway against the competition. Advantageously, data for mouse dynamics does not need to involve anything sensitive or in breach of the user’s privacy. Even without identifying the person, machine learning with mouse dynamics can shine a light on the user, letting you serve them more proper personalization and recommendations, even when other data is sparse. Other uses include biometrics and analytics.

Do not underestimate the impact of differences in mouse devices and settings, and you may arrive at useful and innovative mouse-dynamics-driven solutions to help you stand out.

Combining CSS :has() And HTML &lt;select&gt; For Greater Conditional Styling

Even though the CSS :has() pseudo-class is relatively new, we already know a lot about it, thanks to many, many articles and tutorials demonstrating its powerful ability to conditionally select elements based on their contents. We’ve all seen the card component and header examples, but the conditional nature of :has() actually makes it adept at working with form controls, which are pretty conditional in nature as well.

Let’s look specifically at the <select> element. With it, we can make a choice from a series of <option>s. Combined with :has(), we are capable of manipulating styles based on the selected <option>.

<select>
  <option value="1" selected>Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  <option value="4">Option 4</option>
  <option value="5">Option 5</option>
</select>

This is your standard <select> usage, producing a dropdown menu that contains options for user selection. And while it’s not mandatory, I’ve added the selected attribute to the first <option> to set it as the initial selected option.

Applying styles based on a user’s selection is not a new thing. We’ve had the Checkbox Hack in our pockets for years, using the :checked CSS pseudo-class to style the element based on the selected option. In this next example, I’m changing the element’s color and the background-color properties based on the selected <option>.

See the Pen demo 01 - Using the :has selector on a dropdown menu by Amit Sheen.

But that’s limited to styling the current element, right? If a particular <option> is :checked, then we style its style. We can write a more complex selector and style child elements based on whether an <option> is selected up the chain, but that’s a one-way road in that we are unable to style up parent elements even further up the chain.

That’s where :has() comes in because styling up the chain is exactly what it is designed to do; in fact, it’s often called the “parent selector” for this reason (although “family selector” may be a better descriptor).

For example, if we want to change the background-color of the <select> element according to the value of the selected <option>, we select the element if it has a specific [value] that is :checked.

See the Pen demo 02 - Using the :has selector on a dropdown menu by Amit Sheen.

Just how practical is this? One way I’m using it is to style mandatory <select> elements without a valid selected <option>. So, instead of applying styles if the element :has() a :checked state, I am applying styles if the required element does :not(:has(:checked)).

See the Pen demo 02.1 - Using the :has selector on a dropdown menu by Amit Sheen.

But why stop there? If we can use :has() to style the <select> element as the parent of an <option>, then we can also use it to style the parent of the <select>, as well as its parent, in addition to its parent, and even its parent... all the way up the chain to the :root element. We could even bring :has() all the way up the chain and sniff out whether any <select> child of the document :root :has() a particular <option> that is :checked:

:root:has(select [value="foo"]:checked) {
  // Styles applied if <option value="foo"> is <select>-ed
}

This is useful for setting a custom property value dynamically or applying a set of styles for the whole page. Let’s make a little style picker that illustrates the idea of setting styles on an entire page.

See the Pen demo 03 - Using the :has selector on a dropdown menu by Amit Sheen.

Or perhaps a theme picker:

See the Pen demo 04 - Using the :has selector on a dropdown menu by Amit Sheen.

How that last example works is that I added a class to each <select> element and referenced that class inside the :has() selector in order to prevent unwanted selections in the event that there are multiple <select> elements on the page.

And, of course, we don’t have to go all the way up to the :root element. If we’re working with a specific component, we can scope :has() to that component like in the following demo of a star rating component.

See the Pen demo 05 - Using the :has selector on a dropdown menu by Amit Sheen.

Watch a short video tutorial I made on using CSS to create 3D animated stars.
Conclusion

We’d be doing :has() a great disservice if we only saw it as a “parent selector” rather than the great conditional operator it is for applying styles all the way up the chain. Seen this way, it’s more of a modern upgrade to the Checkbox Hack in that it sends styles up like we were never able to do before.

There are endless examples of using :has() to create style variations of a component according to its contents. We’ve even seen it used to accomplish the once-complicated linked card pattern. But now you have an example for using it to create dropdown menus that conditionally apply styles (or don’t) to a page or component based the currently selected option — depending on how far up the chain we scope it.

I’ve used this technique a few different ways — e.g., as form validation, a style picker, and star ratings — but I’m sure there are plenty of other ways you can imagine how to use it in your own work. And if you are using :has() on a <select> element for something different or interesting, let me know because I’d love to see it!

Further Reading On SmashingMag

Lessons Learned After Selling My Startup

August 2021 marks a milestone for me. That’s when we signed an acquisition agreement to sell Chatra, a profitable live chat platform. I co-founded it after shutting down my first startup after a six-year struggle. Chatra took me and the team six years to finish — that’s six years of learning, experimenting, sometimes failing, and ultimately winning big.

Acquisitions happen all the time. But what does it look like to go through one, putting the thing you built and nurtured up for sale and ceding control to someone else to take over? Sometimes, these things are complicated and contain clauses about what you can and can’t say after the transaction is completed.

So, I’ve curated a handful of the most valuable takeaways from starting, growing, and selling the company. It took me some time to process everything; some lessons were learned immediately, while others took time to sink in. Ultimately, though, it’s a recollection of my personal journey. I hope sharing it can help you in the event you ever find yourself in a similar pair of shoes.

Keeping The Band Together

Rewind six years before the Chatra acquisition. My first startup, Getwear, ran out of steam, and I — along with everyone else — was ready to jump ship.

But we weren’t ready to part ways. My co-founder-partner was a close childhood friend with whom I would sell pirated CDs in the late 90s. Now, I don’t think it’s the most honest way to make a living, but it didn’t bother us much in high school. It also contributed to a strong bond between us, one that led to the launch of Getwear and, later, Chatra.

That partnership and collaboration were too precious to let go; we knew that our work wasn’t supposed to end at Getwear and that we’d have at least one more try together. The fact that we struggled together before is what allowed us to pull through difficult times later. Our friendship allowed us to work through stress, difficulties, and the unavoidable disagreements that always come up.

That was a big lesson for me: It’s good to have a partner you trust along for the ride. We were together before Chatra, and we saw it all the way through to the end. I can’t imagine how things would have been different had I partnered with someone new and unfamiliar, or worse, on my own.

Building Business Foundations

We believed Getwear would make us millionaires. So when it failed, that motivation effectively evaporated. We were no longer inspired to take on ambitious plans, but we still had enough steam to start a digital analog of a döner kebab shop — a simple, sought-after tech product just to pay our bills.

This business wasn’t to be built on the back of investment capital; no, it was bootstrapped. That means we made do with a small, independent, fully-remote team. Remember, this is in 2015. The global pandemic had yet to happen, and a fully remote team was still a novelty. And it was quite a change from how we ran Getwear, which was stocked with an R&D department, a production office, and even a factory in Mumbai. A small distributed team seemed the right approach to keep us nimble as we set about defining our path forward as a company.

Finding our purpose required us to look at the intersection of what the market needs and what we know and can do well. Building a customer support product was an obvious choice: at Getwear, we heavily relied on live chat to help users take their body measurements and place their orders.

We were familiar with existing products on the market. Besides, we already had experience building a conversational support product: we had built an internal tool to facilitate communication between our Mumbai-based factory and an overseas customer-facing team. The best thing about that was that it was built on a relatively obscure framework offering real-time messaging out of the box.

There were maybe 20 established competitors in the space back in 2015, but that didn’t dissuade us. If there was enough room for 20 products to do business, there must be enough for 21. I assumed we should treat competition as a market validation rather than an obstacle.

Looking back, I can confidently say that it’s totally possible to compete (and succeed) in a crowded market.

Product-wise, Getwear was very innovative; no one had ever built an online jeans customizer as powerful as ours. We designed the UX from scratch without relying much on the best practices.

With Chatra, we went down a completely different route: We had improved the established live chat product category via features that were, at that time, commonly found in other types of software but hadn’t made their way to our field. That was the opportunity we seized.

The existing live chat platforms felt archaic in that the interfaces were clunky and reminiscent of Windows 95, the user flows were poorly thought out, and the dated user experience resulted in lost conversation histories.

Slack was a new product at this time and was all the rage with its fresh approach to user interfaces and conversational onboarding. Products like Facebook Messenger and Telegram (which is popular in Eastern Europe and the Middle East) were already standard bearers and formed user expectations for how a messaging experience should work on mobile. We learned a lot from these products and found in them the blueprint to design a modern chat widget and dashboard for agents.

We certainly stood on the shoulders of giants, and there’s nothing wrong with stealing like an artist: in fact, both Steve Jobs and Bill Gates did it.

The takeaway?

A product does not have to be new to redefine and disrupt a market. It’s possible to lead by introducing modern standards and designs rather than coming up with something radically different.

Making A Go-To-Market Strategy

Once we were clear about what we were building and how to build it, the time came to figure out a strategy for bringing our product to market.

Two things were very clear and true to us up front:

  1. We needed to launch and start earning immediately — in months rather than years — being a bootstrapped company and all.
  2. We didn’t have money for things like paid acquisition, brand awareness, or outbound sales representatives to serve as the front line for customer engagement.

Both conclusions, taken together, helped us decide to focus our efforts on small businesses that need fewer features in a product and onboard by self-service. Marketing-wise, that meant we’d need to find a way around prohibitively expensive ads.

Enter growth hacking! The term doesn’t resonate now the way it did in 2015: fresh, aggressive, and effective. As a user-facing website widget, we had a built-in acquisition channel by way of a “powered by Chatra” link. For it to be an effective marketing tool, we had to accumulate a certain number of customers. Otherwise, who’s going to see the link in the first place?

We combined unorthodox techniques to acquire new customers, like web-scraping and email address discovery with cold outreach.

Initially, we decided to go after our competitors’ customers. But the only thing we got out of targeting them with emails was their rightful anger.

In fact, a number of customers complained directly to the competitors, and the CEO of a prominent live chat company demanded we cease communicating with their users.

More than that, he actually requested that we donate to a well-known civil liberty NGO, something we wholeheartedly agreed to, considering it was indeed the right thing to do.

So, we decided to forget about competition and target potential customers (who owned e-commerce websites) using automation for lead research, email sending, and reply processing. We managed to do it on a massive scale with very few resources. By and large, cold outreach has been the single most effective marketing tool we have ever used. And contrary to common assumption, it is not a practice reserved purely for enterprise products.

Once we acquired a significant user mass, the widget link became our Number One acquisition channel. In lean startup terminology, a viral engine of growth is a situation when existing customers start generating leads and filling the marketing funnel for you. It’s where we all want to be, but the way to get there is often murky and unreliable. But my experience tells me that it is possible and can be planned.

For this strategy to work, it has to be based on natural user interactions. With widgets, the mechanic is quite apparent, but not so much with other products. Still, you can do well with serious planning and running experiments to help make informed decisions that achieve the best possible results.

For example, we were surprised that the widget link performed way better in tests when we changed it from “Powered by Chatra” to “Get Chatra!”. We’re talking big increases with minor tweaks. The small details really do matter!

Content marketing was another avenue we explored for generating leads. We had already done the cold outreach and had a good viral engine going with the widget link. Content marketing, in contrast, was an attempt to generate leads at the “top” of the funnel, independent of any outbound marketing or our customers’ websites. We produced books and guides that were well-researched, written, and designed to bring in potential customers while supporting existing ones with resources to get the most out of Chatra.

Sadly, these efforts failed to attract many new leads. I don’t want to say not to invest in quality content; it’s just that this is not a viable short-term growth strategy.

Increasing Lifetime Customer Value

It took six months of development to launch and another year to finally break even. By then, we had achieved a product-market fit with consistent organic growth; it was time to focus on metrics and unit economics. Our challenge was to limit customer churn and find ways to increase the lifetime value of existing customers.

If there’s an arch-enemy to SaaS, it’s churn. Mitigating churn is crucial to any subscription business, as longer subscriptions generate more revenue. Plus, it’s easier to prevent churn than it is to acquire new customers.

We found it helpful to distinguish between avoidable churn and unavoidable (i.e., “natural”) churn. The latter concerns customer behavior beyond our control: if an e-commerce store shuts down, they won’t pay for services. And we had nothing to do with them shutting down — it’s just the reality of life that most small businesses fail. No quick-fix strategy could ever change that; we just had to deal with it.

Chatra’s subscription pricing was fairly inexpensive, yet we enjoyed a relatively high customer lifetime value (cLTV). Many customers tended to stay for a long time — some, for years. Our high cLTV helped us justify higher customer acquisition costs (CAC) for paid ads in the Shopify app store once we decided to run them. Running the ads allowed us to improve our Shopify app store search position. And because of that, we improved and kept our position as a top app within our category. That, I believe, was one of the factors that the company Brevo considered when they later decided to acquire our business.

We tried improving the free-to-paid subscription conversion rate by targeting those who actively used the product but remained on a free plan for an extended period. We offered them an upgraded plan subscription for just one dollar per year. And to our surprise, that failed to convince many people to upgrade. We were forced to conclude that there are two types of customers: those who pay and those who do not (and will not).

From that point forward, things got even weirder. For example, we ran several experiments with subscription pricing and found that we could increase subscription prices from $11 per seat to $19 without adversely affecting either the visitor-to-user or the free-to-paid conversion rates! Apparently, price doesn’t matter as much as you might think. It’s possible to raise prices without adversely affecting conversions, at least in our experience with a freemium pricing model.

We also released additional products we could cross-sell to existing customers. One was Livebar, an app for in-browser notifications on recent online shopping purchases. Another was Yeps, a simple announcement bar that sticks to the top of a webpage. Product-wise, both were good. But despite our efforts to bring awareness to them in all our communications with Chatra customers, they never really took off. We’ve closed the first and sold the second for a price that barely justified the development and ongoing support we were putting into it. We were wrong to assume that if we have a loyal audience, we could automatically sell them another product.

Contemplating An Exit

Chatra was a lean company. As a SaaS business, we had a perfect cost-revenue ratio and gained new customers mainly through viral dynamics and self-onboarding. These didn’t increase our costs much but did indeed bring in extra subscription dollars. The engine worked almost without any effort on our side.

After a few years, the company could mostly function on auto-pilot, giving us — the founders — time and resources to pay our bills and run business experiments. We were enjoying a good life. Our work was a success!

We gave up on an exit strategy even before starting, so we didn’t pay much attention to the acquisition offers we routinely received; most weren’t enticing enough to pull us away. Even those sent by people known in the industry were way too small: the best offer we got was a valuation of 2.5 times our Annual Recurring Revenue (ARR), which was a non-starter for us.

Then, we received an email with another offer. The details were slim, but we decided to at least entertain the idea and schedule a time to chat. I replied that we wouldn’t consider anything lower than an industry-standard venture-backed SaaS valuation (which was about eight times ARR at the time). The response, surprisingly, read: “Let’s talk. Are you ready to sign a non-disclosure agreement?”

My biggest concern was that transferring ownership might lead to the Chatra team being laid off and the product termination. I didn’t want to let down our existing customers! The buyer understood the situation and assured us that Chatra would remain a separate line of business, at least for an extended period. No one on the team would lose their job. The buyer also planned to fork Chatra rather than close it, at least initially.

Still, letting go of it was difficult, and at times, I even felt the urge to blow up the negotiations.

So, why sell at all? We did it for three reasons:

  • First, we felt stuck in the mature stage of the business lifecycle and missed the feeling of creating new things.
  • Second, we (rightfully) knew that the good times could not last forever; we would be wise to avoid putting all our eggs in one basket.
  • Third was a bit of pride. I genuinely wanted to go through the acquisition process, which has always seemed like a rite of passage for entrepreneurs.

Chatra was growing, cash-flow positive, and economic tailwinds seemed to blow our way. On the flip side, however, we had little left to do as founders. We didn’t want to go upmarket and compete with massive players like Intercom and Drift. We were happy in our niche, but it didn’t offer enough growth or expansion opportunities. We felt near the end of the line.

Looking back, I see how fortunate we were. The market took a huge hit soon after the acquisition, to the extent that I’m sure we would not have been able to fetch equally enticing offers within the next two years.

I want to stress that the offer we got was very, very generous. Still I often kick myself for not asking for more, as a deep-pocketed buyer is unlikely to turn away simply because we were trying to increase the company’s valuation. The additional ask would have been negligible to the buyer, but it could have been very meaningful for us.

Different acquisitions wind up looking different in the end. If you’re curious what a transaction looks like, ours was split into three payouts:

  1. An initial, fixed payment on the closing date;
  2. Several flexible payouts based on reaching post-acquisition milestones;
  3. An escrow amount deposited with an escrow agent for the possibility of something going wrong, like legal claims.

We assumed this structure was non-negotiable and didn’t try to agree on a different distribution that would move more money to the initial payment. Why? We were too shy to ask and were sure we’d complete all requirements on time. Accepting a significant payment delay essentially credited the buyer for the amount of the payouts while leaving me and my co-founder vulnerable to uncertainty.

We should’ve been bold and negotiated more favorable terms. After all, it represented the last time we’d have to battle for Chatra. I consider that a lesson learned for next time.

Conclusion

Parting ways with Chatra wasn’t easy. The team became my second family, and every product pixel and bit of code was dear to my heart. And yes, I do still feel nostalgia for it from time to time. But I certainly enjoy the freedom that comes with the financial gains.

One thing I absolutely want to mention before closing this out is that

Having an “exit” under my belt actually did very little to change my personal well-being or sense of self-worth. The biggest lesson I took away from the acquisition is that success is the process of doing things, not the point you can arrive at.

I don’t yet know where the journey will take me from here, but I’m confident that there will be both a business challenge and a way of helping others on their own founder journey. That said, I sincerely hope that my experience gives you a good deal of insight into the process of selling a company. It’s one of those things that often happens behind closed doors. But by shedding a little light on it — at least this one reflection — perhaps you will be more prepared than I was and know what to look for.

The End Of The Free Tier

I love free tiers, and I am not the only one. Everyone loves free things — they’re the best thing in life, after all. But maybe we have grown too accustomed to them, to the extent that a service switching from a “freemium” model to a fully paid plan would probably feel outrageous to you. Nowadays, though, the transition from free to paid services seems inevitable. It’s a matter of when a service drops its free tier rather than if it will.

Companies need to make money. As developers, we probably understand the most that a product comes with costs; there are startup funds, resources, and salaries spent to maintain and support the product against a competitive globalized market.

If I decided to take something I made and ship it to others, you darn well know I would charge money for it, and I assume you’re the same. At the same time, I’m typically more than happy to pay for something, knowing it supports the people who made it.

We get that, and we surely don’t go walk into a grocery store complaining that nothing they have is free. It’s just how things work.

What exactly, then, is so infuriating about a service offering a free tier and later deciding to transition to a priced one?

It’s Positioning, Not Money

It’s not so much about the money as it is the positioning. Who wouldn’t feel somewhat scammed, having invested time and resources into something that was initially advertised as “free” only to be blindsided behind a paywall?

Most of the time, the feeling is less anger than it is mildly annoying. For example, if your favorite browser suddenly became a paid premium offering, you would most likely switch to the next best option. But what happens when the free tier for a hosted product or service is retired? Switching isn’t as easy when hundreds of thousands of developers server their projects in a free-tier hosting plan.

The practice of offering a free tier only to remove it seems like a common practice on the web that won’t go away any time soon. It’s as though companies ditch them once (1) the product becomes mature enough to be a feature-rich offering or (2) the company realizes free customers are not converting into paid customers.

It has been a source of endless complaints, and one only needs to look back at PlanetScale’s recent decision to remove its free-tier database plan, which we will get deeper into in a bit. Are free tiers removed because of their unsustainable nature, or is it to appease profit-hungry companies? I want to explore the why and how of free tiers, better approaches for marketing “free” services, and how to smoothly retire a free tier when it inevitably goes away.

Glossary

Before we wade further into these waters, I think it’s worth having a baseline understanding of pricing concepts that are relevant to the discussion.

A free tier is one of several flavors:

  • Free trial opt-in
    Permits users to try out the product for a limited period without providing payment details. Once the trial ends, so does access to the product features.
  • Free trial opt-out
    Requires users to provide payment information during registration en route to a free trial that, once it ends, automatically converts to a paid account.
  • Freemium model
    Offers access to a product’s “core” features but requires upgrading to a paid account to unlock other features and benefits.
  • Reverse trial model
    Users start with access to the premium tier upon registration and then transition to a freemium tier after the trial period ends.
Case Study: PlanetScale

Let’s start this conversation by looking at PlanetScale and how it killed its free tier at the beginning of the year. Founded in 2018, PlanetScale launched its database as a service in 2021 and has raised $105 million in venture capital and seed funding, becoming one of the fastest-growing tech companies in North America by 2023. In March of this year, CEO Sam Lambert announced the removal of PlanetScale’s hobby tier.

In short, the decision was made to provide “a reliable and sustainable platform for our customers” by not “giving away endless amounts of free resources to keep growing,” which, of course, leaves everyone in the freemium tier until April 8 to either pay for one of the next plans at the outrageous starting price of $39 per month or migrate to another platform.

Again, a company needs steady revenue and a reliable business plan to stay afloat. But PlanetScale gave mixed signals when they stated in the bespoke memo that “[e]very unprofitable company has a date in the future where it could disappear.” Then they went on to say they are “the main database for companies totaling more than $50B in market cap,” and they “have been recognized [...] as one of the fastest growing tech companies in the US.”

In non-bureaucratic speak, PlanetScale says that the product is failing from one side of its mouth and that the company is wildly successful from the other.

The company is doing great. In November 2023, PlanetScale was ranked as the 188th fastest-growing company in North America by Deloitte Technology Fast 500™. Growth doesn’t necessarily equal revenue, but “to be eligible for Technology Fast 500 recognition, [...] [c]ompanies must have base-year operating revenues of at least US $50,000, and current-year operating revenues of at least US $5 million.”

PlanetScale’s decision can only be interpreted as “we want more money,” at least to me. There’s nothing about its current performance that suggests it needs the revenue to keep the company alive.

That’s a punch below the waist for the developer community, especially considering that those on the free tier are likely independent bootstrappers who need to keep their costs low. And let’s not overlook that ending the free tier was accompanied by a round of layoffs at the company.

PlanetScale’s story is not what worries me; it’s that retiring freemium plans is becoming standard practice, as we have seen with the likes of other big PaaS players, including Heroku and Railway.

That said, the PlanetScale case is perhaps the most frustrating because the cheapest alternative to the free tier they now offer is a whopping $39 per month. Compare that to the likes of others in that space, such as Heroku ($7 per month) and Railway ($5 per month).

Is This How A Free Tier Works?

With zero adoption, the value of a new service can’t be seen behind a paywall. Launching any kind of product or service with a freemium pricing model is often used to bring awareness to the product and entice early adopters who might convert into paying customers to help offset the costs of those on the free plan. It’s the old Pareto, or 80/20, rule, where 20% of paying customers ought to pay for the 80% of free users.

A conversion rate is the percentage of users that upgrade from a free tier to a paid one, and an “average” rate depends on the type of free tier or trial being offered.

In a freemium model — without sales assist — a good conversion rate is somewhere between 3–5%, but that’s optimistic. Conversion rates are often way lower in reality and perhaps the toughest to improve for startups with few or no customers. Early on, startups often have so few paying customers that they will have to operate at a loss until figuring out a way to land paying customers who can subsidize the ones who aren’t paying anything.

The longer a company operates at a loss, the more likely it races to generate the highest possible growth before undoubtedly having to cut benefits for free users.

A lot of those free users will feel misled and migrate to another service, but once the audience is big enough, a company can afford to lose free customers in favor of the minority that will switch to premium. Take Evernote, for example. The note-taking app allowed free users to save 100,000 notes and 250 notebooks only to do an about-face in 2023 and limit free users to 50 notes and one notebook.

In principle, a free tier serves the same purpose for SaaS (Software as a System) and PaaS (Product as a System) offerings, but the effects differ. For one, cloud computing costs lots of money, so offering an AWS wrapper in a free tier is significantly harder to sustain. The real difference between SaaS and PaaS, however, is clear when the company decides to kill off its free tier.

Let’s take Zoom as a SaaS example: there is a basic tier that gives you up to 40 minutes of free meeting time, and that is plenty for people who simply don’t need much beyond that. If Zoom were to remove its free tier, free users would most likely move to other freemium alternatives like Google Meet rather than upgrade to one of Zoom’s paid tiers. Those customers have invested nothing in Zoom that locks them in, so the cost of switching to another meeting app is only the learning curve of what app they switch to.

This is in contrast to a PaaS; if the free tier is removed, switching providers introduces costs since a part of your architecture lives in the provider’s free tier. Besides the effort needed to migrate to another provider, moving data and servers can be an expensive operation, thanks to data egress fees. Data egress fees are obscure charges that cloud providers make customers pay for moving data from one service to another. They charge you to stop paying!

Thankfully, there is an increased awareness of this issue through the European Union’s Data Act that requires cloud providers located in Europe to remove barriers that prevent customers from easily switching between companies, including the removal of artificial egress fees.

The Ethics Of The Free Tier

Is it the developer’s fault for hosting a project on a free pricing tier, considering that it can be rolled out at any moment? I have two schools of thought on this: principle and consequential.

  • Principle
    On the one hand, you shouldn’t have to expect a company to pull the rug out from under you by removing a free tier, especially if the company aims to be a reliable and sustainable platform.
  • Consequential
    On the other hand, you don’t expect someone to cut a red light and hit you when you are driving, but you still look at both sides of the street. So it is with using a free tier. Even if it is “immoral” for a company to remove the tier, a developer ought to have a backup plan in the event that it happens, especially as the disappearance of free tiers becomes more prevalent in the industry.

I think it boils down to a matter of transparency. No free tier is advertised as something that may disappear, even if it will in the future. In this case, a free tier is supposed to be another tier with fewer benefits than the paid plan offerings but just as reliable as the most expensive plan, so no user should expect to migrate their projects to other providers any time soon.

What’s The Alternative?

Offering customers a free tier only to remove it once the company gets a “healthy enough” share of the market is just wrong, particularly if it was never attached to an up-front sunset date.

Pretending that the purpose of a free tier is the same as a free trial is unjust since it surely isn’t advertised that way.

If a company wants to give people a taste of how a product or service works, then I think there are far better and more sincere alternatives to the free-tier pricing model:

  • Free trials (opt-in)
    Strapi is an open-source CMS and a perfect example of a service offering a free trial. In 2023, the company released a cloud provider to host Strapi CMS with zero configuration. Even though I think Strapi Cloud is on the pricey side, I still appreciate having a 14-day free trial over a free tier that can or maybe will be removed later. The free trial gives users enough time to get a feel for the product, and there’s no credit card required that would lock someone in (because, let’s face it, some companies count on you forgetting to cancel your free subscription before payments kick in).

  • Free credits
    I have used Railway to host Node.js + Postgres in the past. I think that its “free tier” is the best example of how to help customers try the service: the cheapest plan is a relatively affordable $5 per month, and a new subscriber is credited with $5 to start the project and evaluate the service, again, without the requirement of handing over credit card information or pulling any rugs out from under people. Want to continue your service after the free credits are exhausted? Buy more credits!

Railway is a particular case because it used to have a free tier, but it was withdrawn on June 2, 2023. However, the company removed it with a level of care and concern for customers that PlanetScale lacked and even gave customers who relied on the free tier a trial account with a number of free credits. It is also important to note (and I can’t get over it) that PlanetScale’s new cheapest plan is $39 per month, while Railway was able to limit the damage to $5 per month.

Free Tiers That I Use

I don’t want this article to be just a listicle of free services but rather the start of a conversation about the “free-tier dilemma”. I also want to share some of the free tiers I use, even for small but production-ready projects.

Supabase

You can make pretty much any imaginable web app using Supabase as the back-end since it brings a PostgreSQL database, authentication, real-time subscriptions, and storage in a central dashboard — complete with a generous allocation of database usage in its free tier.

Railway

I have been using Railway to host Strapi CMS for a long time. Aside from its beautiful UI, Railway includes seamless deployment workflows, automatic scaling, built-in CI/CD pipelines, and integration with popular frameworks and databases thanks to its hundreds of templates. It doesn’t include a free tier per se, but you can get the full feel of Railway with the $5 credit they offer.

GitHub Pages

I use GitHub Pages the way I know many of you do as well: for static pages and technical demos. I have used it before to make live examples for my blog posts. So, it’s more of a playground that I use to make a few artifacts when I need to deploy something fast, but I don’t rely on it for anything that would be of consequence if it were to suddenly go away.

Netlify

Beyond hosting, Netlify offers support for almost all modern frameworks, not to mention that they toss in lots of additional perks, including solid documentation, continuous deployment, templates, an edge network, and analytics — all of which are available in a free tier that pleases almost anyone’s needs.

Conclusion

If it isn’t totally clear where I fall on the free pricing tier situation, I’m not advocating that we end the practice, but for more transparency on the side of the companies that offer free tier plans and increased awareness on the side of developers like myself.

I believe that the only way it makes sense to offer a free tier for a SaaS/PaaS is for the company providing it to view it as part of the core product, one that cannot be sunset without a clear and transparent exit strategy, clearly communicated up-front during any sort of registration process. Have a plan for users to painlessly switch services. Allow the customer to make an informed choice and accept responsibility from there.

Free tiers should attract users rather than trap them, and there is an abysmal difference between replacing a free tier for $5 per month with one that costs nearly $40. Taking away the service is one thing; charging exorbitant rates on top of it only adds insult to injury.

We can do better here, and there are plenty of alternatives to free tiers for effectively marketing a product.

Further Reading On SmashingMag

Conducting Accessibility Research In An Inaccessible Ecosystem

Ensuring technology is accessible and inclusive relies heavily on receiving feedback directly from disabled users. You cannot rely solely on checklists, guidelines, and good-faith guesses to get things right. This is often hindered, however, by a lack of accessible prototypes available to use during testing.

Rather than wait for the digital landscape to change, researchers should leverage all the available tools they can use to create and replicate the testing environments they need to get this important research completed. Without it, we will continue to have a primarily inaccessible and not inclusive technology landscape that will never be disrupted.

Note: I use “identity first” disability language (as in “disabled people”) rather than “people first” language (as in “people with disabilities”). Identity first language aligns with disability advocates who see disability as a human trait description or even community and not a subject to be avoided or shamed. For more, review “Writing Respectfully: Person-First and Identity-First Language”.

Accessibility-focused Research In All Phases

When people advocate that UX Research should include disabled participants, it’s often with the mindset that this will happen on the final product once development is complete. One primary reason is because that’s when researchers have access to the most accessible artifact with which to run the study. However,

The real ability to ensure an accessible and inclusive system is not by evaluating a final product at the end of a project; it’s by assessing user needs at the start and then evaluating the iterative prototypes along the way.

Prototype Research Should Include Disabled Participants

In general, the iterative prototype phase of a project is when teams explore various design options and make decisions that will influence the final project outcome. Gathering feedback from representative users during this phase can help teams make informed decisions, including key pivots before significant development and testing resources are used.

During the prototype phase of user testing, the representative users should include disabled participants. By collecting feedback and perspectives of people with a variety of disabilities in early design testing phases, teams can more thoughtfully incorporate key considerations and supplement accessibility guidelines with real-world feedback. This early-and-often approach is the best way to include accessibility and inclusivity into a process and ensure a more accessible final product.

If you instead wait to include disabled participants in research until a product is near final, this inevitably leads to patchwork fixes of any critical feedback. Then, for feedback not deemed critical, it will likely get “backlogged” where the item priorities compete with new feature updates. With this approach, you’ll constantly be playing catch-up rather than getting it right up front and in an elegant and integrated way.

Accessibility Research Can’t Wait Until The End

Not only does research with disabled participants often occur too late in a project, but it is also far too often viewed as separate from other research studies (sometimes referred to as the “main research”). It cannot be understated that this reinforces the notion of separate-and-not-equal as compared to non-disabled participants and other stakeholder feedback. This has a severe negative impact on how a team will view the priority of inclusive design and, more broadly, the value of disabled people. That is, this reinforces “ableism”, a devaluing of disabled people in society.

UX Research with diverse participants that include a wide variety of disabilities can go a long way in dismantling ableist views and creating vitally needed inclusive technology.

The problem is that even when a team is on board with the idea, it’s not always easy to do inclusive research, particularly when involving prototypes. While discovery research can be conducted with minimal tooling and summative research can leverage fully built and accessible systems, prototype research quickly reveals severe accessibility barriers that feel like they can’t be overcome.

Inaccessible Technology Impedes Accessibility Research

Most technology we use has accessibility barriers for users with disabilities. As an example, the WebAIM Million report consistently finds that 96% of web homepages have accessibility errors that are fixable and preventable.

Just like websites, web, and mobile applications are similarly inaccessible, including those that produce early-stage prototypes. Thus, the artifacts researchers might want to use for prototype testing to help create accessible products are themselves inaccessible, creating a barrier for disabled research participants. It quickly becomes a vicious cycle that seems hard to break.

The Limitations Of Figma

Currently, the most popular industry tool for initial prototyping is Figma. These files become the artifacts researchers use to conduct a research study. However, these files often fall short of being accessible enough for many participants with disabilities.

To be clear, I absolutely applaud the Figma employees who have worked very hard on including screen reader support and keyboard functionality in Figma prototypes. This represents significant progress towards removing accessibility barriers in our core products and should not be overlooked. Nevertheless, there are still limitations and even blockers to research.

For one, the Figma files must be created in a way that will mimic the website layout and code. For example, for screen reader navigation to be successful, the elements need to be in their correct reading order in the Layers panel (not solely look correct visually), include labeled elements such as buttons (not solely items styled to look like buttons), and include alternative text for images. Often, however, designers do not build iterative prototypes with these considerations in mind, which prevents the keyboard from navigating correctly and the screen reader from providing the necessary details to comprehend the page.

In addition, Figma’s prototypes do not have selectable, configurable text. This prevents key visual adjustments such as browser zoom to increase text size, dark mode, which is easier for some to view, and selecting text to have it read aloud. If a participant needs these kinds of adjustments (or others I list in the table below), a Figma prototype will not be accessible to them.

Table: Figma prototype limitations per assistive technology

Assistive Technology Disability Category Limitation
Keyboard-only navigation Mobility Must use proper element type (such as button or input) in expected page order to ensure operability
Screen reader Vision Must include structure to ensure readability:
  • Including elements in logical order to ensure correct reading order
  • Alternative text added to images
  • Descriptive names added for buttons
Dark mode/High contrast mode Low Vision
Neurodiversity
Not available
Browser zoom Low Vision
Neurodiversity
Mobility
Not available
Screen reader used with mouse hover
Read aloud software with text selection
Vision
Neurodiversity
Cannot be used
Voice control
Switch control device
Mobility Cannot be used

Inclusive Research Is Needed Regardless

Having accessibility challenges with a prototype doesn’t mean we give up on the research. Instead, it means we need to get creative in our approach. This research is too important to keep waiting for the ideal set-up, particularly when our findings are often precisely what’s needed to create accessible technology.

Part of crafting a research study is determining what artifact to use during the study. Thus, when considering prototype research, it is a matter of creating the artifact best suited for your study. If this isn’t going to be, say, a Figma file you receive from designers, then consider what else can be used to get the job done.

Working Around the Current State

Being able to include diverse perspectives from disabled research participants throughout a project’s creation is possible and necessary. Keeping in mind your research questions and the capabilities of your participants, there are research methods and strategies that can be made accessible to gather authentic feedback during the critical prototype design phase.

With that in mind, I propose five ways you can accomplish prototype research while working around inaccessible prototypes:

  1. Use a survey.
  2. Conduct a co-design session.
  3. Test with a similar system.
  4. Build your own rapid prototype.
  5. Use the Wizard of Oz method.

Use a Survey Instead

Not all research questions at this phase need a full working prototype to be answered, particularly if they are about the general product features or product wording and not the visual design. Oftentimes, a survey tool or similar type of evaluation can be just as effective.

For example, you can confirm a site’s navigation options are intuitive by describing a scenario with a list of navigation choices while also testing if key content is understandable by confirming the user’s next steps based on a passage of text.

Image description
+

Acme Company Website Survey

Complete this questionnaire to help us determine if our site will be understandable.

  1. Scenario: You want to find out this organization's mission statement. Which menu option do you choose?
    [List of radio buttons]
    • Home
    • About
    • Resources
    • Find an Office
    • Search
  2. The following describes directions for applying to our grant. After reading, answer the following question:

    The Council’s Grant serves to advance Acme's goals by sponsoring community events. In determining whether to fund an event, the Council also considers factors including, but not limited to:
    • Target audiences
    • Alignment with the Council’s goals and objectives
    • Evaluations measuring participant satisfaction
To apply, download the form below.

Based on this wording, what would you include in your grant application?
[Input Field]

Just be sure you build a WCAG-compliant survey that includes accessible form layouts and question types. This will ensure participants can navigate using their assistive technologies. For example, Qualtrics has a specific form layout that is built to be accessible, or check out these accessibility tips for Google Forms. If sharing a document, note features that will enhance accessibility, such as using the ribbon for styling in Microsoft Word.

Tip: To find accessibility documentation for the software you’re using, search in your favorite search engine for the product name plus the word “accessibility” to find a product’s accessibility documentation.

Conduct Co-design Sessions

The prototyping phase might be a good time to utilize co-design and participatory design methods. With these methods, you can co-create designs with participants using any variety of artifacts that match the capabilities of your participants along with your research goals. The feedback can range from high-level workflows to specific visual designs, and you can guide the conversation with mock-ups, equivalent systems, or more creative artifacts such as storyboards that illustrate a scenario for user reaction.

For the prototype artifacts, these can range from low- to high-fidelity. For instance, participants without mobility or vision impairments can use paper-and-pencil sketching or whiteboarding. People with somewhat limited mobility may prefer a tablet-based drawing tool, such as using an Apple pencil with an iPad. Participants with visual impairments may prefer more 3-dimensional tools such as craft supplies, modeling clay, and/or cardboard. Or you may find that simply working on a collaborative online document offers the best accessibility as users can engage with their personalized assistive technology to jot down ideas.

Notably, the types of artifacts you use will be beneficial across differing user groups. In fact, rather than limiting the artifacts, try to offer a variety of ways to provide feedback by default. By doing this, participants can feel more empowered and engaged by the activity while also reassuring them you have created an inclusive environment. If you’re not sure what options to include, feel free to confirm what methods will work best as you recruit participants. That is, as you describe the primary activity when they are signing up, you can ask if the materials you have will be operable for the participant or allow them to tell you what they prefer to use.

The discussion you have and any supplemental artifacts you use then depend on communication styles. For example, deaf participants may need sign language interpreters to communicate their views but will be able to see sample systems, while blind participants will need descriptions of key visual information to give feedback. The actual study facilitation comes down to who you are recruiting and what level of feedback you are seeking; from there, you can work through the accommodations that will work best.

I conducted two co-design sessions at two different project phases while exploring how to create a wearable blind pedestrian navigation device. Early in the project, when we were generally talking about the feature set, we brought in several low-fidelity supplies, including a Braille label maker, cardboard, clay, Velcro, clipboards, tape, paper, and pipe cleaners. Based on user feedback, I fashioned a clipboard hanging from pipe cleaners as one prototype.

Later in the project when we were discussing the size and weight, we taped together Arduino hardware pieces representing the features identified by the participants. Both outcomes are pictured below and featured in a paper entitled, “What Not to Wearable: Using Participatory Workshops to Explore Wearable Device Form Factors for Blind Users.”

Ultimately, the benefit of this type of study is the participant-led feedback. In this way, participants are giving unfiltered feedback that is less influenced by designers, which may lead to more thoughtful design in the end.

Test With an Equivalent System

Very few projects are completely new creations, and often, teams use an existing site or application for project inspiration. Consider using similar existing systems and equivalent scenarios for your testing instead of creating a prototype.

By using an existing live system, participants can then use their assistive technology and adaptive techniques, which can make the study more accessible and authentic. Also, the study findings can range from the desirability of the available product features to the accessibility and usability of individual page elements. These lessons can then inform what design and code decisions to make in your system.

One caveat is to be aware of any accessibility barriers in that existing system. Particularly for website and web applications, you can look for accessibility documentation to determine if the company has reported any WCAG-conformance accessibility efforts, use tools like WAVE to test the system yourself, and/or mimic how your participants will use the system with their assistive technology. If there are workarounds for what you find, you may be able to avoid certain parts of the application or help users navigate past the inaccessible parts. However, if the site is going to be completely unusable for your participants, this won’t be a viable option for you.

If the system is usable enough for your testing, however, you can take the testing a step further by making updates on the fly if you or someone you collaborate with has engineering experience. For example, you can manipulate a website’s code with developer tools to add, subtract, or change the elements and styling on a page in real-time. (See “About browser developer tools”.) This can further enhance the feedback you give to your teams as it may more closely match your team’s intended design.

Build a Rapid Website Prototype

Notably, when conducting research focused on physical devices and hardware, you will not face the same obstacles to inaccessibility as with websites and web applications. You can use a variety of materials to create your prototypes, from cardboard to fabric to 3D printed material. I’ve sewn haptic vibration modules to a makeshift leather bracelet when working with wearables, for instance.

However, for web testing, it may be necessary to build a rapid prototype, especially to work around inaccessible artifacts such as a Figma file. This will include using a site builder that allows you to quickly create a replica of your team’s website. To create an accessible website, you’ll need a site builder with accessibility features and capabilities; I recommend WordPress, SquareSpace, Webflow, and Google Sites.

I recently used Google Sites to create a replica of a client’s draft pages in a matter of hours. I was adamant we should include disabled participants in feedback loops early and often, and this included after a round of significant visual design and content decisions. The web agency building the client’s site used Figma but not with the required formatting to use the built-in screen reader functionality. Rather than leave out blind user feedback at such a crucial time in the project, I started with a similar Google Sites template, took a best guess at how to structure the elements such as headings, recreated the anticipated column and card layouts as best I could, and used placeholder images with projected alt text instead of their custom graphics.

The screen reader testing turned into an impromptu co-design session because I could make changes in-the-moment to the live site for the participant to immediately test out. For example, we determined that some places where I used headings were not necessary, and we talked about image alt text in detail. I was able to add specific design and code feedback to my report, as well as share the live site (and corresponding code) with the team for comparison.

The downside to my prototype was that I couldn’t create the exact 1-to-1 visual design to use when testing with the other disabled participants who were sighted. I wanted to gather feedback on colors, fonts, and wording, so I also recruited low vision and neurodiverse participants for the study. However, my data was skewed because those participants couldn’t make the visual adjustments they needed to fully take in the content, such as recoloring, resizing, and having text read aloud. This was unfortunate, but we at least used the prototype to spark discussions of what does make a page accessible for them.

You may find you are limited in how closely you can replicate the design based on the tools you use or lack of access to developer assistance. When facing these limitations, consider what is most important to evaluate and determine if a paired-down version of the site will still give you valuable feedback over no site at all.

Use Wizard of Oz

The Wizard of Oz (WoZ) research method involves the facilitators mimicking system interactions in place of a fully working system. With WoZ, you can create your system’s approximate functionality using equivalent accessible tools and processes.

As an example, I’ll refer you to the talk by an Ally Financial research team that used this method for participants who used screen readers. They pre-programmed screen reader prompts into a clickable spreadsheet and had participants describe aloud what keyboard actions they would take to then trigger the corresponding prompt. While not the ideal set-up for the participants or researchers, it at least brought screen reader user feedback (and recognition of the users themselves) to the early design phases of their work. For more, review their detailed talk “Removing bias with wizard of oz screen reader usability testing”.

This isn’t just limited to screen reader testing, however. In fact, I’ve also often used Wizard of Oz for Voice User Interface (VUI) design. For instance, when I helped create an Alexa “skill” (their name for an app on Amazon speech-enabled devices), our prototype wouldn’t be ready in time for user testing. So, I drafted an idea to use a Bluetooth speaker to announce prompts from a clickable spreadsheet instead. When participants spoke a command to the speaker (thinking it was an Alexa device), the facilitator would select the appropriate pre-recorded prompt or a generic “I don’t understand” message.

Any system can be mimicked when you break down its parts and pieces and think about the ultimate interaction for the user. Creating WoZ set-ups can take creativity and even significant time to put together, but the outcomes can be worth it, particularly for longer-term projects. Once the main pieces are created, the prototype set-up can be edited and reused indefinitely, including during the study or between participants. Also, the investment in an easily edited prototype pays off exponentially if it uncovers something prior to finishing the entire product. In fact, that’s the main goal of this phase of testing: to help teams know what to look out for before they go through the hard work of finishing the product.

Inclusive Research Can No Longer Wait

Much has been documented about inclusive design to help teams craft technology for the widest possible audience. From the Web Content Accessibility Guidelines that help define what it means to be accessible to the Microsoft Inclusive Design Toolkits that tell the human stories behind the guidelines, there is much to learn even before a product begins.

However, the best approach is with direct user feedback. With this, we must recognize the conundrum many researchers are facing: We want to include disabled participants in UX research prior to a product being complete, but often, prototypes we have available for testing are inaccessible. This means testing with something that is essentially broken and will negatively impact our findings.

While it may feel like researchers will always be at a disadvantage if we don’t have the tools we need for testing, I think, instead, it’s time for us to push back. I propose we do this on two fronts:

  1. We make the research work as best we can in the current state.
  2. We advocate for the tools we need to make this more streamlined.

The key is to get disabled perspectives on the record and in the dataset of team members making the decisions. By doing this, hopefully, we shift the culture to wanting and valuing this feedback and bringing awareness to what it takes to make it happen.

Ideally, the awareness raised from our bootstrap efforts will lead to more people helping reduce the current prototype barriers. For some of us, this means urging companies to prioritize accessibility features in their roadmaps. For those working within influential prototype companies, it can mean getting much-needed backing to innovate better in this area.

The current state of our inaccessible digital ecosystem can sometimes feel like an entanglement too big to unravel. However, we must remain steadfast and insist that this does not remain the status quo; disabled users are users, and their diverse and invaluable perspectives must be a part of our research outcomes at all phases.

Using AI For Neurodiversity And Building Inclusive Tools

In 1998, Judy Singer, an Australian sociologist working on biodiversity, coined the term “neurodiversity.” It means every individual is unique, but sometimes this uniqueness is considered a deficit in the eyes of neuro-typicals because it is uncommon. However, neurodiversity is the inclusivity of these unique ways of thinking, behaving, or learning.

Humans have an innate ability to classify things and make them simple to understand, so neurodivergence is classified as something different, making it much harder to accept as normal.

“Why not propose that just as biodiversity is essential to ecosystem stability, so neurodiversity may be essential for cultural stability?”

— Judy Singer

Culture is more abstract in the context of biodiversity; it has to do with values, thoughts, expectations, roles, customs, social acceptance, and so on; things get tricky.

Discoveries and inventions are driven by personal motivation. Judy Singer started exploring the concept of neurodiversity because her daughter was diagnosed with autism. Autistic individuals are people who are socially awkward but are very passionate about particular things in their lives. Like Judy, we have a moral obligation as designers to create products everyone can use, including these unique individuals. With the advancement of technology, inclusivity has become far more important. It should be a priority for every company.

As AI becomes increasingly tangled in our technology, we should also consider how being more inclusive will help, mainly because we must recognize such a significant number. AI allows us to design affordable, adaptable, and supportive products. Normalizing the phenomenon is far easier with AI, and it would help build personalized tools, reminders, alerts, and usage of language and its form.

We need to remember that these changes should not be made only for neurodiverse individuals; it would help everyone. Even neurotypicals have different ways of grasping information; some are kinesthetic learners, and others are auditory or visual.

Diverse thinking is just a different way of approaching and solving problems. Remember, many great minds are neurodiverse. Alan Turing, who cracked the code of enigma machines, was autistic. Fun fact: he was also the one who built the first AI machine. Steve Jobs, the founder and pioneer design thinker, had dyslexia. Emma Watson, famously known for her role as Hermione Granger from the Harry Potter series, has Attention-Deficit/Hyperactivity Disorder (ADHD). There are many more innovators and disruptors out there who are different.

Neurodivergence is a non-medical umbrella term.) used to classify brain function, behavior, and processing, which is different from normal. Let’s also keep in mind that these examples and interpretations are meant to shed some light on the importance of the neglected topic. It should be a reminder for us to invest further and investigate how we can make this rapidly growing technology in favor of this group as we try to normalize neurodiversity.

Types Of Neurodiversities
  • Autism: Autism spectrum disorder (ASD) is a neurological and developmental disorder that affects how people interact with others, communicate, learn, and behave.
  • Learning Disabilities
    The common learning disabilities:
  • Attention-Deficit/Hyperactivity Disorder (ADHD): An ongoing pattern of inattention and/or hyperactivity-impulsivity that interferes with functioning or development.
Making AI Technology More Neuro-inclusive

Artificial Intelligence (AI) enables machines to think and perform tasks. However, this thinking is based on algorithmic logic, and that logic is based on multiple examples, books, and information that AI uses to generate the resulting output. The network of information that AI mimics is just like our brains; it is called a neural network, so data processing is similar to how we process information in our brains to solve a problem.

We do not need to do anything special for neurodiversity, which is the beauty of AI technology in its current state. Everything already exists; it is the usage of the technology that needs to change.

There are many ways we could improve it. Let’s look at four ways that are crucial to get us started.

Workflow Improvements

For: Autistic and ADHD
Focus: Working memory

Gartner found that 80% of executives think automation can be applied to any business decision. Businesses realized that a tactical approach is less successful than a strategic approach to using AI. For example, it can support business decisions that would otherwise require a lot of manual research.

AI has played a massive role in automating various tasks till now and will continue to do so in the future; it helps users reduce the time they spend on repetitive aspects of their jobs. It saves users a lot of time to focus their efforts on things that matter. Mundane tasks get stacked in the working memory; however, there is a limit: humans can keep up to 3–5 ideas simultaneously. If there are more than five ideas at play, humans ought to forget or miss something unless they document it. When completing these typical but necessary tasks, it becomes time-consuming and frustrating for users to focus on their work. This is especially troublesome for neurodivergent employees.

Autistic and ADHD users might have difficulty following through or focusing on aspects of their work, especially if it does not interest them. Straying thoughts is not uncommon; it makes it even harder to concentrate. Autistic individuals are hyper-focused, preventing them from grasping other relevant information. On the contrary, ADHD users lose focus quickly as their attention span is limited, so their working memory takes a toll.

AI could identify this and help users overcome it. Improving and automating the workflow will allow them to focus on the critical tasks. It means less distractions and more direction. Since they have trouble with working memory, allowing the tool to assist them in capturing moments to help recall later would benefit them greatly.

Example That Can Be Improved

Zoom recently launched its AI companion. When a user joins a meeting as a host, they can use this tool for various actions. One of those actions is to summarize the meeting. It auto-generates meeting notes at the end and shares them. AI companion is an excellent feature for automating notes in the meeting, allowing all the participants to not worry about taking notes.

Opportunity: Along with the auto-generated notes, Zoom should allow users to take notes in-app and use them in their summaries. Sometimes, users get tangent thoughts or ideas that could be useful, and they can create notes. It should also allow users to choose the type of summary they want, giving them more control over it, e.g., short, simplified, or list. AI could also personalize this content to allow participants to comprehend it in their own way. Autistic users would benefit from their hyper-focused attention in the meeting. ADHD users can still capture those stray thoughts, which the AI will summarize in the notes. Big corporations usually are more traditional with incremental improvements. Small tech companies have less to lose, so we often see innovation there.

Neurodivergent Friendly Example

Fireflies.ai is an excellent example of how neuro-inclusivity can be considered, and it covers all the bases Zoom falls short of. It auto-generates meeting notes. It also allows participants to take notes, which are then appended to the auto-generated summary: this summary can be in a bullet list or a paragraph. The tool can also transcribe from the shared slide deck within the summary. It shares audio snippets of important points alongside the transcription. The product can support neurodivergent users far better.

Natural Language Processing

For: Autistic, Learning Disabilities, and ADHD
Focus: Use simple words and give emotional assistance

Words have different meanings for all. Some might understand the figurative language, but others might get offended by the choice of it. If this is so common with a neurotypical, imagine how tricky it will be for a neurodivergent. Autistic users have difficulty understanding metaphorical language and empathizing with others. Learning disabilities will have trouble with language, especially figurative language, which perplexes them. ADHD users have a short attention span, and using complex sentences would mean they will lose interest.

Using simple language aids users far better than complex sentence constructions for neurodivergent. Metaphors, jargon, or anecdotal information might be challenging to interpret and frustrate them. The frustration could avert them from pursuing things that they feel are complex. Providing them with a form of motivation by allowing them to understand and grow will enable them to pursue complexities confidently. AI could help multifold by breaking down the complex into straightforward language.

Example That Can Be Improved

Grammarly is a great tool for correcting and recommending language changes. It has grammatical and Grammarly-defined rules based on which the app makes recommendations. It also has a feature that allows users to select the tone of voice or goals, casual or academic style, enhancing the written language to the expectation. Grammarly also lets organizations define style guides; it could help the user write based on the organization’s expectations.

Opportunity: Grammarly still needs to implement a gen AI assistive technology, but that might change in the future. Large learning models (LLM) can further convert the text into inclusive language considering cultural and regional relevance. Most presets are specific to the rules Grammarly or the organization has defined, which is limiting. Sentimental analysis is still not a part of their rules. For example, if the write-up is supposed to be negative, the app recommends changing or making it positive.

Neurodivergent Friendly Example

Writer is another beautiful product that empowers users to follow guidelines established by the organization and, obviously, the grammatical rules. It provides various means to rewrite sentences that make sense, e.g., simplify, polish, shorten, and so on. Writers also assist with sentence reconstruction and recommendation based on the type of content the user writes, for instance, an error or a tooltip. Based on those features and many more under the gen AI list, Writer can perform better for neurodivergent users.

Cognitive Assistance

For: Autistic, Learning Disabilities, and ADHD
Focus: Suggestive technology

Equality Act 2010 was established to bring workplace equality with legislation on neurodiversity. Employers need to understand the additional needs of neurodivergent employees and make amendments to existing policies to incorporate them. The essence of the Equality Act can be translated into actionable digital elements to bring equality of usage of products.

Neurodiverse or not, cognitive differences are present in both groups. The gap becomes more significant when we talk about them separately. Think about it: all AI assistive technologies are cognition supplements.

Cognoassist did a study to understand cognition within people. They found that less than 10% of them score within a typical range of assessment. It proves that the difference is superficial, even if it is observable.

Cognition is not just intelligence but a runway of multiple mental processes, irrespective of the neural inclination. It is just a different way of cognition and reproduction than normal. Nonetheless, neurodivergent users need assistive technologies more than neuro-typicals; it fills the gap quickly. This will allow them to function at the same level by making technology more inclusive.

Example That Can Be Improved

ClickUp is a project management tool that has plenty of automation baked into it. It allows users to automate or customize their daily routine, which helps everyone on the team to focus on their goals. It also lets users connect various productivity and management apps to make it a seamless experience and a one-stop shop for everything they need. The caveat is that the automation is limited to some actions.

Opportunity: Neurodivergent users sometimes need more cognitive assistance than neuro-typicals. Initiating and completing tasks is difficult, and a push could help them get started or complete them. The tool could also help them with organization, benefiting them greatly. Autistic individuals prefer to complete a task in one go, while ADHD people like to mix it up as they get the necessary break from each task and refocus. An intelligent AI system could help users by creating more personalized planned days and a to-do list to get things started.

Neurodivergent Friendly Example

Motion focuses on planning and scheduling the user’s day to help with their productivity goals. When users connect their calendars to this tool, they can schedule their meetings with AI by considering heads-down time or focused attention sessions based on each user’s requirement. The user can personalize their entire schedule according to their liking. The tool will proactively schedule incoming meetings or make recommendations on time. This AI assistive technology also aids them with planning around deadlines.

Adaptive Onboarding

For: Learning Disabilities and ADHD
Focus: Reduce Frustration

According to Epsilon, 80% of consumers want a personalized experience. All of these personalization experiences are to make the user’s workflow easier. These personalized experiences start from the introduction to the usage of the product. Onboarding helps users learn about the product, but learning continues after the initial product presentation.

We cannot expect users to know about the product once the onboarding has been completed and they need assistance in the future. Over time, if users have a hard time comprehending or completing a task, they get frustrated; this is particularly true for ADHD users. At the same time, users with learning disabilities do not remember every step either because they are too complex or have multiple steps.

Adaptive onboarding will allow everyone to re-learn when needed; it would benefit them more since help is available when needed. This type of onboarding could be AI-driven and much more generative. It could focus on different learning styles, either assistive, audio, or video presentation.

Example That Can Be Improved:

Product Fruits has a plethora of offerings, including onboarding. It offers personalization and the ability to tailor the onboarding to cover the product for new users. Allowing customization with onboarding gives the product team more control over what needs attention. It also provides the capability to track product usage based on the onboarding.

Opportunity: Offering AI interventions for different personas or segments will give the tool an additional layer of experience tailored to the needs of individuals. Imagine a user with ADHD who is trying to figure out how to use the feature; they will get frustrated if they do not identify how to use it. What if the tool intuitively nudges the user on how to complete the task? Similarly, if completing the task is complex and requires multiple steps, users with learning disabilities have difficulty following and reproducing it.

Neurodivergent Friendly Example

Onboarding does not always need to be at the start of the product introduction. Users always end up in situations where they need to find a step in the feature of completing a task but might have difficulty discovering it. In such cases, they usually seek help by asking colleagues or looking it up on the product help page.

Chameleon helps by offering features that let users use AI more effectively. Users can ask for help anytime, and the AI will generate answers to help them.

Considerations

All the issues I mentioned are present in everyone; the difference is the occurrence and intensity between neurotypical and neurodiverse individuals. Everyday things, discussions, conclusions, critical thinking, comprehension, and so on, are vastly different. It is like neurodiverse individuals’ brains are wired differently. It becomes more important to build tools that solve problems for neurodiverse users, which we inadvertently solve for everyone.

An argument that every human goes through those problems is easy to make. But, we tend to forget the intensity and criticality of those problems for neurodiverse individuals, which is far too complex than shrugging it off like neuro-typicals who can adapt to it much more quickly. Similarly, AI too has to learn and understand the problems it needs to solve. It can be confusing for the algorithm to learn unless it does not have multiple examples.

Large Language Models (LLM) are trained on vast amounts of data, such as ChatGPT, for example. It is accurate most of the time; however, sometimes, it hallucinates and gives an inaccurate answer. That might be a considerable problem when no additional guidelines exist except for the LLM. As mentioned above, there is still a possibility in most cases, but having the company guidelines and information would help give correct results.

It could also mean the users will be more dependent on AI, and there is no harm in it. If neurodiverse individuals need assistance, there cannot be a human present all the time carrying the patience required every time. Being direct is an advantage of AI, which is helpful in the case of their profession.

Conclusion

Designers should create efficient workflows for neurodivergent users who are having difficulty with working memory, comprehending complex language, learning intricate details, and so on. AI could help by providing cognitive assistance and adaptive technologies that benefit neurodivergent users greatly. Neurodiversity should be considered in product design; it needs more attention.

AI has become increasingly tied in every aspect of the user’s lives. Some are obvious, like conversational UI, chatbots, and so on, while others are hidden algorithms like recommendation engines.

Many problems specific to accessibility are being solved, but are they being solved while keeping neurodiverse issues in mind?

Jamie Diamon famously said:

“Problems don’t age well.”

— Jamie Diamon (CEO, JP Morgan)

This means we have to take critical issues into account sooner. Building an inclusive world for those 1.6 billion people is not a need for the future but a necessity of the present. We should strive to create an inclusive world for neurodiverse users; it is especially true because AI is booming, and making it inclusive now would be easy as it will scale into a behemoth set of features in every aspect of our lives in the future.

Converting Plain Text To Encoded HTML With Vanilla JavaScript

When copying text from a website to your device’s clipboard, there’s a good chance that you will get the formatted HTML when pasting it. Some apps and operating systems have a “Paste Special” feature that will strip those tags out for you to maintain the current style, but what do you do if that’s unavailable?

Same goes for converting plain text into formatted HTML. One of the closest ways we can convert plain text into HTML is writing in Markdown as an abstraction. You may have seen examples of this in many comment forms in articles just like this one. Write the comment in Markdown and it is parsed as HTML.

Even better would be no abstraction at all! You may have also seen (and used) a number of online tools that take plainly written text and convert it into formatted HTML. The UI makes the conversion and previews the formatted result in real time.

Providing a way for users to author basic web content — like comments — without knowing even the first thing about HTML, is a novel pursuit as it lowers barriers to communicating and collaborating on the web. Saying it helps “democratize” the web may be heavy-handed, but it doesn’t conflict with that vision!

We can build a tool like this ourselves. I’m all for using existing resources where possible, but I’m also for demonstrating how these things work and maybe learning something new in the process.

Defining The Scope

There are plenty of assumptions and considerations that could go into a plain-text-to-HTML converter. For example, should we assume that the first line of text entered into the tool is a title that needs corresponding <h1> tags? Is each new line truly a paragraph, and how does linking content fit into this?

Again, the idea is that a user should be able to write without knowing Markdown or HTML syntax. This is a big constraint, and there are far too many HTML elements we might encounter, so it’s worth knowing the context in which the content is being used. For example, if this is a tool for writing blog posts, then we can limit the scope of which elements are supported based on those that are commonly used in long-form content: <h1>, <p>, <a>, and <img>. In other words, it will be possible to include top-level headings, body text, linked text, and images. There will be no support for bulleted or ordered lists, tables, or any other elements for this particular tool.

The front-end implementation will rely on vanilla HTML, CSS, and JavaScript to establish a small form with a simple layout and functionality that converts the text to HTML. There is a server-side aspect to this if you plan on deploying it to a production environment, but our focus is purely on the front end.

Looking At Existing Solutions

There are existing ways to accomplish this. For example, some libraries offer a WYSIWYG editor. Import a library like TinyMCE with a single <script> and you’re good to go. WYSIWYG editors are powerful and support all kinds of formatting, even applying CSS classes to content for styling.

But TinyMCE isn’t the most efficient package at about 500 KB minified. That’s not a criticism as much as an indication of how much functionality it covers. We want something more “barebones” than that for our simple purpose. Searching GitHub surfaces more possibilities. The solutions, however, seem to fall into one of two categories:

  • The input accepts plain text, but the generated HTML only supports the HTML <h1> and <p> tags.
  • The input converts plain text into formatted HTML, but by ”plain text,” the tool seems to mean “Markdown” (or a variety of it) instead. The txt2html Perl module (from 1994!) would fall under this category.

Even if a perfect solution for what we want was already out there, I’d still want to pick apart the concept of converting text to HTML to understand how it works and hopefully learn something new in the process. So, let’s proceed with our own homespun solution.

Setting Up The HTML

We’ll start with the HTML structure for the input and output. For the input element, we’re probably best off using a <textarea>. For the output element and related styling, choices abound. The following is merely one example with some very basic CSS to place the input <textarea> on the left and an output <div> on the right:

See the Pen Base Form Styles [forked] by Geoff Graham.

You can further develop the CSS, but that isn’t the focus of this article. There is no question that the design can be prettier than what I am providing here!

Capture The Plain Text Input

We’ll set an onkeyup event handler on the <textarea> to call a JavaScript function called convert() that does what it says: convert the plain text into HTML. The conversion function should accept one parameter, a string, for the user’s plain text input entered into the <textarea> element:

<textarea onkeyup='convert(this.value);'></textarea>

onkeyup is a better choice than onkeydown in this case, as onkeyup will call the conversion function after the user completes each keystroke, as opposed to before it happens. This way, the output, which is refreshed with each keystroke, always includes the latest typed character. If the conversion is triggered with an onkeydown handler, the output will exclude the most recent character the user typed. This can be frustrating when, for example, the user has finished typing a sentence but cannot yet see the final punctuation mark, say a period (.), in the output until typing another character first. This creates the impression of a typo, glitch, or lag when there is none.

In JavaScript, the convert() function has the following responsibilities:

  1. Encode the input in HTML.
  2. Process the input line-by-line and wrap each individual line in either a <h1> or <p> HTML tag, whichever is most appropriate.
  3. Process the output of the transformations as a single string, wrap URLs in HTML <a> tags, and replace image file names with <img> elements.

And from there, we display the output. We can create separate functions for each responsibility. Let’s name them accordingly:

  1. html_encode()
  2. convert_text_to_HTML()
  3. convert_images_and_links_to_HTML()

Each function accepts one parameter, a string, and returns a string.

Encoding The Input Into HTML

Use the html_encode() function to HTML encode/sanitize the input. HTML encoding refers to the process of escaping or replacing certain characters in a string input to prevent users from inserting their own HTML into the output. At a minimum, we should replace the following characters:

  • < with &lt;
  • > with &gt;
  • & with &amp;
  • ' with &#39;
  • " with &quot;

JavaScript does not provide a built-in way to HTML encode input as other languages do. For example, PHP has htmlspecialchars(), htmlentities(), and strip_tags() functions. That said, it is relatively easy to write our own function that does this, which is what we’ll use the html_encode() function for that we defined earlier:

function html_encode(input) {
  const textArea = document.createElement("textarea");
  textArea.innerText = input;
  return textArea.innerHTML.split("<br>").join("\n");
}

HTML encoding of the input is a critical security consideration. It prevents unwanted scripts or other HTML manipulations from getting injected into our work. Granted, front-end input sanitization and validation are both merely deterrents because bad actors can bypass them. But we may as well make them work a little harder.

As long as we are on the topic of securing our work, make sure to HTML-encode the input on the back end, where the user cannot interfere. At the same time, take care not to encode the input more than once. Encoding text that is already HTML-encoded will break the output functionality. The best approach for back-end storage is for the front end to pass the raw, unencoded input to the back end, then ask the back-end to HTML-encode the input before inserting it into a database.

That said, this only accounts for sanitizing and storing the input on the back end. We still have to display the encoded HTML output on the front end. There are at least two approaches to consider:

  1. Convert the input to HTML after HTML-encoding it and before it is inserted into a database.
    This is efficient, as the input only needs to be converted once. However, this is also an inflexible approach, as updating the HTML becomes difficult if the output requirements happen to change in the future.
  2. Store only the HTML-encoded input text in the database and dynamically convert it to HTML before displaying the output for each content request.
    This is less efficient, as the conversion will occur on each request. However, it is also more flexible since it’s possible to update how the input text is converted to HTML if requirements change.
Applying Semantic HTML Tags

Let’s use the convert_text_to_HTML() function we defined earlier to wrap each line in their respective HTML tags, which are going to be either <h1> or <p>. To determine which tag to use, we will split the text input on the newline character (\n) so that the text is processed as an array of lines rather than a single string, allowing us to evaluate them individually.

function convert_text_to_HTML(txt) {
  // Output variable
  let out = '';
  // Split text at the newline character into an array
  const txt_array = txt.split("\n");
  // Get the number of lines in the array
  const txt_array_length = txt_array.length;
  // Variable to keep track of the (non-blank) line number
  let non_blank_line_count = 0;

  for (let i = 0; i < txt_array_length; i++) {
    // Get the current line
    const line = txt_array[i];
    // Continue if a line contains no text characters
    if (line === ''){
      continue;
    }

    non_blank_line_count++;
    // If a line is the first line that contains text
    if (non_blank_line_count === 1){
      // ...wrap the line of text in a Heading 1 tag
      out += &lt;h1&gt;${line}&lt;/h1&gt;;
      // ...otherwise, wrap the line of text in a Paragraph tag.
    } else {
      out += &lt;p&gt;${line}&lt;/p&gt;;
    }
  }

  return out;
}

In short, this little snippet loops through the array of split text lines and ignores lines that do not contain any text characters. From there, we can evaluate whether a line is the first one in the series. If it is, we slap a <h1> tag on it; otherwise, we mark it up in a <p> tag.

This logic could be used to account for other types of elements that you may want to include in the output. For example, perhaps the second line is assumed to be a byline that names the author and links up to an archive of all author posts.

Tagging URLs And Images With Regular Expressions

Next, we’re going to create our convert_images_and_links_to_HTML() function to encode URLs and images as HTML elements. It’s a good chunk of code, so I’ll drop it in and we’ll immediately start picking it apart together to explain how it all works.


function convert_images_and_links_to_HTML(string){
  let urls_unique = [];
  let images_unique = [];
  const urls = string.match(/https*:\/\/[^\s<),]+[^\s<),.]/gmi) ?? [];
  const imgs = string.match(/[^"'>\s]+.(jpg|jpeg|gif|png|webp)/gmi) ?? [];

  const urls_length = urls.length;
  const images_length = imgs.length;

  for (let i = 0; i < urls_length; i++){
    const url = urls[i];
    if (!urls_unique.includes(url)){
      urls_unique.push(url);
    }
  }

  for (let i = 0; i < images_length; i++){
    const img = imgs[i];
    if (!images_unique.includes(img)){
      images_unique.push(img);
    }
  }

  const urls_unique_length = urls_unique.length;
  const images_unique_length = images_unique.length;

  for (let i = 0; i < urls_unique_length; i++){
    const url = urls_unique[i];
    if (images_unique_length === 0 || !images_unique.includes(url)){
      const a_tag = &lt;a href="${url}" target="&#95;blank"&gt;${url}&lt;/a&gt;;
      string = string.replace(url, a_tag);
    }
  }

  for (let i = 0; i < images_unique_length; i++){
    const img = images_unique[i];
    const img_tag = &lt;img src="${img}" alt=""&gt;;
    const img_link = &lt;a href="${img}"&gt;${img&#95;tag}&lt;/a&gt;;
    string = string.replace(img, img_link);
  }
  return string;
}

Unlike the convert_text_to_HTML() function, here we use regular expressions to identify the terms that need to be wrapped and/or replaced with <a> or <img> tags. We do this for a couple of reasons:

  1. The previous convert_text_to_HTML() function handles text that would be transformed to the HTML block-level elements <h1> and <p>, and, if you want, other block-level elements such as <address>. Block-level elements in the HTML output correspond to discrete lines of text in the input, which you can think of as paragraphs, the text entered between presses of the Enter key.
  2. On the other hand, URLs in the text input are often included in the middle of a sentence rather than on a separate line. Images that occur in the input text are often included on a separate line, but not always. While you could identify text that represents URLs and images by processing the input line-by-line — or even word-by-word, if necessary — it is easier to use regular expressions and process the entire input as a single string rather than by individual lines.

Regular expressions, though they are powerful and the appropriate tool to use for this job, come with a performance cost, which is another reason to use each expression only once for the entire text input.

Remember: All the JavaScript in this example runs each time the user types a character, so it is important to keep things as lightweight and efficient as possible.

I also want to make a note about the variable names in our convert_images_and_links_to_HTML() function. images (plural), image (singular), and link are reserved words in JavaScript. Consequently, imgs, img, and a_tag were used for naming. Interestingly, these specific reserved words are not listed on the relevant MDN page, but they are on W3Schools.

We’re using the String.prototype.match() function for each of the two regular expressions, then storing the results for each call in an array. From there, we use the nullish coalescing operator (??) on each call so that, if no matches are found, the result will be an empty array. If we do not do this and no matches are found, the result of each match() call will be null and will cause problems downstream.

const urls = string.match(/https*:\/\/[^\s<),]+[^\s<),.]/gmi) ?? [];
const imgs = string.match(/[^"'>\s]+.(jpg|jpeg|gif|png|webp)/gmi) ?? [];

Next up, we filter the arrays of results so that each array contains only unique results. This is a critical step. If we don’t filter out duplicate results and the input text contains multiple instances of the same URL or image file name, then we break the HTML tags in the output. JavaScript does not provide a simple, built-in method to get unique items in an array that’s akin to the PHP array_unique() function.

The code snippet works around this limitation using an admittedly ugly but straightforward procedural approach. The same problem is solved using a more functional approach if you prefer. There are many articles on the web describing various ways to filter a JavaScript array in order to keep only the unique items.

We’re also checking if the URL is matched as an image before replacing a URL with an appropriate <a> tag and performing the replacement only if the URL doesn’t match an image. We may be able to avoid having to perform this check by using a more intricate regular expression. The example code deliberately uses regular expressions that are perhaps less precise but hopefully easier to understand in an effort to keep things as simple as possible.

And, finally, we’re replacing image file names in the input text with <img> tags that have the src attribute set to the image file name. For example, my_image.png in the input is transformed into <img src='my_image.png'> in the output. We wrap each <img> tag with an <a> tag that links to the image file and opens it in a new tab when clicked.

There are a couple of benefits to this approach:

  • In a real-world scenario, you will likely use a CSS rule to constrain the size of the rendered image. By making the images clickable, you provide users with a convenient way to view the full-size image.
  • If the image is not a local file but is instead a URL to an image from a third party, this is a way to implicitly provide attribution. Ideally, you should not rely solely on this method but, instead, provide explicit attribution underneath the image in a <figcaption>, <cite>, or similar element. But if, for whatever reason, you are unable to provide explicit attribution, you are at least providing a link to the image source.

It may go without saying, but “hotlinking” images is something to avoid. Use only locally hosted images wherever possible, and provide attribution if you do not hold the copyright for them.

Before we move on to displaying the converted output, let’s talk a bit about accessibility, specifically the image alt attribute. The example code I provided does add an alt attribute in the conversion but does not populate it with a value, as there is no easy way to automatically calculate what that value should be. An empty alt attribute can be acceptable if the image is considered “decorative,” i.e., purely supplementary to the surrounding text. But one may argue that there is no such thing as a purely decorative image.

That said, I consider this to be a limitation of what we’re building.

Displaying the Output HTML

We’re at the point where we can finally work on displaying the HTML-encoded output! We've already handled all the work of converting the text, so all we really need to do now is call it:

function convert(input_string) {
  output.innerHTML = convert_images_and_links_to_HTML(convert_text_to_HTML(html_encode(input_string)));
}

If you would rather display the output string as raw HTML markup, use a <pre> tag as the output element instead of a <div>:

<pre id='output'></pre>

The only thing to note about this approach is that you would target the <pre> element’s textContent instead of innerHTML:

function convert(input_string) {
  output.textContent = convert_images_and_links_to_HTML(convert_text_to_HTML(html_encode(input_string)));
}
Conclusion

We did it! We built one of the same sort of copy-paste tool that converts plain text on the spot. In this case, we’ve configured it so that plain text entered into a <textarea> is parsed line-by-line and encoded into HTML that we format and display inside another element.

See the Pen Convert Plain Text to HTML (PoC) [forked] by Geoff Graham.

We were even able to keep the solution fairly simple, i.e., vanilla HTML, CSS, and JavaScript, without reaching for a third-party library or framework. Does this simple solution do everything a ready-made tool like a framework can do? Absolutely not. But a solution as simple as this is often all you need: nothing more and nothing less.

As far as scaling this further, the code could be modified to POST what’s entered into the <form> using a PHP script or the like. That would be a great exercise, and if you do it, please share your work with me in the comments because I’d love to check it out.

References

How To Monitor And Optimize Google Core Web Vitals

This article is a sponsored by DebugBear

Google’s Core Web Vitals initiative has increased the attention website owners need to pay to user experience. You can now more easily see when users have poor experiences on your website, and poor UX also has a bigger impact on SEO.

That means you need to test your website to identify optimizations. Beyond that, monitoring ensures that you can stay ahead of your Core Web Vitals scores for the long term.

Let’s find out how to work with different types of Core Web Vitals data and how monitoring can help you gain a deeper insight into user experiences and help you optimize them.

What Are Core Web Vitals?

There are three web vitals metrics Google uses to measure different aspects of website performance:

  • Largest Contentful Paint (LCP),
  • Cumulative Layout Shift (CLS),
  • Interaction to Next Paint (INP).

Largest Contentful Paint (LCP)

The Largest Contentful Paint metric is the closest thing to a traditional load time measurement. However, LCP doesn’t track a purely technical page load milestone like the JavaScript Load Event. Instead, it focuses on what the user can see by measuring how soon after opening a page, the largest content element on the page appears.

The faster the LCP happens, the better, and Google rates a passing LCP score below 2.5 seconds.

Cumulative Layout Shift (CLS)

Cumulative Layout Shift is a bit of an odd metric, as it doesn’t measure how fast something happens. Instead, it looks at how stable the page layout is once the page starts loading. Layout shifts mean that content moves around, disorienting the user and potentially causing accidental clicks on the wrong UI element.

The CLS score is calculated by looking at how far an element moved and how big the element is. Aim for a score below 0.1 to get a good rating from Google.

Interaction to Next Paint (INP)

Even websites that load quickly often frustrate users when interactions with the page feel sluggish. That’s why Interaction to Next Paint measures how long the page remains frozen after user interaction with no visual updates.

Page interactions should feel practically instant, so Google recommends an INP score below 200 milliseconds.

What Are The Different Types Of Core Web Vitals Data?

You’ll often see different page speed metrics reported by different tools and data sources, so it’s important to understand the differences. We’ve published a whole article just about that, but here’s the high-level breakdown along with the pros and cons of each one:

  • Synthetic Tests
    These tests are run on-demand in a controlled lab environment in a fixed location with a fixed network and device speed. They can produce very detailed reports and recommendations.
  • Real User Monitoring (RUM)
    This data tells you how fast your website is for your actual visitors. That means you need to install an analytics script to collect it, and the reporting that’s available is less detailed than for lab tests.
  • CrUX Data
    Google collects from Chrome users as part of the Chrome User Experience Report (CrUX) and uses it as a ranking signal. It’s available for every website with enough traffic, but since it covers a 28-day rolling window, it takes a while for changes on your website to be reflected here. It also doesn’t include any debug data to help you optimize your metrics.
Start By Running A One-Off Page Speed Test

Before signing up for a monitoring service, it’s best to run a one-off lab test with a free tool like Google’s PageSpeed Insights or the DebugBear Website Speed Test. Both of these tools report with Google CrUX data that reflects whether real users are facing issues on your website.

Note: The lab data you get from some Lighthouse-based tools — like PageSpeed Insights — can be unreliable.

INP is best measured for real users, where you can see the elements that users interact with most often and where the problems lie. But a free tool like the INP Debugger can be a good starting point if you don’t have RUM set up yet.

How To Monitor Core Web Vitals Continuously With Scheduled Lab-Based Testing

Running tests continuously has a few advantages over ad-hoc tests. Most importantly, continuous testing triggers alerts whenever a new issue appears on your website, allowing you to start fixing them right away. You’ll also have access to historical data, allowing you to see exactly when a regression occurred and letting you compare test results before and after to see what changed.

Scheduled lab tests are easy to set up using a website monitoring tool like DebugBear. Enter a list of website URLs and pick a device type, test location, and test frequency to get things running:

As this process runs, it feeds data into the detailed dashboard with historical Core Web Vitals data. You can monitor a number of pages on your website or track the speed of your competition to make sure you stay ahead.

When regression occurs, you can dive deep into the results using DebuBears’s Compare mode. This mode lets you see before-and-after test results side-by-side, giving you context for identifying causes. You see exactly what changed. For example, in the following case, we can see that HTTP compression stopped working for a file, leading to an increase in page weight and longer download times.

How To Monitor Real User Core Web Vitals

Synthetic tests are great for super-detailed reporting of your page load time. However, other aspects of user experience, like layout shifts and slow interactions, heavily depend on how real users use your website. So, it’s worth setting up real user monitoring with a tool like DebugBear.

To monitor real user web vitals, you’ll need to install an analytics snippet that collects this data on your website. Once that’s done, you’ll be able to see data for all three Core Web Vitals metrics across your entire website.

To optimize your scores, you can go into the dashboard for each individual metric, select a specific page you’re interested in, and then dive deeper into the data.

For example, you can see whether a slow LCP score is caused by a slow server response, render blocking resources, or by the LCP content element itself.

You’ll also find that the LCP element varies between visitors. Lab test results are always the same, as they rely on a single fixed screen size. However, in the real world, visitors use a wide range of devices and will see different content when they open your website.

INP is tricky to debug without real user data. Yet an analytics tool like DebugBear can tell you exactly what page elements users are interacting with most often and which of these interactions are slow to respond.

Thanks to the new Long Animation Frames API, we can also see specific scripts that contribute to slow interactions. We can then decide to optimize these scripts, remove them from the page, or run them in a way that does not block interactions for as long.

Conclusion

Continuously monitoring Core Web Vitals lets you see how website changes impact user experience and ensures you get alerted when something goes wrong. While it’s possible to measure Core Web Vitals using a wide range of tools, those tools are limited by the type of data they use to evaluate performance, not to mention they only provide a single snapshot of performance at a specific point in time.

A tool like DebugBear gives you access to several different types of data that you can use to troubleshoot performance and optimize your website, complete with RUM capabilities that offer a historial record of performance for identifying issues where and when they occur. Sign up for a free DebugBear trial here.

Sliding 3D Image Frames In CSS

In a previous article, we played with CSS masks to create cool hover effects where the main challenge was to rely only on the <img> tag as our markup. In this article, pick up where we left off by “revealing” the image from behind a sliding door sort of thing — like opening up a box and finding a photograph in it.

This is because the padding has a transition that goes from s - 2*b to 0. Meanwhile, the background transitions from 100% (equivalent to --s) to 0. There’s a difference equal to 2*b. The background covers the entire area, while the padding covers less of it. We need to account for this.

Ideally, the padding transition would take less time to complete and have a small delay at the beginning to sync things up, but finding the correct timing won’t be an easy task. Instead, let’s increase the padding transition’s range to make it equal to the background.

img {
  --h: calc(var(--s) - var(--b));
  padding-top: min(var(--h), var(--s) - 2*var(--b));
  transition: --h 1s linear;
}
img:hover {
  --h: calc(-1 * var(--b));
}

The new variable, --h, transitions from s - b to -b on hover, so we have the needed range since the difference is equal to --s, making it equal to the background and clip-path transitions.

The trick is the min() function. When --h transitions from s - b to s - 2*b, the padding is equal to s - 2*b. No padding changes during that brief transition. Then, when --h reaches 0 and transitions from 0 to -b, the padding remains equal to 0 since, by default, it cannot be a negative value.

It would be more intuitive to use clamp() instead:

padding-top: clamp(0px, var(--h), var(--s) - 2*var(--b));

That said, we don’t need to specify the lower parameter since padding cannot be negative and will, by default, be clamped to 0 if you give it a negative value.

We are getting much closer to the final result!

First, we increase the border’s thickness on the left and bottom sides of the image:

img {
  --b: 10px; /* the image border */
  --d: 30px; /* the depth */

  border: solid #0000;
  border-width: var(--b) var(--b) calc(var(--b) + var(--d)) calc(var(--b) + var(--d));
}

Second, we add a conic-gradient() on the background to create darker colors around the box:

background: 
  conic-gradient(at left var(--d) bottom var(--d),
   #0000 25%,#0008 0 62.5%,#0004 0) 
  var(--c);

Notice the semi-transparent black color values (e.g., #0008 and #0004). The slight bit of transparency blends with the colors behind it to create the illusion of a dark variation of the main color since the gradient is placed above the background color.

And lastly, we apply a clip-path to cut out the corners that establish the 3D box.

clip-path: polygon(var(--d) 0, 100% 0, 100% calc(100% - var(--d)), calc(100% - var(--d)) 100%, 0 100%, 0 var(--d));

See the Pen The image within a 3D box by Temani Afif.

Now that we see and understand how the 3D effect is built let’s put back the things we removed earlier, starting with the padding:

See the Pen Putting back the padding animation by Temani Afif.

It works fine. But note how we’ve introduced the depth (--d) to the formula. That’s because the bottom border is no longer equal to b but b + d.

--h: calc(var(--s) - var(--b) - var(--d));
padding-top: min(var(--h),var(--s) - 2*var(--b) - var(--d));

Let’s do the same thing with the linear gradient. We need to decrease its size so it covers the same area as it did before we introduced the depth so that it doesn’t overlap with the conic gradient:

See the Pen Putting back the gradient animation by Temani Afif.

We are getting closer! The last piece we need to add back in from earlier is the clip-path transition that is combined with the box-shadow. We cannot reuse the same code we used before since we changed the clip-path value to create the 3D box shape. But we can still transition it to get the sliding result we want.

The idea is to have two points at the top that move up and down to reveal and hide the box-shadow while the other points remain fixed. Here is a small video to illustrate the movement of the points.

See that? We have five fixed points. The two at the top move to increase the area of the polygon and reveal the box shadow.

img {
  clip-path: polygon(
    var(--d) 0, /* --> var(--d) calc(-1*(var(--s) - var(--d))) */
    100%     0, /* --> 100%     calc(-1*(var(--s) - var(--d))) */

    /* the fixed points */ 
    100% calc(100% - var(--d)), /* 1 */
    calc(100% - var(--d)) 100%, /* 2 */
    0 100%,                     /* 3 */
    0 var(--d),                 /* 4 */
    var(--d) 0);                /* 5 */
}

And we’re done! We’re left with a nice 3D frame around the image element with a cover that slides up and down on hover. And we did it with zero extra markup or reaching for pseudo-elements!

See the Pen 3D image with reveal effect by Temani Afif.

And here is the first demo I shared at the start of this article, showing the two sliding variations.

See the Pen Image gift box (hover to reveal) by Temani Afif.

This last demo is an optimized version of what we did together. I have written most of the formulas using the variable --h so that I only update one value on hover. It also includes another variation. Can you reverse-engineer it and see how its code differs from the one we did together?

One More 3D Example

Want another fancy effect that uses 3D effects and sliding overlays? Here’s one I put together using a different 3D perspective where the overlay splits open rather than sliding from one side to the other.

See the Pen Image gift box II (hover to reveal) by Temani Afif.

Your homework is to dissect the code. It may look complex, but if you trace the steps we completed for the original demo, I think you’ll find that it’s not a terribly different approach. The sliding effect still combines the padding, the object-* properties, and clip-path but with different values to produce this new effect.

Conclusion

I hope you enjoyed this little 3D image experiment and the fancy effect we applied to it. I know that adding an extra element (i.e., a parent <div> as a wrapper) to the markup would have made the effect a lot easier to achieve, as would pseudo-elements and translations. But we are here for the challenge and learning opportunity, right?

Limiting the HTML to only a single element allows us to push the limits of CSS to discover new techniques that can save us time and bytes, especially in those situations where you might not have direct access to modify HTML, like when you’re working in a CMS template. Don’t look at this as an over-complicated exercise. It’s an exercise that challenges us to leverage the power and flexibility of CSS.

Connecting With Users: Applying Principles Of Communication To UX Research

Communication is in everything we do. We communicate with users through our research, our design, and, ultimately, the products and services we offer. UX practitioners and those working on digital product teams benefit from understanding principles of communication and their application to our craft. Treating our UX processes as a mode of communication between users and the digital environment can help unveil in-depth, actionable insights.

In this article, I’ll focus on UX research. Communication is a core component of UX research, as it serves to bridge the gap between research insights, design strategy, and business outcomes. UX researchers, designers, and those working with UX researchers can apply key aspects of communication theory to help gather valuable insights, enhance user experiences, and create more successful products.

Fundamentals of Communication Theory

Communications as an academic field encompasses various models and principles that highlight the dynamics of communication between individuals and groups. Communication theory examines the transfer of information from one person or group to another. It explores how messages are transmitted, encoded, and decoded, acknowledges the potential for interference (or ‘noise’), and accounts for feedback mechanisms in enhancing the communication process.

In this article, I will focus on the Transactional Model of Communication. There are many other models and theories in the academic literature on communication. I have included references at the end of the article for those interested in learning more.

The Transactional Model of Communication (Figure 1) is a two-way process that emphasizes the simultaneous sending and receiving of messages and feedback. Importantly, it recognizes that communication is shaped by context and is an ongoing, evolving process. I’ll use this model and understanding when applying principles from the model to UX research. You’ll find that much of what is covered in the Transactional Model would also fall under general best practices for UX research, suggesting even if we aren’t communications experts, much of what we should be doing is supported by research in this field.

Understanding the Transactional Model

Let’s take a deeper dive into the six key factors and their applications within the realm of UX research:

  1. Sender: In UX research, the sender is typically the researcher who conducts interviews, facilitates usability tests, or designs surveys. For example, if you’re administering a user interview, you are the sender who initiates the communication process by asking questions.
  2. Receiver: The receiver is the individual who decodes and interprets the messages sent by the sender. In our context, this could be the user you interview or the person taking a survey you have created. They receive and process your questions, providing responses based on their understanding and experiences.
  3. Message: This is the content being communicated from the sender to the receiver. In UX research, the message can take various forms, like a set of survey questions, interview prompts, or tasks in a usability test.
  4. Channel: This is the medium through which the communication flows. For instance, face-to-face interviews, phone interviews, email surveys administered online, and usability tests conducted via screen sharing are all different communication channels. You might use multiple channels simultaneously, for example, communicating over voice while also using a screen share to show design concepts.
  5. Noise: Any factor that may interfere with the communication is regarded as ‘noise.’ In UX research, this could be complex jargon that confuses respondents in a survey, technical issues during a remote usability test, or environmental distractions during an in-person interview.
  6. Feedback: The communication received by the receiver, who then provides an output, is called feedback. For example, the responses given by a user during an interview or the data collected from a completed survey are types of feedback or the physical reaction of a usability testing participant while completing a task.
Applying the Transactional Model of Communication to Preparing for UX Research

We can become complacent or feel rushed to create our research protocols. I think this is natural in the pace of many workplaces and our need to deliver results quickly. You can apply the lens of the Transactional Model of Communication to your research preparation without adding much time. Applying the Transactional Model of Communication to your preparation should:

  • Improve Clarity
    The model provides a clear representation of communication, empowering the researcher to plan and conduct studies more effectively.
  • Minimize misunderstanding
    By highlighting potential noise sources, user confusion or misunderstandings can be better anticipated and mitigated.
  • Enhance research participant participation
    With your attentive eye on feedback, participants are likely to feel valued, thus increasing active involvement and quality of input.

You can address the specific elements of the Transactional Model through the following steps while preparing for research:

Defining the Sender and Receiver

In UX research, the sender can often be the UX researcher conducting the study, while the receiver is usually the research participant. Understanding this dynamic can help researchers craft questions or tasks more empathetically and efficiently. You should try to collect some information on your participant in advance to prepare yourself for building a rapport.

For example, if you are conducting contextual inquiry with the field technicians of an HVAC company, you’ll want to dress appropriately to reflect your understanding of the context in which your participants (receivers) will be conducting their work. Showing up dressed in formal attire might be off-putting and create a negative dynamic between sender and receiver.

Message Creation

The message in UX research typically is the questions asked or tasks assigned during the study. Careful consideration of tenor, terminology, and clarity can aid data accuracy and participant engagement. Whether you are interviewing or creating a survey, you need to double-check that your audience will understand your questions and provide meaningful answers. You can pilot-test your protocol or questionnaire with a few representative individuals to identify areas that might cause confusion.

Using the HVAC example again, you might find that field technicians use certain terminology in a different way than you expect, such as asking them about what “tools” they use to complete their tasks yields you an answer that doesn’t reflect digital tools you’d find on a computer or smartphone, but physical tools like a pipe and wrench.

Choosing the Right Channel

The channel selection depends on the method of research. For instance, face-to-face methods might use physical verbal communication, while remote methods might rely on emails, video calls, or instant messaging. The choice of the medium should consider factors like tech accessibility, ease of communication, reliability, and participant familiarity with the channel. For example, you introduce an additional challenge (noise) if you ask someone who has never used an iPhone to test an app on an iPhone.

Minimizing Noise

Noise in UX research comes in many forms, from unclear questions inducing participant confusion to technical issues in remote interviews that cause interruptions. The key is to foresee potential issues and have preemptive solutions ready.

Facilitating Feedback

You should be prepared for how you might collect and act on participant feedback during the research. Encouraging regular feedback from the user during UX research ensures their understanding and that they feel heard. This could range from asking them to ‘think aloud’ as they perform tasks or encouraging them to email queries or concerns after the session. You should document any noise that might impact your findings and account for that in your analysis and reporting.

Track Your Alignment to the Framework

You can track what you do to align your processes with the Transactional Model prior to and during research using a spreadsheet. I’ll provide an example of a spreadsheet I’ve used in the later case study section of this article. You should create your spreadsheet during the process of preparing for research, as some of what you do to prepare should align with the factors of the model.

You can use these tips for preparation regardless of the specific research method you are undertaking. Let’s now look closer at a few common methods and get specific on how you can align your actions with the Transactional Model.

Applying the Transactional Model to Common UX Research Methods

UX research relies on interaction with users. We can easily incorporate aspects of the Transactional Model of Communication into our most common methods. Utilizing the Transactional Model in conducting interviews, surveys, and usability testing can help provide structure to your process and increase the quality of insights gathered.

Interviews

Interviews are a common method used in qualitative UX research. They provide the perfect method for applying principles from the Transactional Model. In line with the Transactional Model, the researcher (sender) sends questions (messages) in-person or over the phone/computer medium (channel) to the participant (receiver), who provides answers (feedback) while contending with potential distraction or misunderstanding (noise). Reflecting on communication as transactional can help remind us we need to respect the dynamic between ourselves and the person we are interviewing. Rather than approaching an interview as a unidirectional interrogation, researchers need to view it as a conversation.

Applying the Transactional Model to conducting interviews means we should account for a number of facts to allow for high-quality communication. Note how the following overlap with what we typically call best practices.

Asking Open-ended Questions

To truly harness a two-way flow of communication, open-ended questions, rather than close-ended ones, are crucial. For instance, rather than asking, “Do you use our mobile application?” ask, “Can you describe your use of our mobile app?”. This encourages the participant to share more expansive and descriptive insights, furthering the dialogue.

Actively Listening

As the success of an interview relies on the participant’s responses, active listening is a crucial skill for UX researchers. The researcher should encourage participants to express their thoughts and feelings freely. Reflective listening techniques, such as paraphrasing or summarizing what the participant has shared, can reinforce to the interviewee that their contributions are being acknowledged and valued. It also provides an opportunity to clarify potential noise or misunderstandings that may arise.

Being Responsive

Building on the simultaneous send-receive nature of the Transactional Model, researchers must remain responsive during interviews. Providing non-verbal cues (like nodding) and verbal affirmations (“I see,” “Interesting”) lets participants know their message is being received and understood, making them feel comfortable and more willing to share.

Minimizing Noise

We should always attempt to account for noise in advance, as well as during our interview sessions. Noise, in the form of misinterpretations or distractions, can disrupt effective communication. Researchers can proactively reduce noise by conducting a dry run in advance of the scheduled interviews. This helps you become more fluent at going through the interview and also helps identify areas that might need improvement or be misunderstood by participants. You also reduce noise by creating a conducive interview environment, minimizing potential distractions, and asking clarifying questions during the interview whenever necessary.

For example, if a participant uses a term the researcher doesn’t understand, the researcher should politely ask for clarification rather than guessing its meaning and potentially misinterpreting the data.

Additional forms of noise can include participant confusion or distraction. You should let participants know to ask if they are unclear on anything you say or do. It’s a good idea to always ask participants to put their smartphones on mute. You should only provide information critical to the process when introducing the interview or tasks. For example, you don’t need to give a full background of the history of the product you are researching if that isn’t required for the participant to complete the interview. However, you should let them know the purpose of the research, gain their consent to participate, and inform them of how long you expect the session to last.

Strategizing the Flow

Researchers should build strategic thinking into their interviews to support the Transaction Model. Starting the interview with less intrusive questions can help establish rapport and make the participant more comfortable, while more challenging or sensitive questions can be left for later when the interviewee feels more at ease.

A well-planned interview encourages a fluid dialogue and exchange of ideas. This is another area where conducting a dry run can help to ensure high-quality research. You and your dry-run participants should recognize areas where questions aren’t flowing in the best order or don’t make sense in the context of the interview, allowing you to correct the flow in advance.

While much of what the Transactional Model informs for interviews already aligns with common best practices, the model would suggest we need to have a deeper consideration of factors that we can sometimes give less consideration when we become overly comfortable with interviewing or are unaware of the implications of forgetting to address the factors of context considerations, power dynamics, and post-interview actions.

Context Considerations

You need to account for both the context of the participant, e.g., their background, demographic, and psychographic information, as well as the context of the interview itself. You should make subtle yet meaningful modifications depending on the channel you are conducting an interview.

For example, you should utilize video and be aware of your facial and physical responses if you are conducting an interview using an online platform, whereas if it’s a phone interview, you will need to rely on verbal affirmations that you are listening and following along, while also being mindful not to interrupt the participant while they are speaking.

Power Dynamics

Researchers need to be aware of how your role, background, and identity might influence the power dynamics of the interview. You can attempt to address power dynamics by sharing research goals transparently and addressing any potential concerns about bias a participant shares.

We are responsible for creating a safe and inclusive space for our interviews. You do this through the use of inclusive language, listening actively without judgment, and being flexible to accommodate different ways of knowing and expressing experiences. You should also empower participants as collaborators whenever possible. You can offer opportunities for participants to share feedback on the interview process and analysis. Doing this validates participants’ experiences and knowledge and ensures their voices are heard and valued.

Post-Interview Actions

You have a number of options for actions that can close the loop of your interviews with participants in line with the “feedback” the model suggests is a critical part of communication. Some tactics you can consider following your interview include:

  • Debriefing
    Dedicate a few minutes at the end to discuss the participant’s overall experience, impressions, and suggestions for future interviews.
  • Short surveys
    Send a brief survey via email or an online platform to gather feedback on the interview experience.
  • Follow-up calls
    Consider follow-up calls with specific participants to delve deeper into their feedback and gain additional insight if you find that is warranted.
  • Thank you emails
    Include a “feedback” section in your thank you email, encouraging participants to share their thoughts on the interview.

You also need to do something with the feedback you receive. Researchers and product teams should make time for reflexivity and critical self-awareness.

As practitioners in a human-focused field, we are expected to continuously examine how our assumptions and biases might influence our interviews and findings.

We shouldn’t practice our craft in a silo. Instead, seeking feedback from colleagues and mentors to maintain ethical research practices should be a standard practice for interviews and all UX research methods.

By considering interviews as an ongoing transaction and exchange of ideas rather than a unidirectional Q&A, UX researchers can create a more communicative and engaging environment. You can see how models of communication have informed best practices for interviews. With a better knowledge of the Transactional Model, you can go deeper and check your work against the framework of the model.

Surveys

The Transactional Model of Communication reminds us to acknowledge the feedback loop even in seemingly one-way communication methods like surveys. Instead of merely sending out questions and collecting responses, we need to provide space for respondents to voice their thoughts and opinions freely. When we make participants feel heard, engagement with our surveys should increase, dropouts should decrease, and response quality should improve.

Like other methods, surveys involve the researcher(s) creating the instructions and questionnaire (sender), the survey, including any instructions, disclaimers, and consent forms (the message), how the survey is administered, e.g., online, in person, or pen and paper (the channel), the participant (receiver), potential misunderstandings or distractions (noise), and responses (feedback).

Designing the Survey

Understanding the Transactional Model will help researchers design more effective surveys. Researchers are encouraged to be aware of both their role as the sender and to anticipate the participant’s perspective as the receiver. Begin surveys with clear instructions, explaining why you’re conducting the survey and how long it’s estimated to take. This establishes a more communicative relationship with respondents right from the start. Test these instructions with multiple people prior to launching the survey.

Crafting Questions

The questions should be crafted to encourage feedback and not just a simple yes or no. You should consider asking scaled questions or items that have been statistically validated to measure certain attributes of users.

For example, if you were looking deeper at a mobile banking application, rather than asking, “Did you find our product easy to use?” you would want to break that out into multiple aspects of the experience and ask about each with a separate question such as “On a scale of 1–7, with 1 being extremely difficult and 7 being extremely easy, how would you rate your experience transferring money from one account to another?”.

Minimizing Noise

Reducing ‘noise,’ or misunderstandings, is crucial for increasing the reliability of responses. Your first line of defense in reducing noise is to make sure you are sampling from the appropriate population you want to conduct the research with. You need to use a screener that will filter out non-viable participants prior to including them in the survey. You do this when you correctly identify the characteristics of the population you want to sample from and then exclude those falling outside of those parameters.

Additionally, you should focus on prioritizing finding participants through random sampling from the population of potential participants versus using a convenience sample, as this helps to ensure you are collecting reliable data.

When looking at the survey itself, there are a number of recommendations to reduce noise. You should ensure questions are easily understandable, avoid technical jargon, and sequence questions logically. A question bank should be reviewed and tested before being finalized for distribution.

For example, question statements like “Do you use and like this feature?” can confuse respondents because they are actually two separate questions: do you use the feature, and do you like the feature? You should separate out questions like this into more than one question.

You should use visual aids that are relevant whenever possible to enhance the clarity of the questions. For example, if you are asking questions about an application’s “Dashboard” screen, you might want to provide a screenshot of that page so survey takers have a clear understanding of what you are referencing. You should also avoid the use of jargon if you are surveying a non-technical population and explain any terminology that might be unclear to participants taking the survey.

The Transactional Model suggests active participation in communication is necessary for effective communication. Participants can become distracted or take a survey without intending to provide thoughtful answers. You should consider adding a question somewhere in the middle of the survey to check that participants are paying attention and responding appropriately, particularly for longer surveys.

This is often done using a simple math problem such as “What is the answer to 1+1?” Anyone not responding with the answer of “2” might not be adequately paying attention to the responses they are providing and you’d want to look closer at their responses, eliminating them from your analysis if deemed appropriate.

Encouraging Feedback

While descriptive feedback questions are one way of promoting dialogue, you can also include areas where respondents can express any additional thoughts or questions they have outside of the set question list. This is especially useful in online surveys, where researchers can’t immediately address participant’s questions or clarify doubts.

You should be mindful that too many open-ended questions can cause fatigue, so you should limit the number of open-ended questions. I recommend two to three open-ended questions depending on the length of your overall survey.

Post-Survey Actions

After collecting and analyzing the data, you can send follow-up communications to the respondents. Let them know the changes made based on their feedback, thank them for their participation, or even share a summary of the survey results. This fulfills the Transactional Model’s feedback loop and communicates to the respondent that their input was received, valued, and acted upon.

You can also meet this suggestion by providing an email address for participants to follow up if they desire more information post-survey. You are allowing them to complete the loop themselves if they desire.

Applying the transactional model to surveys can breathe new life into the way surveys are conducted in UX research. It encourages active participation from respondents, making the process more interactive and engaging while enhancing the quality of the data collected. You can experiment with applying some or all of the steps listed above. You will likely find you are already doing much of what’s mentioned, however being explicit can allow you to make sure you are thoughtfully applying these principles from the field communication.

Usability Testing

Usability testing is another clear example of a research method highlighting components of the Transactional Model. In the context of usability testing, the Transactional Model of Communication’s application opens a pathway for a richer understanding of the user experience by positioning both the user and the researcher as sender and receiver of communication simultaneously.

Here are some ways a researcher can use elements of the Transactional Model during usability testing:

Task Assignment as Message Sending

When a researcher assigns tasks to a user during usability testing, they act as the sender in the communication process. To ensure the user accurately receives the message, these tasks need to be clear and well-articulated. For example, a task like “Register a new account on the app” sends a clear message to the user about what they need to do.

You don’t need to tell them how to do the task, as usually, that’s what we are trying to determine from our testing, but if you are not clear on what you want them to do, your message will not resonate in the way it is intended. This is another area where a dry run in advance of the testing is an optimal solution for making sure tasks are worded clearly.

Observing and Listening as Message Receiving

As the participant interacts with the application, concept, or design, the researcher, as the receiver, picks up on verbal and nonverbal cues. For instance, if a user is clicking around aimlessly or murmuring in confusion, the researcher can take these as feedback about certain elements of the design that are unclear or hard to use. You can also ask the user to explain why they are giving these cues you note as a way to provide them with feedback on their communication.

Real-time Interaction

The transactional nature of the model recognizes the importance of real-time interaction. For example, if during testing, the user is unsure of what a task means or how to proceed, the researcher can provide clarification without offering solutions or influencing the user’s action. This interaction follows the communication flow prescribed by the transactional model. We lose the ability to do this during unmoderated testing; however, many design elements are forms of communication that can serve to direct users or clarify the purpose of an experience (to be covered more in article two).

Noise

In usability testing, noise could mean unclear tasks, users’ preconceived notions, or even issues like slow software response. Acknowledging noise can help researchers plan and conduct tests better. Again, carrying out a pilot test can help identify any noise in the main test scenarios, allowing for necessary tweaks before actual testing. Other forms of noise can be less obvious but equally intrusive. For example, if you are conducting a test using a Macbook laptop and your participant is used to a PC, there is noise you need to account for, given their unfamiliarity with the laptop you’ve provided.

The fidelity of the design artifact being tested might introduce another form of noise. I’ve always advocated testing at any level of fidelity, but you should note that if you are using “Lorem Ipsum” or black and white designs, this potentially adds noise.

One of my favorite examples of this was a time when I was testing a financial services application, and the designers had put different balances on the screen; however, the total for all balances had not been added up to the correct total. Virtually every person tested noted this discrepancy, although it had nothing to do with the tasks at hand. I had to acknowledge we’d introduced noise to the testing. As at least one participant noted, they wouldn’t trust a tool that wasn’t able to total balances correctly.

Encouraging Feedback

Under the Transactional Model’s guidance, feedback isn’t just final thoughts after testing; it should be facilitated at each step of the process. Encouraging ‘think aloud’ protocols, where the user verbalizes their thoughts, reactions, and feelings during testing, ensures a constant flow of useful feedback.

You are receiving feedback throughout the process of usability testing, and the model provides guidance on how you should use that feedback to create a shared meaning with the participants. You will ultimately summarize this meaning in your report. You’ll later end up uncovering if this shared meaning was correctly interpreted when you design or redesign the product based on your findings.

We’ve now covered how to apply the Transactional Model of Communication to three common UX Research methods. All research with humans involves communication. You can break down other UX methods using the Model’s factors to make sure you engage in high-quality research.

Analyzing and Reporting UX Research Data Through the Lens of the Transactional Model

The Transactional Model of Communication doesn’t only apply to the data collection phase (interviews, surveys, or usability testing) of UX research. Its principles can provide valuable insights during the data analysis process.

The Transactional Model instructs us to view any communication as an interactive, multi-layered dialogue — a concept that is particularly useful when unpacking user responses. Consider the ‘message’ components: In the context of data analysis, the messages are the users’ responses. As researchers, thinking critically about how respondents may have internally processed the survey questions, interview discussion, or usability tasks can yield richer insights into user motivations.

Understanding Context

Just as the Transactional Model emphasizes the simultaneous interchange of communication, UX researchers should consider the user’s context while interpreting data. Decoding the meaning behind a user’s words or actions involves understanding their background, experiences, and the situation when they provide responses.

Deciphering Noise

In the Transactional Model, noise presents a potential barrier to effective communication. Similarly, researchers must be aware of snowballing themes or frequently highlighted issues during analysis. Noise, in this context, could involve patterns of confusion, misunderstandings, or consistently highlighted problems by users. You need to account for this, e.g., the example I provided where participants constantly referred to the incorrect math on static wireframes.

Considering Sender-Receiver Dynamics

Remember that as a UX researcher, your interpretation of user responses will be influenced by your understandings, biases, or preconceptions, just as the responses were influenced by the user’s perceptions. By acknowledging this, researchers can strive to neutralize any subjective influence and ensure the analysis remains centered on the user’s perspective. You can ask other researchers to double-check your work to attempt to account for bias.

For example, if you come up with a clear theme that users need better guidance in the application you are testing, another researcher from outside of the project should come to a similar conclusion if they view the data; if not, you should have a conversation with them to determine what different perspectives you are each bringing to the data analysis.

Reporting Results

Understanding your audience is crucial for delivering a persuasive UX research presentation. Tailoring your communication to resonate with the specific concerns and interests of your stakeholders can significantly enhance the impact of your findings. Here are some more details:

  • Identify Stakeholder Groups
    Identify the different groups of stakeholders who will be present in your audience. This could include designers, developers, product managers, and executives.
  • Prioritize Information
    Prioritize the information based on what matters most to each stakeholder group. For example, designers might be more interested in usability issues, while executives may prioritize business impact.
  • Adapt Communication Style
    Adjust your communication style to align with the communication preferences of each group. Provide technical details for developers and emphasize user experience benefits for executives.

Acknowledging Feedback

Respecting this Transactional Model’s feedback loop, remember to revisit user insights after implementing design changes. This ensures you stay user-focused, continuously validating or adjusting your interpretations based on users’ evolving feedback. You can do this in a number of ways. You can reconnect with users to show them updated designs and ask questions to see if the issues you attempted to resolve were resolved.

Another way to address this without having to reconnect with the users is to create a spreadsheet or other document to track all the recommendations that were made and reconcile the changes with what is then updated in the design. You should be able to map the changes users requested to updates or additions to the product roadmap for future updates. This acknowledges that users were heard and that an attempt to address their pain points will be documented.

Crucially, the Transactional Model teaches us that communication is rarely simple or one-dimensional. It encourages UX researchers to take a more nuanced, context-aware approach to data analysis, resulting in deeper user understanding and more accurate, user-validated results.

By maintaining an ongoing feedback loop with users and continually refining interpretations, researchers can ensure that their work remains grounded in real user experiences and needs.

Tracking Your Application of the Transactional Model to Your Practice

You might find it useful to track how you align your research planning and execution to the framework of the Transactional Model. I’ve created a spreadsheet to outline key factors of the model and used this for some of my work. Demonstrated below is an example derived from a study conducted for a banking client that included interviews and usability testing. I completed this spreadsheet during the process of planning and conducting interviews. Anonymized data from our study has been furnished to show an example of how you might populate a similar spreadsheet with your information.

You can customize the spreadsheet structure to fit your specific research topic and interview approach. By documenting your application of the transactional model, you can gain valuable insights into the dynamic nature of communication and improve your interview skills for future research.

Stage Columns Description Example
Pre-Interview Planning Topic/Question (Aligned with research goals) Identify the research question and design questions that encourage open-ended responses and co-construction of meaning. Testing mobile banking app’s bill payment feature. How do you set up a new payee? How would you make a payment? What are your overall impressions?
Participant Context Note relevant demographic and personal information to tailor questions and avoid biased assumptions. 35-year-old working professional, frequent user of the online banking and mobile application but unfamiliar with using the app for bill pay.
Engagement Strategies Outline planned strategies for active listening, open-ended questions, clarification prompts, and building rapport. Open-ended follow-up questions (“Can you elaborate on XYZ? Or Please explain more to me what you mean by XYZ.”), active listening cues, positive reinforcement (“Thank you for sharing those details”).
Shared Understanding List potential challenges to understanding participant’s perspectives and strategies for ensuring shared meaning. Initially, the participant expressed some confusion about the financial jargon I used. I clarified and provided simpler [non-jargon] explanations, ensuring we were on the same page.
During Interview Verbal Cues Track participant’s language choices, including metaphors, pauses, and emotional expressions. Participant used a hesitant tone when describing negative experiences with the bill payment feature. When questioned, they stated it was “likely their fault” for not understanding the flow [it isn’t their fault].
Nonverbal Cues Note participant’s nonverbal communication like body language, facial expressions, and eye contact. Frowning and crossed arms when discussing specific pain points.
Researcher Reflexivity Record moments where your own biases or assumptions might influence the interview and potential mitigation strategies. Recognized my own familiarity with the app might bias my interpretation of users’ understanding [e.g., going slower than I would have when entering information]. Asked clarifying questions to avoid imposing my assumptions.
Power Dynamics Identify instances where power differentials emerge and actions taken to address them. Participant expressed trust in the research but admitted feeling hesitant to criticize the app directly. I emphasized anonymity and encouraged open feedback.
Unplanned Questions List unplanned questions prompted by the participant’s responses that deepen understanding. What alternative [non-bank app] methods for paying bills that you use? (Prompted by participant’s frustration with app bill pay).
Post-Interview Reflection Meaning Co-construction Analyze how both parties contributed to building shared meaning and insights. Through dialogue, we collaboratively identified specific design flaws in the bill payment interface and explored additional pain points and areas that worked well.
Openness and Flexibility Evaluate how well you adapted to unexpected responses and maintained an open conversation. Adapted questioning based on participant’s emotional cues and adjusted language to minimize technical jargon when that issue was raised.
Participant Feedback Record any feedback received from participants regarding the interview process and areas for improvement. Thank you for the opportunity to be in the study. I’m glad my comments might help improve the app for others. I’d be happy to participate in future studies.
Ethical Considerations Reflect on whether the interview aligned with principles of transparency, reciprocity, and acknowledging power dynamics. Maintained anonymity throughout the interview and ensured informed consent was obtained. Data will be stored and secured as outlined in the research protocol.
Key Themes/Quotes Use this column to identify emerging themes or save quotes you might refer to later when creating the report. Frustration with a confusing interface, lack of intuitive navigation, and desire for more customization options.
Analysis Notes Use as many lines as needed to add notes for consideration during analysis. Add notes here.

You can use the suggested columns from this table as you see fit, adding or subtracting as needed, particularly if you use a method other than interviews. I usually add the following additional Columns for logistical purposes:

  • Date of Interview,
  • Participant ID,
  • Interview Format (e.g., in person, remote, video, phone).
Conclusion

By incorporating aspects of communication theory into UX research, UX researchers and those who work with UX researchers can enhance the effectiveness of their communication strategies, gather more accurate insights, and create better user experiences. Communication theory provides a framework for understanding the dynamics of communication, and its application to UX research enables researchers to tailor their approaches to specific audiences, employ effective interviewing techniques, design surveys and questionnaires, establish seamless communication channels during usability testing, and interpret data more effectively.

As the field of UX research continues to evolve, integrating communication theory into research practices will become increasingly essential for bridging the gap between users and design teams, ultimately leading to more successful products that resonate with target audiences.

As a UX professional, it is important to continually explore and integrate new theories and methodologies to enhance your practice. By leveraging communication theory principles, you can better understand user needs, improve the user experience, and drive successful outcomes for digital products and services.

Integrating communication theory into UX research is an ongoing journey of learning and implementing best practices. Embracing this approach empowers researchers to effectively communicate their findings to stakeholders and foster collaborative decision-making, ultimately driving positive user experiences and successful design outcomes.

References and Further Reading

The Things Users Would Appreciate In Mobile Apps

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.

The constraint is a challenge to make sure that whatever is sent to mobile devices is directly relevant to what users should need; 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.

/* A sample CSS snippet for a responsive main content */

/* Base Styles */
.main-content {
  container: main / inline-size;
}

.gallery {
  display: grid;
  gap: 1rem;
}

/* Container is wider than or equal to 700px */
@container main (width >= 700px) {
  .gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Container is wider than or equal to 1200px */
@container main (width >= 1200px) {
  .gallery {
    grid-template-columns: repeat(4, 1fr);
  }
}

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 have shown us just how unpredictable of a medium the web is, including what sort of device is displaying our work all the way down to a user’s individual preferences.

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.

I’ve been keeping track of the mobile interfaces I use and have started finding common patterns that feel 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.

Economically-Sized Forms

There are myriad problems that come up while completing mobile forms — e.g., small tap targets, lack of offline support, and incorrect virtual keyboards, to name a few — but it’s how a mobile form interacts with the device’s virtual keyboard that draws my ire the most.

The keyboard obscures the form more times than not. You tap a form field, and the keyboard slides up, and — poof! — it’s as though half the form is chopped out of view. If you’re thinking, Meh, all that means is a little extra scrolling, 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.

A more delightful user experience for mobile forms is to take a “less is more” approach. Display one form field at a time for an economical layout 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.

More Room For Searching

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?)

The problem with burying search in a mobile context is two-fold:

  1. The feature is less apparent, and
  2. The space to enter a search query, add any filters, and display results is minimized.

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.

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.

Some sites even have search forms that occupy the full screen without surrounding components, offering a “distraction-free” interface for typing queries.

No Drop-Downs, If Possible

The <select> element can negatively impact mobile UX in two glaring ways:

  1. An expanding <select> with so many options that it produces excessive scrolling.
  2. Scrolling excessively, particularly on long pages, produces an awkward situation where the page continues scrolling after already scrolling through the list of <option>s.

I’ll come across these situations, stare at my phone, and ask:

Do I really need a <select> populated with one hundred years to submit my birthdate?

Seems like an awful lot of trouble to sort through one hundred years compared to manually typing in a four-digit value myself.

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.

A Restrained Overview

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.

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.

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.

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.

My advice: Hide sensitive information until prompted by the user to display it.

It’s generally a good practice to obscure sensitive information and have a liberal definition of what constitutes sensitive information.

Shortcuts Provided In The Login Flow

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.

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 shortcut.

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).

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.

The Right Keyboard Configuration

All modern mobile operating systems are smart enough to tailor the virtual keyboard for specialized form inputs. A form field markup with type="email", for instance, triggers an onscreen keyboard that shows the “@" key in the primary view, preventing users from having to tap Shift to reveal it in a subsequent view. The same is true with URLs, where a “.com” key surfaces for inputs with type="url".

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, type=email, type=url, type=tel, and such.

<!-- Input Types for Virtual Keyboards -->
<input type="text">   <!-- default -->
<input type="tel">    <!-- numeric keyboard -->
<input type="number"> <!-- numeric keyboard -->
<input type="email">  <!-- displays @ key -->
<input type="url">    <!-- displays .com key -->
<input type="search"> <!-- displays search button -->
<input type="date">   <!-- displays date picker or wheel controls -->
Bigger Fonts With Higher Contrast

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.

The default size of body text is 16px 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 Human Interface Guidelines 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 typography tokens.

Scale Body Text (pt) Body Text (px)
Small 12pt 16px
Medium 14pt 18.66px
Large 16pt 21.33px

The real standard we ought to be looking at is the current WCAG 2.2, and here’s what it says on the topic:

“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.”

So, bottom line is that the bottom end of a font’s scale matches the web’s default 16px if we accept Android’s “Small” defaults. But even then, there are caveats because WCAG is more focused on contrast than size. 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.

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 “beat the “Reader Mode” button. 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.

Wrapping Up

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.

The point is that we’re in some kind of “post-responsive” era of web design, 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.

Iconography In Design Systems: Easy Troubleshooting And Maintenance

We all have an inherent tendency to like aesthetic and approachable things. That’s why any designer strives to deliver an intuitive and comprehensive design. And any designer knows it’s a pain in the neck, particularly when it comes to complex projects with lots of pages of design, components, and prototypes. As someone who has already traveled that road and learned quite a few valuable lessons, I have some wisdom to share with you.

Granted, tons of articles have been written about design systems, their complexity, and all the ways they can benefit a project. So, I’m not going to waste too many words on the matter. Instead, in this article, I want to dig deeper into iconography as part of a design system. Besides, I’ll share with you doable tips that will turn icon creation and maintenance into an enjoyable — or at least bearable — process. Shall we?

Design Systems: From Chaos To Order

Design Systems 101

Before we actually dive into the alluring and frightening world of iconography, I want to take some time to introduce you to the concept of a design system, as well as share my thoughts and rules that I follow while working with them.

If I were to call a design the way your interface speaks with your user, then — building on the metaphor — it would be fair to view a design system as a language.

Simply put, a design system is a functional set of defined technical standards, best user behavior practices, and navigational patterns that are used to build a pixel-perfect digital product.

It is a powerful designer tool that helps you make sure that you will end up with a coherent product and not a pathetic mess.

It seems that nowadays, designers are obliged to try their hand at creating or at least adopting a design system. So what exactly makes it an all-around beneficial thing for the designer lot? Let’s have a look:

  • The design system makes for the only source of truth since all the components are under one roof and are easily referable.
  • It hosts all the guidelines on how to implement existing components. And following the very same guidelines, designers can easily create new ones that match the former.
  • In the case of a two- (or more) designer team, a design system allows for visual consistency (which is crucial if your project is major and fast-evolving).
  • You can either use ready-made design components or alter them swiftly and in accordance with the guideline if any need arises.
  • You have access to a library of surefire design patterns, which greatly reduces the strain of coming up with new solutions.

That sounds like a treat, right? Still, creating a design system is generally viewed as an exceptionally time- and effort-consuming endeavor. If you do want to develop a design system, there is a way to make the process a bit easier. Enter the atomic design approach.

Atomic Design Approach

It’s been over six years since I first introduced the atomic approach into my workflow, and let me tell you, it was a game-changer for me as a designer. This methodology is a blessing if you work on a big project with a team of fellow designers.

If you know the pain of trying to track the changes in components throughout the projects, especially if these components are minor, then you’ll see why I’m so enthusiastic about the atomic approach. It allows for smooth and well-coordinated teamwork where every designer is aware of what component they are creating and how to make it consistent with the rest of the system.

The atomic design approach was pioneered by Brad Frost (a chemist of all occupations). It implies building your system brick-by-brick, starting with the smallest items and going all the way up while sustaining hierarchy. There are five stages to the process.

  • Atoms
    In a nutshell, these are basic HTML elements.

  • Molecules
    They are single-pattern components that do one thing.

  • Organisms
    They are composed of groups of molecules, or/and atoms, or/and other organisms.

  • Templates
    They provide a context for using molecules and organisms and focus on the page’s underlying content structure. In other words, templates are the guidelines.

  • Pages
    They show what a UI looks like with proper content.

What exactly makes this approach a thing designers gravitate towards? Here are my two cents on the matter:

  • Creating a design system resembles playing with a construction set. You begin with the smallest components and progress in size, which means you are eating the elephant a bite at a time and don’t get overwhelmed.
  • Altering a component in one place will cause updates wherever a certain atom, molecule, or organism is applied. This eliminates any need for manual tweaking of components.
  • This approach provides designers with design patterns, meaning that you no longer need to create new ones and worry about their consistency.

That’s clearly not all the advantages of this methodology, so if you are interested, go ahead and read more about it in Brad Frost’s book.

What I’m really willing to focus on is our job as designers in creating and maintaining those fabled design systems, both atomic and regular. More specifically, on iconography. And even more specifically, on the pitfalls we have a nasty habit of falling into when dealing with icons as the atoms of our systems. Off we go.

Iconography In Design Systems: Maladies and Remedies

Common Problems

Since I’m relying on my own experience when it comes to design systems, it would only be fair if I shared the biggest issues that I personally have with iconography in the context of design systems and how I solve them. I’ll share with you surefire tips on how to keep your iconography consistent and ensure its smooth integration into design environments.

If we regard a single icon from the atomic design standpoint, we would consider it an atom — the smallest but essential element, just like the color palette or typography. If we continue with our language analogy, I will take the liberty of calling icons a design’s vocabulary. So, it’s fairly clear that icons are the actual core of your design.

As any designer knows, users heavily rely on icons as an interactional element of an interface. Despite being the smallest of components, icons might prove to be a major pain in the neck in terms of creation. This is the lesson I have learned during my tenure as a UX designer.

Tip 1: Since an atom is not just an autonomous element, you have to think beforehand about how it will behave as part of a larger component, like a molecule, an organism, or a template.

These are the variables you have to keep in mind when developing an icon:

  • Is your icon scalable?
  • Does it have color variations?
  • Do you classify your icon according to meaning, group, style, or location?
  • Is there an option to change the icon’s meaning or style?
  • How can you easily introduce a new icon into an existing roster?
  • How should you navigate a situation when different designers develop icons separately?
  • How can you make locating a certain icon within your design system easier?

Here are some challenges that I personally face while developing iconography for a design system:

  • How should I keep track of icon updates and maintain their consistency?
  • How should I develop icon creation guidelines?
  • What should I do if current icons happen to be inconsistent?
  • How should I inform my design team of any changes?

It might be hard to wrap your head around so many questions, but worry not. I’ll try my best to cover all these issues as we go on.

Rules Of Thumb

An icon isn’t just a little pictogram with a certain meaning behind it. An icon is a symbol of action, an interactive element of a digital interface that helps users navigate through the system.

In other words, it is a tool, and the process of building a tool implies following rules. I found out firsthand that if you master the basic icon rules, then you’ll be able to build both stand-alone icons and those that are part of a larger environment with equal efficiency. Besides, you’ll enhance your ability to create icon sets and various icon types within a single project, all while maintaining their readability and accessibility.

Tip 2: Keep consistency by establishing the basic icon rules before building your icon library.

The following are the rules that I abide by:

Grid

I use the classic 24px grid for standard icons and a 44px grid for larger icons. Each grid consists of the padding area (marked in red, 2 px) and the live area (marked in blue, 20 px). The live area is the space that your icon content stays inside. Its shape depends on the icon’s body and could be circular, square, vertical-rectangular, or horizontal-rectangular.

Before you sit down to draw your icon, decide how much space your icon’s body will occupy in order to come up with the proper shape.

Size

Each icon within a design system has to have a primary size, which is the size that up to 90% of all icons share. I consider the 24px icon size suggested by Google’s Material Design to be the golden standard. This size works well both for desktop and mobile devices.

Still, there is room for exceptions in this rule when an icon needs to be smaller or larger. In this case, I employ a 4-pixel step rule. I increase or decrease the icon’s size by 4 pixels at a time (e.g., I go from 24 to 20, then 16, then 12 px, or 28, 32 px, and so on). I would still personally prefer the golden standard of 24 pixels since I find smaller sizes less readable or larger sizes too visually domineering.

Weight

Another key property to consider is the outline weight of your icon if you opt for this type. If you are building an icon library from scratch, it would be wise to test several outline weight values before you make a decision. This is especially crucial for icons that contain fine details.

Granted, you can assign different weight values to different types of icons, but you might struggle to write clear guidelines for your fellow designers. I usually make a conscious decision to go with a unified outline weight for all the icons, namely, 2 points.

Fill

A solid icon variant might considerably enhance the accessibility and readability of an interface icon. It’s really handy to have both solid and outline icon types. But not all your icons should have two options. If you choose to draw a solid option, determine what parts of your icon you want to make solid.

Design principles

As I’ve mentioned before, an icon is an essential interface element. This means that an icon should be simplistic, bold, and — what’s even more important in the context of design systems — created according to the unified rules.

I have a little trick I use to see how well a new icon fits the standard. I simply integrate the new icon into the interface populated by already existing elements. This helps determine if the new icon matches the rest.

Anatomy

Such aspects as corner, counterstroke, and stroke terminal provide the much-desired visual consistency. Obviously, all these elements should be unified for all the icons within a design system. A comprehensive guide to icon anatomy is available at Material Design.

Icon Consistency: Surefire Tips

Before I actually share my tips on how to deal with icons within a design system efficiently, here’s a little backstory to how I came up with them. It all started when I joined a project that already had an established host of icons. There were over a hundred of them. And the number grew because the project was a fast-evolving thing. So, the design system, like any other, was like a living being, constantly in a state of change.

The icon library was a mishmash of different icon types, creating quite a noise. The majority of icons differed in style, size, and application. Another problem I had was the fact that most of the icons did not have the source file. So, there was no way to quickly tweak an icon to match the rest.

The first and most important thing I did was to establish the basic rules for icon creation (that’s something we’ve already covered). This step was supposed to keep the design team from creating inconsistent icons.

Tip 3: Put all your icons on one layout. This way, you’ll get a full visual understanding of your icons and determine repetitive design patterns.

Now, here comes the juicy stuff. Here is my guide on how to sustain iconography in the context of a design system.

  • Divide your icons into subcategories.
    This rule works wonders when you have an array of inconsistent icons on your hands. There is no rule on what subcategories there should be. It all depends on your design system and the number of existing icons.
    In my case, I established three groups divided by size and icon style, which resulted in three subcategories: regular icons, detailed icons, and illustrations. Once you divide your icons in the same manner, it’ll be easier to apply the same rules to each group. Besides, this approach allows for a more structured storage of these icons within your design system.

  • Determine guidelines for each icon type.
    The next step is as wise as it is hard to pull off. You need to assign certain icon creation rules for each of the icon types (provided you have more than one). This is the basis upon which all your other attempts at achieving visual consistency will be built. To tame all the mismatched icons, I used the basic icon rules we’ve covered above. To keep track, I created a page in Figma for each of the icon types and used the basic size as the file name.

  • Group your icons wisely.
    When naming icons, I opt for the semantic section approach. Generally, you can divide all your icons into groups based on their meaning or application in the interface. Look at the example below; we have three distinct semantic sections: Transport, Services, and Warnings. Depending on their meaning, icons should be assigned to the corresponding sections. Then, those sections are, in turn, divided into subsections. For instance, the Transport section has Ground Transport and Air Transport. The main idea you should stick to is to keep your icons in separate sections.

  • Stick to clear names and descriptions.
    I have to admit that dividing icons into semantic sections does have a massive disadvantage: this division could be quite subjective. This is why it is crucial to add a detailed description to each of the icons. This will simplify icon search within a design system and will give a clear understanding of an icon’s application. This is how I create a description:
    • Tags: reference words that facilitate searching for an icon within the system.
    • Usage: a brief description of an icon’s application.
    • Group Name: the name of the group an icon belongs to. This helps with locating an icon right within the library.
    • Designs: an incredibly nifty tool that allows you to insert a link to the design and documentation that features the icon in question. This way, you’ll know the context in which the icon is applied.

  • Use color coding and symbols while updating icon design.
    This trick works best when you are not yet done with the icon library, but you need to communicate to your team which icons are ready to use and which still need a bit of enhancement. For instance, I mark the names of finished icons with a green symbol. An orange symbol marks those icons that need to be improved. And in case I need an icon deleted or drawn anew, I use a red cross.

  • Keep non-rasterised versions of icons.
    It can be handy to have a non-rasterised version of an icon at arm’s length. There’ve been cases when I was asked to create a similar icon or an icon that could use the same graphic forms as the existing ones. Should this happen again, I can simply take the original file and easily draw an icon. I store all the non-rasterised icons on a separate page in the file following the defined hierarchy.

  • Rasterise the icon vector.
    Be sure to apply the Outline Stroke feature before you create the icon component. This will allow for easy color change (more on this in the next tip) and scaling.

  • Mind the colors of your icons.
    I suggest keeping icons in the primary, most commonly used color by default. Another worthwhile thing to do is to name all icon colors according to their intended use and the interactions they perform. In order to do that, you need to equip your color library with a separate set of colors for all icon states, like primary, hover, and disabled. Make sure to name each set properly.

  • Assign a designer to maintain icons in the system.
    This is a seemingly trivial tip that, however, will save you trouble maintaining style and categorization consistency. I’ve personally had edge cases when the established rules failed. Having a designated designer who knew their way around the system helped to find a quick solution.

Real Example Of Guidelines Applied

To wrap up this whole lecture and actually see all these rules in action, take a look at the following template file.

Final Thoughts: Is It Worth It?

No matter how sick you might be dealing with unending visual inconsistency, design systems are still challenging. They can scare any designer regardless of their experience. Still, if you want to bring order to chaos, introducing a design system into your workflow is worth the trouble, especially when it comes to maintaining iconography.

After all, iconography is the most volatile part of a design system in terms of visual variety. That’s why iconography was the biggest challenge I had to face in my tenure as a designer. And that’s exactly why I am genuinely proud that I’ve tamed that beast and can now share my hacks with you.

Resources

Public design systems:

Design systems resources:

Icons resources:

How Developers Can Strengthen Their Mental Health Amidst High-Pressure Projects

I have had my fair share of projects that have given me life because of what I accomplished, as well as those that have cost me life when I reflect on the terrible stress they caused. I know I’m not unique that way; sometimes, my work makes me feel like a rock star and other times, I question whether I should be a developer at all. Some projects test you — like really test you.

In the first week of December 2023, I got a contract to rebuild an entire web app from the ground-up using a new technology designed to be launched alongside a “new year, new system” initiative heading into 2024.

I think you know where this is going. I built up a lot of confidence heading into the project but soon found that I had bitten off way more than I could chew. The legacy code I inherited was the epitome of “legacy” code, and its spaghetti nature needed more than one developer to suss out. The project looked doomed from the beginning, and I hadn’t even written a line of code!

I quit the job. After weeks of stress-laden sleep, I simply couldn’t stomach the work. I actually dreaded work altogether. And with that dread came doubts about my career and whether I should start looking outside the industry.

Is this starting to sound familiar?

That job wasn’t just a project that posed a personal challenge; no, it was a battle for my mental health. I was officially burned out. Thankfully, I was relieved of some pressure when, to my surprise, the client was weirdly understanding and offered to bring in an additional developer to share the load. That really helped, and it gave me what I needed to roll my sleeves back up and finish the job.

Is This Success?

The project launched, and the client was happy with it. But I still experience aftershocks, even today, where the trauma from that contract seeps back in and reminds me just how awful it was and the extent to which it made me question my entire career.

So, even though the project was ultimately a success, I wouldn’t say it was “successful.” There was a real non-monetary cost that I paid just for taking the job.

I’m sure it is the same for you. We’ve all had stressful projects that push us to the brink of what feels like self-destruction. It’s clear because there are so many other articles and blog posts about it, all offering insightful personal advice for relieving stress, like exercise, sleep, and eating right.

In fact, as I reflected back on projects that predated this one particular nightmare, I realized there had been other projects I’d taken that had likely contributed to the burnout. Interestingly, I found a few common threads between them that I now use as “warning flags” going into new work.

All of our experiences are unique to us, and there is no standard recipe for managing stress and protecting your mental health. Advice in this area is always best described as “your mileage may vary” for no other reason than that it is scoped to a specific individual. True, one’s experiences can go so far as to help someone through a tough situation. I find it’s the same thing with self-help books — the best advice is usually the same advice found elsewhere, only articulated better or in a way that resonates with you.

Think of this article as more of my personal story of experiences safeguarding my mental health when finding myself in super specific work situations.

The Urgent Hotfix

Remember that project with the “comfortable” deadline? Yeah, me neither. It’s that common thing where you ask when the project needs to be completed, and you get back a sarcastic “last Tuesday.”

In this particular instance, it was a usual Monday morning. There I was, still in bed, happily rested after a fulfilling weekend. Then Slack started blasting me with notifications, all of which were in the vein of,

“Hey, users can’t make payments on the app — urgent!”

You can fault me for having Slack notifications enabled early on a Monday. But still, it killed my good mood and practically erased whatever respite I gained from the weekend. But I got up, headed over to the laptop, and began working as quickly as the day had started.

The timeline for this sort of fix is most definitely a “due last Tuesday” situation. It’s urgent and demands immediate attention at the expense of dropping everything else. There’s nothing easygoing about it. The pressure is on. As we were all trying to fix the bug, the customer support team also added to the pressure by frequently reporting the rising number of users having difficulties processing payments.

We read through this huge codebase and ran different kinds of tests, but nothing worked. I think it was around 40 minutes before the deadline that a colleague came across a Reddit post (dated six years ago or so) that had the solution in it. I tell you, that bug stood no chance. We finally got the payment system up and running. I was relieved, but at what cost?

What I Learned About HotFixes

Urgent hotfixes are a reality for most developers I know. They sort of come with the territory. But allowing them to take away your well-earned peace of mind is all too easy. A day can go from peaceful to panicking with just one Slack notification, and it may happen at any time, even first thing on a Monday morning.

What I’d Do Differently

It’s funny how Slack is called “Slack” because it really does feel like “slacking off” when you’re not checking in. But I can tell you that my Slack notifications are now paused until more reasonable hours.

Yes, it was a very real and very urgent situation, but allowing it to pull me completely out of my personal time wasn’t the best choice. I am not the only person on the team, so someone else who is already readily available can take the call.

After all, a rested developer is a productive developer, especially when faced with an urgent situation.

The Pit Of Procrastination

I once got myself into a contract for a project that was way above my skill set. But what’s that thing developers love saying, “Fake it ’til you make it,” or something like that? It’s hard to say “no” to something, particularly if your living depends on winning project bids. Plus, I won’t lie: there’s a little pride in not wanting to admit defeat.

When I accepted the job, I convinced myself that all I needed was two full days of steady focus and dedication to get up to speed and knock things out. But guess what? I procrastinated.

It actually started out very innocently. I’d give myself a brain break and read for 30 minutes, then maybe scroll through socials, then switch to YouTube, followed by… you get the picture. By the time I realize what happened, I’m several hours off schedule and find stress starting to harbor and swell inside me.

Those half hours here and there took me right up to the eleventh hour.

Unfortunately, I lost the contract as I couldn’t hit my promised timeline. I take full responsibility for that, of course, but I want to be honest and illustrate the real consequences that happen when stress and fear take over. I let myself get distracted because I was essentially afraid of the project and wasn’t being honest with myself.

What I Learned About Procrastination

The “fake it ’til you make it” ethos is a farce. There are relatively “safe” situations where getting into unfamiliar territory outside your skillset is going to be just fine. However, a new client with a new project spending new money on my expertise is not one of them.

Saying “yes” to a project is a promise, not a gamble.

And I’m no longer gambling with my client’s projects.

What I’d Do Differently

Learning on the job without a solid plan is a bad idea. If a project screams “out of my league,” I’ll politely decline. In fact, I have found that referring a client to another developer with the right skill set is actually a benefit because the client appreciates the honesty and convenience of not having to find another lead. I actually get more work when I push away the work I’m least suited for.

The Unrealistic Request

This happened recently at a startup I volunteered for and is actually quite funny in hindsight. Slack chimed in with a direct message from a marketing lead on the team:

“Hi, we are gonna need to add an urgent feature for a current social media trend. Can you implement it ASAP?”

It was a great feature! I dare say I was even eager to work on it because I saw its potential for attracting new users to the platform. Just one problem: what exactly does “ASAP” mean in this instance? Yes, I know it’s “as soon as possible,” but what is the actual deadline, and what’s driving it? Are we talking one day? One week? One month? Again, startups are famous for wanting everything done two weeks ago.

But I didn’t ask those questions. I dropped everything I was doing and completed the feature in two weeks’ time. If I’m being honest, there was also an underlying fear of saying “no” to the request. I didn’t want to disappoint someone on my team.

That’s the funny part. “ASAP” was really code for “as soon as possible with your current workload.” Was that communicated well? Definitely not. Slack isn’t exactly the best medium for detailed planning. I had a lot more time than I thought, yet I let myself get swept up by the moment. Sure, I nailed the new feature, and it did indeed attract new users — but again, at what cost? I patted myself on the back for a job well done but then swiveled my chair around to realize that I was facing a pile of work that I let mount up in the meantime.

And thus, the familiar weight of stress began taking its typical toll.

What I Learned About Unrealistic Requests

Everything has a priority. Someone else may have a pressing deadline, but does it supersede your own priorities? Managing priorities is more of a juggling act, but I was treating them as optional tasks that I could start and stop at will.

What I’d Do Differently

There are two things I’d do differently next time an unrealistic request comes up:

  • First, I’ll be sure to get a firm idea of when the request is actually needed and compare it to my existing priorities before agreeing to it.
  • Second, I plan on saying “no” without actually saying it. How different would the situation have been had I simply replied, “Yes, if...” instead, as in, “Yes, if I can complete this thing I’m working on first, then I’d be happy to jump on that next.” That puts the onus on the requester to do a little project management rather than allowing myself to take on the burden carte blanche.
The 48-Hour Workday

How many times have you pulled an all-nighter to get something done? If the answer is zero, that’s awesome. In my experience, though, it’s come up more times than I can count on two hands. Sometimes it’s completely my doing; I’ll get sucked into a personal side project or an interesting bug that leads to hours passing by like minutes.

I have more than a few friends and acquaintances who wear sleepless nights like merit badges as if accumulating them is somehow a desirable thing.

The most recent example for me was a project building a game. It was supposed to be pretty simple: You’re a white ball chasing red balls that are flying around the screen. That might not be the most exciting thing in the world, but it was introducing me to some new coding concepts, and I started riding a wave I didn’t want to leave. In my head, this little game could be the next Candy Crush, and there was no way I’d risk losing success by quitting at 2:00 a.m. No way.

To this day, the game is sitting dormant and collecting digital dust in a GitHub repository, unfinished and unreleased. I’m not convinced the five-day marathon was worth it. If anything, it’s like I had spent my enthusiasm for the job all at once, and when it burned me out, I needed a marathon stretch of sleep to get back to reality.

What I Learned About All-Nighters

The romanticized image of a fast-typing developer sporting a black hoodie in a dark room of servers and screens only exists in movies and is not something to emulate. There’s a reason there are 24 hours in a day instead of 48 — we need breaks and rest, if for nothing else, to be better at what we do. Mimicking a fictional stereotype is not the path to becoming a good developer, nor is it the path to sustainable living conditions.

What I’d Do Differently

I’m definitely more protective of the boundaries between me and my work. There’s a time to work, just as there’s a time for resting, personal needs, and even a time for playing.

That means I have clearly defined working hours and respect them. Naturally, there are days I need to be adaptable, but having the boundaries in place makes those days the exception as opposed to the rule.

I also identify milestones in my work that serve as natural pauses to break things up into more manageable pieces. If I find myself coding past my regular working hours, especially on consecutive days, then that’s an indication that I am taking on too much, that I am going outside of scope, or that the scope hasn’t been defined at all and needs more definition.

Bugged By A Bug

There are no escaping bugs. As developers, we’re going to make mistakes and clean them up as we go. I won’t say I enjoy bugfixes as much as developing new features, but there is some little part of me at the same time that’s like, “Oh yeah, challenge accepted!” Bugs can often be approached as mini puzzles, but that’s not such a bad thing.

But there are those bugs that never seem to die. You know, the kind you can’t let go of? You’re absolutely sure that you’ve done everything correctly, and yet, the bug persists. It nearly gets to the point where you might be tempted to blame the bug on the browser or whatever dependency you’re working with, but you know it’s not. It sticks with you at night as you go to bed.

Then comes the epiphany: Oh crap, it’s a missing X. And X is usually a missing semicolon or anything else that’s the equivalent of unplugging the thing and plugging it back in only to find things are working perfectly.

I have lots of stories like this. This one time, however, takes the cake. I was working on this mobile app with React Native and Expo. Everything was going smoothly, and I was in the zone! Then, a rendering error cropped up for no clear reason. My code compiled, and all the tests passed, but the app refused to render on my mobile device.

So, like any logical developer, I CTRL + Z’d my way back in time until I reached a point where I was sure that the app rendered as expected. I still got the same rendering error.

That was when I knew this bug was out for my blood. I tried every trick I knew in the book to squash that thing, but it simply would not go away. I was removing and installing packages like a madman, updating dependencies, restarting VS Code, pouring through documentation, and rebooting my laptop. Still nothing.

For context: Developers typically use Expo on their devices to render the apps in real-time when working with React Native and Expo. I was not, and therein lies the problem. My phone had decided to ditch the same Wi-Fi network that my laptop was connected to.

All I had to do was reconnect my phone to the network. Problem solved. But agony in the process.

What I Learned About Bugfixes

Not every code bug has a code solution. Even though I had produced perfectly valid scripts, I doubted my work and tackled the issue with what’s natural to me: code.

If I had stepped back from my work for even a moment, then I probably would have seen the issue and saved myself many hours and headaches. I let my frustration take over to the extent that the bug was no longer a mini puzzle but the bane of my existence. I really needed to read my temperature level and know when to take a break.

Bugs sometimes make me doubt my credibility as a developer, especially when the solution is both simple and right under my nose the entire time — like network connectivity.

What I’d Do Differently

There’s an old Yiddish saying: To a worm in horseradish, the world is horseradish. You may recognize it as the leading quote in Malcolm Gladwell’s What the Dog Saw and Other Adventures. It’s closely related to other common sayings along the lines of, “To a hammer, everything is a nail.”

In addition to trying to look at bugs from a non-horseradish perspective, I now know to watch my frustration level when things start feeling helpless. Take breaks. Take a walk. Eat lunch. Anything to break the cycle of rumination. It’s often in that moment of clarity that the puzzle finally starts to come together.

The Meeting-Working Imbalance

I don’t like meetings, and I’m sure many developers would agree with me on that. They’re somewhat of a necessary evil right? There’s value, for example, in the weekly standups for checking in on the team’s progress and staying on the same page as far as what’s coming up in the following week of planning.

If only that was the one single meeting I had to attend on a given day.

Let me describe one particular day that I feel is emblematic of what I think is a common conflict between time spent in meetings and time spent working. I got to my workspace and was ready for the usual half-hour weekly team check-in. It went a little over, which was fine, but it did mean I had to rush to the next meeting instead of having a little buffer between the two. That meeting was a classic one, the type where everyone wants a developer in the room in case something technical comes up but never does, leaving me bored and dividing my attention with my actual work.

We had five meetings that day. In my book, that’s a full day completely wasted because I was unable to get around to writing any code at all, save for a few lines I could squeeze in here and there. That’s no way to work, but is unfortunately a common pattern.

What I Learned About Meetings

Meetings have to happen. I get that. But I’ve learned that not every meeting is one that I personally need to attend. In many cases, I can get the gist of what happened in a meeting by watching the recording or reading the project manager’s notes. I now know that meetings can “happen” in lots of ways, and what comes from them can still be learned asynchronously in many instances.

What I’d Do Differently

From here on out, I am asking (politely, of course) whether my attendance is mandatory or not when certain meetings come up. I also ask if I can either prepare something for the group in advance or get caught up to speed after the meeting has happened.

Conclusion

That’s it! These are a handful of situations I have found myself in the past couple of years. It’s funny how seemingly small events are able to coalesce and reveal patterns of behavior. There’s a common thread of stubbornness running through them that has opened my eyes to the way I work and how I manage my mental health.

I’m sure it is the same for you. What times can you remember when stress, anxiety, and frustration consumed you? Are you able to write them down? Do you see a pattern forming? I believe doing this sort of mental inventory is valuable because you start to see specific things that trigger your feelings, and with that, it’s possible to recognize and avoid them in the future.

Further Reading On SmashingMag

Setting And Persisting Color Scheme Preferences With CSS And A “Touch” Of JavaScript

Many modern websites give users the power to set a site-specific color scheme preference. A basic implementation is straightforward with JavaScript: listen for when a user changes a checkbox or clicks a button, toggle a class (or attribute) on the <body> element in response, and write the styles for that class to override design with a different color scheme.

CSS’s new :has() pseudo-class, supported by major browsers since December 2023, opens many doors for front-end developers. I’m especially excited about leveraging it to modify UI in response to user interaction without JavaScript. Where previously we have used JavaScript to toggle classes or attributes (or to set styles directly), we can now pair :has() selectors with HTML’s native interactive elements.

Supporting a color scheme preference, like “Dark Mode,” is a great use case. We can use a <select> element anywhere that toggles color schemes based on the selected <option> — no JavaScript needed, save for a sprinkle to save the user’s choice, which we’ll get to further in.

Respecting System Preferences

First, we’ll support a user’s system-wide color scheme preferences by adopting a “Light Mode”-first approach. In other words, we start with a light color scheme by default and swap it out for a dark color scheme for users who prefer it.

The prefers-color-scheme media feature detects the user’s system preference. Wrap “dark” styles in a prefers-color-scheme: dark media query.

selector {
  /* light styles */

  @media (prefers-color-scheme: dark) {
    /* dark styles */
  }
}

Next, set the color-scheme property to match the preferred color scheme. Setting color-scheme: dark switches the browser into its built-in dark mode, which includes a black default background, white default text, “dark” styles for scrollbars, and other elements that are difficult to target with CSS, and more. I’m using CSS variables to hint that the value is dynamic — and because I like the browser developer tools experience — but plain color-scheme: light and color-scheme: dark would work fine.

:root {
  /* light styles here */
  color-scheme: var(--color-scheme, light);

  /* system preference is "dark" */
  @media (prefers-color-scheme: dark) {
    --color-scheme: dark;
    /* any additional dark styles here */
  }
}
Giving Users Control

Now, to support overriding the system preference, let users choose between light (default) and dark color schemes at the page level.

HTML has native elements for handling user interactions. Using one of those controls, rather than, say, a <div> nest, improves the chances that assistive tech users will have a good experience. I’ll use a <select> menu with options for “system,” “light,” and “dark.” A group of <input type="radio"> would work, too, if you wanted the options right on the surface instead of a dropdown menu.

<select id="color-scheme">
  <option value="system" selected>System</option>
  <option value="light">Light</option>
  <option value="dark">Dark</option>
</select>

Before CSS gained :has(), responding to the user’s selected <option> required JavaScript, for example, setting an event listener on the <select> to toggle a class or attribute on <html> or <body>.

But now that we have :has(), we can now do this with CSS alone! You’ll save spending any of your performance budget on a dark mode script, plus the control will work even for users who have disabled JavaScript. And any “no-JS” folks on the project will be satisfied.

What we need is a selector that applies to the page when it :has() a select menu with a particular [value]:checked. Let’s translate that into CSS:

:root:has(select option[value="dark"]:checked)

We’re defaulting to a light color scheme, so it’s enough to account for two possible dark color scheme scenarios:

  1. The page-level color preference is “system,” and the system-level preference is “dark.”
  2. The page-level color preference is “dark”.

The first one is a page-preference-aware iteration of our prefers-color-scheme: dark case. A “dark” system-level preference is no longer enough to warrant dark styles; we need a “dark” system-level preference and a “follow the system-level preference” at the page-level preference. We’ll wrap the prefers-color-scheme media query dark scheme styles with the :has() selector we just wrote:

:root {
  /* light styles here */
  color-scheme: var(--color-scheme, light);

  /* page preference is "system", and system preference is "dark" */
  @media (prefers-color-scheme: dark) {
    &:has(#color-scheme option[value="system"]:checked) {
      --color-scheme: dark;
      /* any additional dark styles, again */
    }
  }
}

Notice that I’m using CSS Nesting in that last snippet. Baseline 2023 has it pegged as “Newly available across major browsers” which means support is good, but at the time of writing, support on Android browsers not included in Baseline’s core browser set is limited. You can get the same result without nesting.

:root {
  /* light styles */
  color-scheme: var(--color-scheme, light);

  /* page preference is "dark" */
  &:has(#color-scheme option[value="dark"]:checked) {
    --color-scheme: dark;
    /* any additional dark styles */
  }
}

For the second dark mode scenario, we’ll use nearly the exact same :has() selector as we did for the first scenario, this time checking whether the “dark” option — rather than the “system” option — is selected:

:root {
  /* light styles */
  color-scheme: var(--color-scheme, light);

  /* page preference is "dark" */
  &:has(#color-scheme option[value="dark"]:checked) {
    --color-scheme: dark;
    /* any additional dark styles */
  }

  /* page preference is "system", and system preference is "dark" */
  @media (prefers-color-scheme: dark) {
    &:has(#color-scheme option[value="system"]:checked) {
      --color-scheme: dark;
      /* any additional dark styles, again */
    }
  }
}

Now the page’s styles respond to both changes in users’ system settings and user interaction with the page’s color preference UI — all with CSS!

But the colors change instantly. Let’s smooth the transition.

Respecting Motion Preferences

Instantaneous style changes can feel inelegant in some cases, and this is one of them. So, let’s apply a CSS transition on the :root to “ease” the switch between color schemes. (Transition styles at the :root will cascade down to the rest of the page, which may necessitate adding transition: none or other transition overrides.)

Note that the CSS color-scheme property does not support transitions.

:root {
  transition-duration: 200ms;
  transition-property: /* properties changed by your light/dark styles */;
}

Not all users will consider the addition of a transition a welcome improvement. Querying the prefers-reduced-motion media feature allows us to account for a user’s motion preferences. If the value is set to reduce, then we remove the transition-duration to eliminate unwanted motion.

:root {
  transition-duration: 200ms;
  transition-property: /* properties changed by your light/dark styles */;

  @media screen and (prefers-reduced-motion: reduce) {
    transition-duration: none;
  }
}

Transitions can also produce poor user experiences on devices that render changes slowly, for example, ones with e-ink screens. We can extend our “no motion condition” media query to account for that with the update media feature. If its value is slow, then we remove the transition-duration.

:root {
  transition-duration: 200ms;
  transition-property: /* properties changed by your light/dark styles */;

  @media screen and (prefers-reduced-motion: reduce), (update: slow) {
    transition-duration: 0s;
  }
}

Let’s try out what we have so far in the following demo. Notice that, to work around color-scheme’s lack of transition support, I’ve explicitly styled the properties that should transition during theme changes.

See the Pen CSS-only theme switcher (requires :has()) [forked] by Henry.

Not bad! But what happens if the user refreshes the pages or navigates to another page? The reload effectively wipes out the user’s form selection, forcing the user to re-make the selection. That may be acceptable in some contexts, but it’s likely to go against user expectations. Let’s bring in JavaScript for a touch of progressive enhancement in the form of…

Persistence

Here’s a vanilla JavaScript implementation. It’s a naive starting point — the functions and variables aren’t encapsulated but are instead properties on window. You’ll want to adapt this in a way that fits your site’s conventions, framework, library, and so on.

When the user changes the color scheme from the <select> menu, we’ll store the selected <option> value in a new localStorage item called "preferredColorScheme". On subsequent page loads, we’ll check localStorage for the "preferredColorScheme" item. If it exists, and if its value corresponds to one of the form control options, we restore the user’s preference by programmatically updating the menu selection.

/*
 * If a color scheme preference was previously stored,
 * select the corresponding option in the color scheme preference UI
 * unless it is already selected.
 */
function restoreColorSchemePreference() {
  const colorScheme = localStorage.getItem(colorSchemeStorageItemName);

  if (!colorScheme) {
    // There is no stored preference to restore
    return;
  }

  const option = colorSchemeSelectorEl.querySelector([value=${colorScheme}]);
if (!option) { // The stored preference has no corresponding option in the UI. localStorage.removeItem(colorSchemeStorageItemName); return; } if (option.selected) {
// The stored preference's corresponding menu option is already selected return; } option.selected = true; } /* * Store an event target's value in localStorage under colorSchemeStorageItemName */ function storeColorSchemePreference({ target }) { const colorScheme = target.querySelector(":checked").value; localStorage.setItem(colorSchemeStorageItemName, colorScheme); } // The name under which the user's color scheme preference will be stored. const colorSchemeStorageItemName = "preferredColorScheme"; // The color scheme preference front-end UI. const colorSchemeSelectorEl = document.querySelector("#color-scheme"); if (colorSchemeSelectorEl) { restoreColorSchemePreference(); // When the user changes their color scheme preference via the UI, // store the new preference. colorSchemeSelectorEl.addEventListener("input", storeColorSchemePreference); }

Let’s try that out. Open this demo (perhaps in a new window), use the menu to change the color scheme, and then refresh the page to see your preference persist:

See the Pen CSS-only theme switcher (requires :has()) with JS persistence [forked] by Henry.

If your system color scheme preference is “light” and you set the demo’s color scheme to “dark,” you may get the light mode styles for a moment immediately after reloading the page before the dark mode styles kick in. That’s because CodePen loads its own JavaScript before the demo’s scripts. That is out of my control, but you can take care to improve this persistence on your projects.

Persistence Performance Considerations

Where things can get tricky is restoring the user’s preference immediately after the page loads. If the color scheme preference in localStorage is different from the user’s system-level color scheme preference, it’s possible the user will see the system preference color scheme before the page-level preference is restored. (Users who have selected the “System” option will never get that flash; neither will those whose system settings match their selected option in the form control.)

If your implementation is showing a “flash of inaccurate color theme”, where is the problem happening? Generally speaking, the earlier the scripts appear on the page, the lower the risk. The “best option” for you will depend on your specific stack, of course.

What About Browsers That Don’t Support :has()?

All major browsers support :has() today Lean into modern platforms if you can. But if you do need to consider legacy browsers, like Internet Explorer, there are two directions you can go: either hide or remove the color scheme picker for those browsers or make heavier use of JavaScript.

If you consider color scheme support itself a progressive enhancement, you can entirely hide the selection UI in browsers that don’t support :has():

@supports not selector(:has(body)) {
  @media (prefers-color-scheme: dark) {
    :root {
      /* dark styles here */
    }
  }

  #color-scheme {
    display: none;
  }
}

Otherwise, you’ll need to rely on a JavaScript solution not only for persistence but for the core functionality. Go back to that traditional event listener toggling a class or attribute.

The CSS-Tricks “Complete Guide to Dark Mode” details several alternative approaches that you might consider as well when working on the legacy side of things.

Crafting Experiences: Uniting Rikyu’s Wisdom With Brand Experience Principles

In today’s dynamic and highly competitive market, the concept of brand experience is a key aspect of customer engagement: designers, take note.

Brand experience refers to all customer interactions and engagements with a brand, encompassing various brand channels, products, services, and encounters from the company website to unpacking its product. It involves following the user each time she comes into contact with the brand and ensuring that her experience is consistent and pleasant.

Beyond merely designing products or services, the designers or design team (along with the marketing department) must strive to create memorable, emotional, and immersive interactions with their customers. A compelling brand experience attracts and retains customers while reinforcing the brand promise.

Achieving this goal can be daunting but not impossible as long as designers follow specific principles. Recently, I attended a tea ceremony in the Japanese city of Kyoto, where I was introduced to Rikyu’s timeless wisdom. With fascination, I saw that such wisdom and insight could be applied to the principles of a compelling brand experience in the following ways.

The Japanese Tea Ceremony, According to Tea Master Rikyu

The seven principles of Rikyu were developed by Sen no Rikyu, a revered tea master of the 16th century. Each principle encapsulates the essence of the Japanese tea ceremony, emphasizing not only the preparation of tea but also the creation of a harmonious, meaningful experience.

During my own captivating tea ceremony experience, I gained valuable insights and a fresh perspective on how designers can help create meaningful connections between brands and their audiences, much as the tea ceremony has done for generations.

Rule One: Making a Satisfying Bowl of Tea

The first principle of Rikyu goes right to the heart of the tea ceremony: preparing a satisfying bowl of tea.

This deceptively simple principle reminds designers that everything we design for a brand should be able to provide a memorable experience for the final user. We should aim to go beyond simple brand and customer transactions and instead focus on crafting experiences through products and services.

Examples:

  • Airbnb,
  • Duolingo.

Both of them facilitate extraordinary experiences beyond the basic user interaction of “rent a house for my trip” and “learn a foreign language.”

Airbnb: Redefining Travel Through Experience

Compared to competitors like Booking.com, Airbnb has completely redefined the experience of travelling, adding a strong storytelling aspect.

From the beginning, the brand has offered a way for travelers to truly immerse themselves in the culture and lifestyle of their destinations.

Today, Airbnb’s website shows the brand offering the possibility of “living” in an extraordinary place, from cozy apartments to extravagant castles. We can see that their brand isn’t just about finding the right accommodation but also about creating enduring memories and stories.

Their services have been significantly updated in recent years, offering customers great flexibility to book in total safety from qualified hosts (called Superhosts) with homes that have been reviewed and reflect Airbnb quality standards.

Takeaway: Aim to create experiences that stay with people long after they have interacted with your brand.

Duolingo: Language-Learning as a Playful Adventure

Language learning is often considered a daunting task, one that pushes us out of our comfort zones. But Duolingo, with its playful and gamified approach, is changing that perception.

Their app has transformed language learning into a delightful adventure that anyone can join, even for just five minutes a day.

By creating characters that team up with Duo (the owl mascot), Duolingo injects a sense of companionship and relatability into language learning, making it feel like taking a journey alongside a helpful friend.

Takeaway: Break down complex tasks into enjoyable, bite-sized experiences that improve the long-term experience.

Rule Two: Efficiently Laying the Charcoal for Boiling Water

As I took my place in the tea room, just opposite the tea master, he explained that charcoal plays an extremely important role in the ceremony. It must be precisely placed to encourage airflow, prevent the fire from extinguishing prematurely, and prepare tea at the perfect temperature.

For designers, this translates into creating a comprehensive set of guidelines and rules that dictate how every brand element should look, feel, and behave.

Much like the precise arrangement of charcoal, a well-designed brand system is the foundation of consistent and efficient brand representation that ensures harmony and coherence across every touchpoint.

This may seem obvious, but it is only in the last decade that technology companies have started creating elaborate and complete brand guidelines.

Examples:

  • IBM,
  • Atlassian.

IBM: Consistency Breeds Loyalty and Recognisability

When we think about the connection between brand and technology, it’s natural to think immediately of Apple and Steve Jobs. So you could be surprised that in fact, IBM was one of the first tech companies to hire a professional graphic designer.

Acclaimed graphic designer Paul Rand designed the iconic IBM logo in 1956. The collaboration between Paul Rand and the company went on for many years, becoming a benchmark for the integration of design principles into the corporate identity of a tech company.

Even today, IBM’s design system Carbon is a testament to the power of simplicity and consistency. Focusing on clarity and functionality, IBM’s brand elements work seamlessly across a diverse range of products and services, including events and workplaces. The Carbon design system is also open source, meaning anyone can contribute to improving it.

Takeaway: A consistent and well-designed brand identity allows for organic growth and expansion without diluting the brand, reinforcing brand loyalty and recognition.

Atlassian: Guiding Future Decisions

Atlassian is a software company with a diverse product portfolio. Their design system promotes scalability and flexibility, while their brand elements are designed to adapt harmoniously across various Atlassian applications.

This adaptability ensures a unified brand experience while accommodating the unique characteristics of each product. It serves as a compass, helping designers navigate the vast landscape of possibilities and ensuring that each design decision made for each Atlassian product aligns with the brand’s essence.

Takeaway: A strong design foundation serves as an invaluable guide as brands evolve and expand their offering through more different products and services.

Rule 3: Providing Warmth in Winter and Coolness in Summer

In the art of the Japanese tea ceremony, the provision of warmth in winter and coolness in summer is a delicate balance, attuned to the emotional and physical states of the participants. This is well-reflected in the tea room’s decoration, and the tea served, depending on the hour and the season, in a bowl chosen by the tea master.

When I attended the tea ceremony, the room was decorated to reflect the spring season. The sweet was also inspired by the blooming cherry blossoms, which were pink and light green. The door to the garden was left open so that we could appreciate the scent of fresh blossoms in the gentle spring breeze.

In the design world, this rule translates into the profound understanding and adaptation of experiences to align with customers’ ever-changing needs and emotional states throughout their journey.

Understanding the natural flow of emotions during the user journey allows brands to create responsive experiences that feel personal.

Examples:

  • Nike,
  • Netflix.

Nike: Versatility in Style and Experience

Nike, better than any other brand leader in sportswear, exemplifies mastery in tailoring brand experiences.

The brand recognizes that customers engage with their products across diverse activities.

For this reason, Nike offers a wide range of products, sometimes presented with mini-websites and beautiful campaigns, each with its own distinct style and purpose.

Takeaway: By catering to their users’ varied tastes and needs, brands can tailor experiences to individual preferences and emotions, fostering a deeper connection and resonance.

Netflix: Personalised Home Entertainment

Netflix has deftly pioneered the use of advanced algorithms and artificial intelligence to tailor its content recommendations. These are not only based on geographic location but individual user preferences.

The platform dynamically adjusts preview images and trailers, aiming to match each user’s unique taste.

Their latest update includes Dynamic Sizzle Reel, short personalized clips of upcoming shows that offer each member a unique and effective experience.

It is worth noting, however, that while Netflix puts effort into yielding greater engagement and enjoyment for their members, the subjective nature of taste can sometimes lead to surprises, where a preview may align perfectly with an individual user’s taste, yet the show itself varies in style.

Takeaway: When customizing experiences, designers should create an interplay between familiarity and novelty, tailoring content to individual tastes while respecting the user’s need for both comfort and discovery.

Rule 4: Arranging Flowers as Though They Were in the Field

As I stepped into the tea room, there was a sense of harmony and tranquillity infused by nature forming part of the interior environment.

The flowers were meticulously arranged in a pot as though plucked directly from the field at that very moment. According to Rikyu’s principles, their composition should be an ode to nature’s simplicity and authenticity.

For designers, this rule echoes the importance of using aesthetics to create a visually captivating brand experience that authentically reflects the brand’s values and mission.

The aesthetic choices in design can convey a brand’s essence, creating a harmonious and truthful representation of the brand and its services.

It is important to remember, however, that a visually appealing brand experience is not just about aesthetics alone, but using them to create an emotional and truthful connection with the audience.

Examples:

  • Kerrygold,
  • WWF.

Kerrygold: Forging Memorable Narratives

The Kerrygold “Magic Pantry” website is testament to the art of visual storytelling, following the brand’s mission to spread authentic Irish recipes and stories from Ireland and its farms.

Through a captivating storytelling game, users explore a recipe or storybook, pick a favorite dish based on their meal, and choose their assistant.

In a perfect story fashion, with a good amount of personalization, users then learn how to cook their chosen recipes using Kerrygold products.

This immersive experience showcases the excellence of Kerrygold’s products and conveys the brand’s commitment to quality and authenticity, while the storybook confers the idea of passing family traditions across the world (so common in the past!)

Takeaway: Through visuals, designers need to be authentic, reflecting the truth about the brand. This truthfulness enhances the credibility of the brand’s narrative and establishes deeper user connections.

WWF: Enhancing Memorability Through Beauty and Truth

WWF employs visual storytelling to raise awareness about environmental issues and species in danger of extinction. Their campaign websites always present a beautiful and immersive visual journey that authentically communicates the urgency of their mission.

While these two websites are grounded in the universal act of eating, WWF prompts users to reflect on their habits’ profound impact on the environment.

Both websites ingeniously guide users to think about food consumption in more detail, fostering a journey toward mindful eating that respects both species and the environment.

The websites adopt a quiz-like approach for users to reflect on and reassess their food consumption patterns, fostering a journey toward mindful eating that respects both species and the environment.

Beyond individual insights, the interactive nature of these platforms encourages users to extend their newfound knowledge to their friends, amplifying awareness of crucial topics such as food consumption, CO2 emissions, and sustainable alternatives.

Takeaway: By infusing elements of discovery and self-reflection, designers can help brands promote their values and missions while empowering their users to become ambassadors for change.

Rule 5: Being Ready Ahead of Time

In the Japanese tea ceremony, Rule 5 of Rikyu’s principles places emphasis on the seamless art of preparation, ensuring that everything is ready for the guests.

For their part, guests are expected to arrive on time for their appointment and be mindfully prepared to attend the ceremony.

Designers should note that this principle underscores the significance of foresight, careful planning, and meticulous attention — both to detail and the user’s time.

For brands, being ready ahead of time is paramount for delivering a seamless and exceptional customer experience.

By proactively addressing customer needs and meticulously planning every touchpoint, designers can create a seamless and memorable brand experience that fosters customer satisfaction and loyalty by respecting the value of time.

Examples:

  • IKEA,
  • Amazon.

IKEA: Anticipating Customer Expectations

IKEA, the global furniture and home goods giant, is a brand that, since the beginning, has used its vast warehouse store layout to anticipate and plan customers’ needs — even the unconscious ones. In fact, you could well be among those shoppers who plan to buy just a few items but leave the store with a trolley full of things they never knew they needed!

When customers enter an IKEA store, they encounter a meticulously planned and organized environment designed as a circular one-way system.

This specific layout of the IKEA store creates a sense of discovery. It encourages shoppers to keep walking through the different departments effortlessly, anticipating or projecting needs that they may have been unaware of before they entered.

Takeaway: Brands should harness the creative ability to tap into customers’ subconscious minds through environment and product display in a way that exceeds their expectations.

Amazon: A Ready-to-go Shopping Experience

Amazon understands that their customers’ time is valuable, creating seamless online and offline experiences that streamline the shopping experience. Their unique systems strive to avoid inconveniences and provide a quick, ready-to-go shopping experience.

For example, their patented one-click ordering system simplifies the checkout process, reducing friction by saving users the trouble of manually selecting or entering settings (like address and payment methods) that are used repeatedly.

Meanwhile, the brick-and-mortar Amazon Go stores exemplify innovation, offering a shopping experience where customers can grab items and go without waiting in line.

These stores work by using the same types of technologies found in self-driving cars, such as computer vision, sensor fusion, and deep learning.

This technology can detect when products are taken or returned to the shelves, tracking them in the customer’s virtual cart. When customers leave the store with their goods, their Amazon account is charged, and a receipt is sent.

Please note: Even though Amazon recently announced the closure of some of its physical shops, the experience remains an example of innovative and efficient shopping methods.

Takeaways: Ingrain the art of preparation by utilizing advanced technologies in the brand’s operational philosophy to avoid inconvenience and provide an effortless customer experience.

Rule 6: Being Prepared in Case It Should Rain

In the context of the Japanese tea ceremony, being prepared for rain means being prepared for unexpected challenges.

According to Rikyu, when having tea, the host must be intentionally calm and ready to accommodate any situation that arises. Of course, this doesn’t just apply to changes in the weather!

For designers crafting brand experiences, this principle underscores the importance of building resilience and adaptability into the core of their strategies.

Examples:

  • Zoom,
  • Lego.

Zoom: Pioneering Remote Communication

Zoom was mostly used for business meetings before the Covid-19 pandemic struck. When it did, it forced most companies to digitize far more quickly than they otherwise would have done.

Zoom stepped up, improving its features so that everyone, from children to their baby boomer grandparents, found the user interface seamless and easy when connecting from their homes.

One of the most exciting business decisions taken by Zoom was to turn their Freemium tier wholly free and unlimited for K-12 students. This decision was taken during the early stages of the pandemic (March 2020) demonstrating empathy with the challenges faced by K-12 educational institutions.

The program significantly impacted schools, teachers, and students. It allowed for more collaborative and engaging virtual classrooms, thanks to features like Groups and useful interactions like whiteboards, raising hands, and replying with emojis.

As schools gradually returned to in-person learning and adapted to hybrid models, the free program ended. However, the positive impact of Zoom’s support during a critical period underlined the company’s adaptability and responsiveness to societal needs.

Takeaway: Designers should prioritize creating intuitive interfaces and scalable infrastructure that can accommodate surges in demand whilst also considering the impact on society.

Lego: Rebuilding From The Bricks Up

Without continuous adaptability and recognition of the ever-changing landscape of play, even a historic brand like Lego may have closed its doors!

In fact, if you are a Lego fan, you may have noticed that the brand underwent a profound change in the early 2000s.

In 1998, Lego launched an educational initiative known as Lego Mindstorm. This project used Lego’s signature plastic bricks to teach children how to construct and program robots — an innovative concept at the time since Arduino had not yet been introduced.

Lego’s decision to merge traditional play with technology demonstrated their dedication to keeping up with the digital age. Additionally, Lego Mindstorm appealed to a new audience: the broader open-source hardware and DIY electronics community that emerged during the period (and who, in 2005, found a better match in Arduino).

Please note: Even though the program is set to be discontinued by 2024, Lego’s resurgence is often cited as one of the most successful corporate turnarounds.

Lego still continues to thrive, expanding its product lines, collaborating with popular franchises, and maintaining its status as a beloved brand among children and adults alike.

Takeaway: Designers can adapt to change by refocusing on the brand’s core strengths, embracing digital innovation and new targets to exemplify resilience in the face of challenges.

Rule 7: Acting with Utmost Consideration Towards Your Guests

During the tea ceremony in Kyoto, I perceived in every gesture the perfect level of attention to detail, including my response to the tasting and the experience as a whole. I felt the impact of my experience from the moment I entered until long after I left the tea room, even as I write about it now.

This rule highlights the importance of intuitive hospitality and involves creating an environment in which guests feel welcomed, valued, and respected.

For designers, this means facilitating brand experiences that put customer satisfaction first and aim to build strong and lasting relationships.

Brands that excel in this rule go above and beyond to provide uniquely personalized experiences that foster long-term loyalty.

Examples:

  • Stardust App,
  • Tony’s Chocolonely.

Stardust App: Empowering Women’s Health with Privacy and Compassion

Stardust is an astrology-based menstrual cycle-tracking app that debuted in the Apple Store. It became the most downloaded iPhone app in late June after the U.S. Supreme Court struck down Roe v. Wade (which ended the constitutional right to an abortion and instantly made abortion care illegal in more than a dozen states).

In a world where tracking apps often lack sensitivity, Stardust App emerges with an elegant interface that makes monitoring women’s health a visually pleasing experience. But beyond aesthetics, what really sets Stardust apart is its witty and humorous tone of voice.

Acknowledging the nuances of mood swings and pains associated with periods, Stardust’s notification messages and in-app descriptions resonate with women, adding a delightful touch to a typically challenging time.

This blend of sophistication and humor creates a unique and supportive space for women’s wellness.

Note:

The female-founded app underwent scrutiny from TechCrunch, Vice, and Screen Rant, which questioned their collaboration with third parties and its safety. So on October 29th, 2022, they released a more precise and comprehensive Privacy Policy that explains in a readable way how third parties are used and how the end-to-end encryption works.

They also ensured that all sessions were anonymous so that the app would not be able to associate data with users in case of law enforcement.

Takeaway: Design a brand experience with utmost consideration toward users and that transcends the transactional to foster an enduring sense of trust, empathy, and loyalty.

Tony’s Chocolonely: Sweet Indulgence and Ethical Excellence

In their commitment to fair trade, Tony’s Chocolonely exemplifies acting with utmost consideration towards both consumers and the environment beyond merely offering delicious chocolate.

More specifically, Tony’s Chocolonely has redefined the chocolate industry by championing fair-trade practices. By introducing a sustainable business model, not only do they satisfy the cravings of chocolate enthusiasts, but they also address the broader demand for ethically sourced and produced chocolate.

In every detail, from the wrapper to the chocolate bar itself, Tony’s Chocolonely is a brand on a mission. The intentional unevenness of their chocolate bar is a profound symbol, mirroring the uneven and unjust landscape of the chocolate industry. This urges consumers to choose fairness, ethical sourcing, and a commitment to change.

Takeaway: Designers can elevate brand experiences by integrating thoughtful and personalized elements that speak to their industry and resonate with the values of their audience.

Conclusion

In the gentle and artistic practice of the Japanese tea ceremony, I discovered through Rikyu’s seven principles an illuminated path of consideration that resonates beyond the tea room, offering profound insights for crafting compelling brand experiences.

Rikyu’s ancient wisdom serves as a timeless guide, reminding us that creating a memorable experience is a balanced dance between intention and harmony and, above all, the valuable attention of those we invite into our brand spaces as welcome guests.

Sketchnotes And Key Takeaways From SmashingConf Antwerp 2023

I have been reading and following Smashing Magazine for years — I’ve read many of the articles and even some of the books published. I’ve also been able to attend several Smashing workshops, and perhaps one of the peak experiences of my isolation times was the online SmashingConf in August 2020. Every detail of that event was so well-designed that I felt genuinely welcomed. The mood was exceptional, and even though it was a remote event, I experienced similar vibes to an in-person conference. I felt the energy of belonging to a tribe of other great design professionals.

I was really excited to find out that the talks at SmashingConf Antwerp 2023 were going to be focused on design and UX! This time, I attended remotely again, just like back in 2020: I could watch and live-sketch note seven talks (and I’m already looking forward to watching the remaining talks I couldn’t attend live).

Even though I participated remotely, I got really inspired. I had a lot of fun, and I felt truly involved. There was an online platform where the talks were live-streamed, as well as a dedicated Slack channel for the conference attendees. Additionally, I shared my key takeaways and sketchnotes right after each talk on social media. That way, I could have little discussions around the topics &mdash, even though I wasn’t there in person.

In this article, I would like to offer a brief summary of each talk, highlighting my takeaways (and my screenshots). Then, I will share my sketchnotes of those seven talks (+ two more I watched after the conference).

Day 1 Talks

Introduction

At the very beginning of the conference, Vitaly said hello to everyone watching online, so even though I participated remotely, I felt welcomed. :-) He also shared that there is an overarching mystery theme of the conference, and the first one who could guess it would get a free ticket for the next Smashing conference — I really liked this gamified approach.

Vitaly also reminded us that we should share our success stories as well as our failure stories (how we’ve grown, learned, and improved over time).

We were introduced to the Pac-man rule: if we are having a conversation, and someone is speaking from the back and wants to join, open the door for them — just like Pac-man does (well, Pac-man opens his mouth because he wants to eat, you want to encourage conversations).

In between talks, Vitaly told us a lot of design jokes; for instance, this one related to design systems was a great fit for the first talk:

Where did Gray 500 and Button Primary go on their first date?

To a naming convention.

After this little warm-up, Molly Hellmuth delivered the first talk of the event. Molly has been a great inspiration for me not only as a design system consultant but also as a content creator and community builder. I’m also enthusiastic about learning the more advanced aspects of Figma, so I was really glad that Molly chose this topic for her talk.

“Design System Traps And Pitfalls” by Molly Hellmuth

Molly is a design system expert specializing in Figma design systems, and she teaches a course called Design System Bootcamp. Every time she runs this course, she sees students make similar mistakes. In this talk, she shared the most common mistakes and how to avoid them.

Molly shared the most common mistakes she experienced during her courses:

  • Adopting new features too quickly,
  • Adding too many color variables,
  • Using groups instead of frames,
  • Creating jumbo component sets,
  • Not prepping icons for our design system.

She also shared some rapid design tips:

  • Set the nudge amount to 8
  • We can hide components in a library by adding a period or an underscore
  • We can go to a specific layer by double-clicking on the layer icon
  • Scope variables, e.g., colors meant for text is, only available for text
  • Use auto layout stacking order (it is not only for avatars, e.g., it is great for dropdown menus, too).

“How AI Ate My Website” by Luke Wroblewski

I have been following Luke Wroblewski since the early days of my design career. I read his book “Web Form Design: Filling in the Blanks” back in 2011, so I was really excited to attend his talk. Also, the topic of AI and design has been a hot one lately, so I was very curious about the conversational interface he created.

Luke has been creating content for 27 years; for example, there are 2,012 articles on his website. There are also videos, books, and PDFs. He created an experience that lets us ask questions from AI that have been fed with this data (all of his articles, videos, books, and so on).

In his talk, he explained how he created the interaction pattern for this conversational interface. It is more like a FAQ pattern and not a chatbot pattern. Here are some details:

  • He also tackled the “what I should ask” problem by providing suggested questions below the most recent answer; that way, he can provide a smoother, uninterrupted user flow.

  • He linked all the relevant sources so that users can dig deeper (he calls it the “object experience”). Users can click on a citation link, and then they are taken to, e.g., a specific point of a video.

He also showed us how AI eats all this stuff (e.g., processing, data cleaning) and talked about how it assembles the answers (e.g., how to pick the best answers).

So, to compare Luke’s experience to e.g., Chat GPT, here are some points:

  • It is more opinionated and specific (Chat GPT gives a “general world knowledge” answer);
  • We can dig deeper by using the relevant resources.

You can try it out on the ask.lukew.com website.

“A Journey in Enterprise UX” by Stéphanie Walter

Stéphanie Walter is also a huge inspiration and a designer friend of mine. I really appreciate her long-form articles, guides, and newsletters. Additionally, I have been working in banking and fintech for the last couple of years, so working for an enterprise (in my case, a bank) is a situation I’m familiar with, and I couldn’t wait to hear about a fellow designer’s perspective and insights about the challenges in enterprise UX.

Stéphanie’s talk resonated with me on so many levels, and below is a short summary of her insightful presentation.

On complexity, she discussed the following points:

  1. Looking at quantitative data: What? How much?
    Doing some content analysis (e.g., any duplicates?)
  2. After the “what” and discovering the “as-is”: Why? How?
    • By getting access to internal users;
    • Conducting task-focused user interviews;
    • Documenting everything throughout the process;
    • “Show me how you do this today” to tackle the “jumping into solutions” mindset.

Stéphanie shared with us that there are two types of processes:

  • Fast track
    Small features, tweaks on the UI — in these cases, there is no time or no need to do intensive research; it involves mostly UI design.
  • Specific research for high-impact parts
    When there is a lot of doubt (“we need more data”). It involves gathering the results of the previous research activities; scheduling follow-up sessions; iterating on design solutions and usability testing with prototypes (usually using Axure).
    • Observational testing
      “Please do the things you did with the old tool but with the new tool” (instead of using detailed usability test scripts).
    • User diary + longer studies to help understand the behavior over a period of time.

She also shared what she wishes she had known sooner about designing for enterprise experiences, e.g., it can be a trap to oversimplify the UI or the importance of customization and providing all the data pieces needed.

It was also very refreshing that she corrected the age-old saying about user interfaces: you know, the one that starts with, “The user interface is like a joke...”. The thing is, sometimes, we need some prior knowledge to understand a joke. This fact doesn’t make a joke bad. It is the same with user interfaces. Sometimes, we just need some prior knowledge to understand it.

Finally, she talked about some of the main challenges in such environments, like change management, design politics and complexity.

Her design process in enterprise UX looks like this:

  • Complexity
    How am I supposed to design that?
  • Analysis
    Making sense of this complexity.
  • Research
    Finding and understanding the puzzle pieces.
  • Solution design
    Eventually, everything clicks into place.

The next talk was about creating a product with a Point of View, meaning that a product’s tone of voice can be “unique,” “unexpected,” or “interesting.”

“Designing A Product With A Point Of View” by Nick DiLallo

Unlike in the case of the other eight speakers whose talks I sketched, I wasn’t familiar with Nick’s work before the conference. However, I’m really passionate about UX writing (and content design), so I was excited to hear Nick’s points. After his talk, I have become a fan of his work; check out his great articles on Medium).

In his talk, Nick DiLallo shared many examples of good and not-so-good UX copies.

His first tip was to start with defining our target audience since the first step towards writing anything is not writing. Rather, it is figuring out who is going to be reading it. If we manage to define who will be reading as a starting point, we will be able to make good design decisions for our product.

For instance, instead of designing for “anyone who cooks a lot”, it is a lot better to design for “expert home chefs”. We don’t need to tell them to “salt the water when they are making pasta”.

After defining our audience, the next step is saying something interesting. Nick’s recommendation is that we should start with one good sentence that can unlock the UI and the features, too.

The next step is about choosing good words; for example, instead of “join” or “subscribe,” we can say “become a member.” However, sometimes we shouldn’t get too creative, e.g., we should never say “add to submarine” instead of “add to cart” or “add to basket”.

We should design our writing. This means that what we include signals what we care about, and the bigger something is visual, the more it will stand out (it is about establishing a meaningful visual hierarchy).

We should also find moments to add voice, e.g., the footer can contain more than a legal text. On the other hand, there are moments and places that are not for adding more words; for instance, a calendar or a calculator shouldn’t contain brand voice.

Nick also highlighted that the entire interface speaks about who we are and what our worldview is. For example, what options do we include when we ask the user’s gender?

He also added that what we do is more important than what we write. For example, we can say that it is a free trial, but if the next thing the UI asks is to enter our bank card details, well, it’s like saying that we are vegetarian, and then we eat a cheeseburger in front of me.

Nick closed his talk by saying that companies should hire writers or content designers since words are part of the user experience.

“When writing and design work together, the results are remarkable.”

“The Invisible Power of UI Typography” by Oliver Schöndorfer

This year, Oliver has quickly become one of my favorite design content creators. I attended some of his webinars, I’m a subscriber of his Font Friday newsletter, and I really enjoy his “edutainment style”. He is like a stand-up comedian. His talks and online events are full of great jokes and fun, but at the same time, Oliver always manages to share his extensive knowledge about typography and UI design. So I knew that the following talk was going to be great. :)

During his talk, Oliver redesigned a banking app screen live, gradually adding the enhancements he talked about. His talk started with this statement:

“The UI is the product, and a big part of it is the text.”

After that, he asked an important question:

“How can we make the type work for us?”

Some considerations we should keep in mind:

  • Font Choice
    System fonts are boring. We should think about what the voice of our product is! So, pick fonts that:
    • are in the right category (mostly sans, sometimes slabs),
    • have even strokes with a little contrast (it must work in small sizes),
    • have open-letter shapes,
    • have letterforms that are easy to distinguish (the “Il1” test).

  • Hierarchy
    i.e. “What is the most important thing in this view?”

Start with the body text, then emphasize and deemphasize everything else — and watch out for the accessibility aspects (e.g. minimum contrast ratios).

Accessibility is important, too!

  • Spacing
    Relations should be clear (law of proximity) and be able to define a base unit.

Then we can add some final polish (and if it is appropriate, some delight).

As Oliver said, “Go out there and pimp that type!

Day 2 Talks

“Design Beyond Breakpoints” by Christine Vallaure

I’m passionate about the designer-developer collaboration topic (I have a course and some articles about it), so I was very excited to hear Christine’s talk! Additionally, I really appreciate all the Figma content she shares, so I was sure that I’d learn some new exciting things about our favorite UI design software.

Christine’s talk was about pushing the current limits of Figma: how to do responsive design in Figma, e.g., by using the so-called container queries. These queries are like media queries, but we are not looking at the viewport size. Instead, we are looking at the container. So a component behaves differently if, e.g., it is inside a sidebar, and we can also nest container queries, e.g., tell an icon button inside a card that upon resizing, the icon should disappear).

Recommended Reading: A Primer On CSS Container Queries by Stephanie Eckles

She also shared that there is a German fairy tale about a race between a hedgehog and a rabbit. The hedgehog wins the race even though he is slower. Since he is smarter, he sends his wife (who looks exactly like him) to the finish line in advance. Christine told us that she had mixed feelings about this story because she didn’t like the idea of pretending to be fast when someone has other great skills. In her analogy, the rabbits are the developers, and the hedgehogs are the designers. Her lesson was that we should embrace each others’ tools and skills instead of trying to mimic each others’ work.

The lesson of the talk was not really about pushing the limits. Rather, the talk was about reminding us of why we are doing all this:

  • To communicate our design decisions better to the developers,
  • To try out how our design behaves in different cases (e.g., where it should break and how), and
  • It is also great for documentation purposes; she recommended the EightShapes Specs plugin by Nathan Curtis.

Her advice is:

  • We should create a playground inside Figma and try out how our components and designs work (and let developers try out our demo, too);
  • Have many discussions with developers, and don’t start these discussions from zero, e.g., read a bit about frontend development and have a fundamental knowledge of development aspects.

“It’s A Marathon, And A Sprint” by Fabricio Teixeira

If you are a design professional, you have surely encountered at least a couple of articles published by the UX Collective, a very impactful design publication. Fabricio is one of the founders of that awesome corner of the Internet, so I knew that his talk would be full of insights and little details. He shared four case studies and included a lot of great advice.

During his talk, Fabricio used the analogy of running. When we prepare for a long-distance running competition, 80% of the time, we should do easy runs, and 20% of the time should be devoted to intensive because short interval runs get the best results. He also highlighted that just like during a marathon running, things will get hard during our product design projects, but we must remember how much we trained. When someone from the audience asked how not to get overly confident, he said that we should build an environment of trust so that other people on our team can make us realize if we’ve become too confident.

He then mentioned four case studies; all of these projects required a different, unique approach and design process:

  • Product requirements are not required.
    Vistaprint and designing face masks — the world needed them to design really fast; it was a 15-day sprint, and they did not have time to design all the color and sizing selectors (and only after the launch did it turn into a marathon).

  • Timelines aren’t straight lines.
    The case study of Equinox treadmill UI: they created a fake treadmill to prototype the experience; they didn’t wait for the hardware to get completed (the hardware got delayed due to manufacturing issues), so there was no delay in the project even in the face of uncertainty and ambiguity. For example, they took into account the hand reach zones, increased the spacing between UI elements so that these remained usable even while the user was running, and so on.

Exciting challenge: Average treadmill interface, a complicated dashboard, everything is fighting for our attention.

  • Research is a mindset, not a step.
    He mentioned the Gofundme project, where they applied a fluid approach to research meaning that design and research ran in parallel, the design informed research and vice versa. Also, insights can come from anyone from the team, not just from researchers. I really liked that they started a book club, everyone read a book about social impact, and they created a Figma file that served as a knowledge hub.

  • Be ready for some math
    During the New York City Transit project, they created a real-time map of the subway system, which required them to create a lot of vectors and do some math. One of the main design challenges was, “How to clean up complexity?”

Fabricio shared that we should be “flexibly rigorous”: just as during running, we should listen to our body, we should listen to the special context of a given project. There is no magic formula out there. Rigor and discipline is important, but we must listen to our body so that we don’t lose touch of reality.

The key takeaway is that because, we as a design community focus a lot on processes, and of course there is no one way to do design, we should combine sprints and marathons, adjust our approach to the needs of the given project, and most of all, focus more on principles, e.g. how we, as a team, want to work together?

A last note is when Fabricio mentioned in the post-talk discussion with Vitaly Friedman that having a 1–3-hour long kick-off meeting with our team is too short, we will work on something for e.g. 6 months, so Fabricio’s team introduced kick-off weeks.

Kat delivered one of the most important talks (or maybe the most important talk) of the conference. The ethics of design is a topic that has been around for many years now. Delivering a talk like this is challenging because it requires a perspective that easily gets lost in our everyday design work. I was really curious about how Kat would make us think and have us question our way of working.

“Design Ethically: From Imperative To Action” by Kat Zhou

Kat’s talk walked us through our current reality such as how algorithms have built in biases, manipulate users, hide content that shouldn’t be hidden, and don’t block things that shouldn’t be allowed. The main question, however, is:

Why is that happening? Why do designers create such experiences?

Kat’s answer is that companies must ruthlessly design for growth. And we, as designers, have the power to exercise control over others.

She showed us some examples of what she considers oppressive design, like the Panopticon by Jeremy Bentham. She also provided an example of hostile architecture (whose goal is to prevent humans from resting in public places). There are also dark patterns within digital experiences similar to the New York Times subscription cancellation flow (users had to make a call to cancel).

And the end goal of oppressive design is always to get more user data, more users’ time, and more of the users’ money. What amplifies this effect is that from an employee’s (designer’s) perspective, the performance is tied to achieving OKRs.

Our challenge is how we might redesign the design process so that it doesn’t perpetuate the existing systems of power. Kat’s suggestion is that we should add some new parts to the design process:

  • There are two phases:
    Intent: “Is this problem a worthy problem to solve?”
    Results: “What consequences do our solutions have? Who is it helping? Who is it harming?”
  • Add “Evaluate”:
    “Is the problem statement we defined even ethically worthy of being addressed?”
  • Add “Forecast”:
    “Can any ethical violations occur if we implement this idea?”
  • Add “Monitor”:
    “Are there any new ethical issues occurring? How can we design around them?”

Kat shared a toolkit and framework that help us understand the consequences of the things we are building.

Kat talked about forecasting in more detail. As she said,

“Forecasted consequences often are design problems.”

Our responsibility is to design around those forecasted consequences. We can pull a product apart by thinking about the layers of effect:

  • The primary layer of effect is intended and known, e.g.: Google Search is intended and known as a search engine.
  • The secondary effect is also known, and intended by the team, e.g. Google Search is an ad revenue generator.
  • The tertiary effect: typically unintended, possibly known, e.g. Algorithms of Oppression, Safiya Umoja Noble talks about the biases built in Google Search.

So designers should define and design ethical primary and secondary effects, and forecast tertiary effects, and ensure that they don’t pose any significant harm.

I first encountered atomic design in 2015, and I remember that I was so fascinated by the clear logical structure behind this mental model. Brad is one of my design heroes because I really admire all the work he has done for the design community. I knew that behind the “clickbait title” (Brad said it himself), there’ll be some great points. And I was right: he mentioned some ideas I have been thinking about since his talk.

“Is Atomic Design Dead?” by Brad Frost

In the first part of the talk, Brad gave us a little WWW history starting from the first website all the way to web components. Then he summarized that design systems inform and influence products and vice versa.

I really liked that he listed three problematic cases:

  • When the design system team is very separated, sitting in their ivory tower.
  • When the design system police put everyone in the design system jail for detaching an instance.
  • When the product roadmaps eat the design system efforts.

He then summarized the foundations of atomic design (atoms, molecules, organisms, templates and pages) and gave a nice example using Instagram.

He answered the question asked in the title of the talk: atomic design is not dead, since it is still a useful mental model for thinking about user interfaces, and it helps teams find a balance, and equilibrium between design systems and products.

And then here came the most interesting and thought-provoking part: where do we go from here?

  1. What if we don’t waste any more human potential on designing yet another date picker, but instead, we create a global design system together, collaboratively? It’d be an unstyled component that we can style for ourselves.

  2. The other topic he brought up is the use of AI, and he mentioned Luke Wroblewski’s talk, too. He also talked about the project he is working on with Kevin Coyle: it is about converting a codebase (and its documentation) to a format that GPT 4 can understand. Brad showed us a demo of creating an alert component using ChatGPT (and this limited corpus).

His main point was that since the “genie” is out of the bottle, it is on us to use AI more responsibly. Brad closed his talk by highlighting the importance of using human potential and time for better causes than designing one more date picker.

Mystery Theme/Other Highlights

When Vitaly first got on stage, one of the things he asked the audience to keep an eye out for was an overarching mystery theme that connects all the talks. At the end of the conference, he finally revealed the answer: the theme was connected to the city of Antwerp!

Where does the name "Antwerp" come from? “Hand werpen” or “to throw a hand”. Once upon a time, there was a giant that collected money from everyone passing the river. One time, a soldier came and just cut off the hand of this giant and threw it to the other side, liberating the city. So, the story and the theme were “legends.” For instance, Molly Hellmuth included Bigfoot (Sasquatch), Stéphanie mentioned Prometheus, Nick added the word "myth" to one of his slides, Oliver applied a typeface usually used in fairy tales, Christine mentioned Sisyphus and Kat talked about Pandora’s box.

My Very Own Avatar

One more awesome thing that happened thanks to attending this conference is that I got a great surprise from the Smashing team! I won the hidden challenge 'Best Sketch Notes', and I have been gifted a personalized avatar created by Smashing Magazine’s illustrator, Ricardo.

Full Agenda

There were other great talks — I’ll be sure to watch the recordings! For anyone asking, here is the full agenda of the conference.

A huge thanks again to all of the organizers! You can check out all the current and upcoming Smashing conferences planned on the SmashingConf website anytime.

Saving The Best For Last: Photos And Recordings

The one-and-only Marc Thiele captured in-person vibes at the event — you can see the stunning, historic Bourla venue it took place in and how memorable it all must have been for the attendees! 🧡

For those who couldn’t make it in person and are curious to watch the talks, well, I have good news for you! The recordings have been recently published — you can watch them over here:


Thank you for reading! I hope you enjoyed reading this as much as I did writing it! See you at the next design & UX SmashingConf in Antwerp, maybe?