Speed Needs Design, or: You can’t delight users you’ve annoyed
Tags: webdev, performance, css, design
Published: 2022-03-24 12:31:17

---
title: Speed Needs Design, or: You can’t delight users you’ve annoyed
description: Grab bag of design tweaks. Like avoiding purple, the slowest color (lie)
published: true
series: Streets
tags: webdev, performance, css, design
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqahx5987ocy0q19fg7p.jpg
---

To really sell streamed HTML’s impact, I should have shown the existing site’s design, but with streaming’s speed.

## The demo’s design deviations

Unfortunately, **it was impossible to have the existing design and the speed I needed**. This post is about some of the differences.

If I were _a good scientist_, I’d have before/after traces for each deviation. Unfortunately, I do not — I was hastily testing and retesting [on real hardware and a packet-throttled connection](https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na#some-sort-of-higherlevel-goal). If something _felt_ slower, I tossed it.

If I’m wrong about something, I’ll happily accept corrections with a good source.

### Design goals

Look, I’m jealous of native devs too. They get 60–120fps fullscreen animations with builtin platform support and minimum overhead.

But this is **a website that sells food**: I will relentlessly sacrifice delight for easier access. I can never _truly_ catch up to native apps’ interaction richness, platform fidelity, and SFX flaunting. But I _can_ beat native apps at [the Web’s strength of getting things done, easily, with little fuss](https://daringfireball.net/2004/06/location_field). I wasn’t just trying to outrace our existing site — I aimed at our native app too.[^3]

[^3]: Native has much improved since [Gruber wrote that post](https://daringfireball.net/2004/06/location_field). It’s time web devs _also_ stepped up our game, and not by playing catch-up: “What I missed when I dismissed them a decade ago is that web apps don’t need to beat desktop apps on the same terms.”

I figured I could add flourishes and rise to meet interesting challenges _after_ finishing the usable, accessible bones.

<figure>
<table>
<thead><tr>
<th>My amateur login page<th>The professional login page

<tbody><tr>
<td><img alt="A basic login page, with a “Sign in to Kroger” heading, a username field, a password field, a “Forgot your password?” link, a “Stay signed in on this device” checkbox, and “Sign in”/“Create an account” buttons." src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/au4hu7hk1mfje2ejio8h.png">
<td><img alt="A loading spinner in a sea of white." src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em2aus7uqasjghkwcxs1.png">
</table>

<figcaption><p>Which design looks better, 10 seconds in?</figcaption>
</figure>

That said, I’m no designer. **A _real_ designer could probably make these tradeoffs with better results.**

### Fonts

Kroger.com’s [Nunito](https://fonts.google.com/specimen/Nunito) `.woff2` files are ≈14kB each, totaling 55.3kB.

If I found a smaller lookalike font, subset aggressively, and applied [more arcane font optimizations](https://pixelambacht.nl/2016/font-awesome-fixed/), that could probably shrink to ~12kB total.[^1] But remember [the 20kB budget](https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na#googles-suggestions-to-be-fast-on-mobile)? 60% of that for a decorative font is hard to justify.

[^1]: Based on results with [Poppins](https://fonts.google.com/specimen/Poppins) from an earlier design.

System fonts also spend less time on the main thread: each webfont `src` causes a reflow (they can batch together, but don’t count on it). [Usually the reflow isn’t awful](https://twitter.com/csswizardry/status/1504507222491074560), but you know who’s not too good for free performance? 👉 This dork! 👈

So, **no webfonts**.

But that was an easy decision: avoiding custom fonts is _negative_ work. How about something harder?

### Product carousels

Displaying products is ecommerce’s most important job. Which is why it was a problem that our scrolling product lists didn’t fit on [the phone we sell](https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na#some-sort-of-higherlevel-goal):

<figure>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gpivx7apssnhncjdjflf.png" alt="With the Poblano screen dimensions, the product cards are too tall, and you can only see one at a time.">

<figcaption><p>I know whitespace is an important part of design, but this is probably not what they meant.</figcaption>
</figure>

That was the obvious reason to change, but there’s also subtler drawbacks:

<dl>
<dt>Memory pressure and increased layout cost
  <dd><p>Scroll panes use RAM and VRAM for hardware-accelerated scrolling — but we need those for fast <code>&lt;body&gt;</code> scrolling, too!
  <figure>
  <img alt="A long white bar of the total surface area of the vertically-scrolling page, with four other long bars poking out to the right for the horizontal scroll areas. One of them extends so far it’s not fully shown." src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/05x25917kk5tbquxqc1s.png">
  <figcaption><p>Current homepage in Chrome DevTools’ Layers view. It only rasterizes the non-blank parts, but still calculates layout for all areas.</figcaption>
  </figure>

  <p>Remember how iOS Safari needed opting-in for accelerated scrolling with <code>-webkit-overflow-scrolling: touch</code>? Now you know why.

<dt>Scroll trap on touchscreens
  <dd><p>The lists take up so much vertical space that they become infuriating scroll traps on the demo phones, <a href="https://bradfrost.com/blog/post/adaptive-maps/">like mobile map embeds</a>. (It doesn’t help that cheap touchscreens often get swipe direction confused at the beginning of a gesture.)

<dt>Reflow and pop-in
  <dd><p>As new cards load on scroll, they all resize their height to match the tallest. Even when the shift is minimal, the necessary layout calculations still hiccuped on the demo phones.

<dt>Poor use of small viewport
  <dd><p>The Poblano wasn’t the smallest screen I targeted. I wanted to preserve legibility down to the Apple Watch.
</dl>

Instead, I displayed products like this:

<figure>
<img alt="The demo’s products displayed as a vertical list, in block-level flow as websites do by default." src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a3scml4bgijyvbbglt6.png">

<figcaption><p>No, it’s not very attractive. But it works a hell of a lot better.</figcaption>
</figure>

You can probably spot several tricks to save space in the above screenshot: product sizes inline with the names, relying on text’s horizontal nature for more wrapping compactness, etc. 

{% details ⌛ Other things I wanted to do, but ran out of time %}

I wanted to use [`shape-outside`](https://developer.mozilla.org/en-US/docs/Web/CSS/shape-outside) to flow text into the empty spaces of non-rectangular products, so I could make the image larger. (I had a burning desire to maximize how many onscreen pixels could be image pixels without sacrificing too much information density.) I might post how to do this, someday.

Later, the “stickiness” of scrolling lists was cited to me as an important quality. I wanted to recoup some of that by decorating the “See more” links at the end of each product list with thumbnails of what more was on that list, but ran out of time before the demo. <em lang="fr">C’est la vie.</em>

{% enddetails %}

### `box-shadow` more like `bourgeois-shadow`

I know, I know. Shadows are fashionable. If used correctly they make interfaces more understandable. The interplay of light and shadow is the sum total of visual art itself.

But even from a strict design perspective, shadows aren’t all-upside:

- The **area needed to spread/blur shadows requires space around elements**, which comes at a premium on the small screens I targeted.
- Current popular use is subtle, which means **they can never be the workhorse of a design**. (Unless you make [shadows the _only_ design element, and that’s terrible](https://axesslab.com/neumorphism/).)
- **An affordance that doesn’t meet contrast requirements is questionably useful for the people who need it most.** Or even most people: glaring sunlight, tired eyes, distracted attention, etc. need _strong_ affordances, not minimal ones. (I’m not even sure how many people _can_ notice trendy shadows, from a visual accessibility perspective.)

And from _not_ a strict design perspective…

<!--
{% details 🦕 Some of box-shadow’s rap sheet circa 2010–2016 %}

- [CSS box-shadow Can Slow Down Scrolling](https://medium.com/airbnb-engineering/css-box-shadow-can-slow-down-scrolling-d8ea47ec6867)
- [We spent a week making Trello boards load extremely fast. Here’s how we did it.](http://sking7.github.io/articles/529414047.html)
- [Many box shadows will make your app unusable on smartphones and tablets](https://makandracards.com/makandra/17609-many-box-shadows-will-make-your-app-unusable-on-smartphones-and-tablets)
- [Poor performance on Android due to CSS Drop Shadows](https://forum.ionicframework.com/t/poor-performance-on-android-due-to-css-drop-shadows/14916)
- [extremely slow CSS3 box-shadows in Chrome](https://stackoverflow.com/questions/8993541/extremely-slow-css3-box-shadows-in-chrome)
- [WebKit shadow fast path turns into a slow path in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=119778)
- [Regression: Smooth navigation is not seen on 'Lufthansa'.](https://bugs.chromium.org/p/chromium/issues/detail?id=425427)
- [box-shadow and border-radius are slow](https://bugs.chromium.org/p/chromium/issues/detail?id=99045)
- [40-50% usage when scrolling twitter.com (jerky and slow)](https://bugs.chromium.org/p/chromium/issues/detail?id=97716#c54)
- [box-shadow extremely slow when width or height exceeds certain size, especially noticeable when combined with animation](https://bugzilla.mozilla.org/show_bug.cgi?id=1250037)
- [Performance tricks for (mobile) web development](http://web.archive.org/web/20170223160146/https://cubiq.org/performance-tricks-for-mobile-web-development)

> We aim to make a box shadow of 3–5px performant.
> I don’t think we ever expect a 200px box shadow to be remotely close to performant.
>
> —&thinsp;[Inset Box Shadow Caused Major Issues on Chrome for Android](https://bugs.chromium.org/p/chromium/issues/detail?id=351328#c4)

{% enddetails %} -->

Historically, [`box-shadow` is a career performance criminal](https://medium.com/airbnb-engineering/css-box-shadow-can-slow-down-scrolling-d8ea47ec6867). Probably not as much nowadays, unless some combination of inset, blur radius, `border-radius`, transparency, shadowed element size, screen resolution, browser, OS, or hardware falls off browsers’ fast paths.

![A flame chart bar, with a legend reading: 341ms requestAnimationFrame callbacks; Type: Paint.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9m3dkljc0u2sth4by04.png)

Modern advice mostly [warns about transitioning or animating around shadows](https://web.dev/animations-guide/#paint) — especially since [scrolling under them also counts, as Facebook learned](https://ishadeed.com/article/new-facebook-css/#using-an-image-for-the-shadow).

(Yes, Google’s Material Design went all-in on blurring and animating _lots_ of shadows. Have you seen [how much effort they spend because of that](https://github.com/flutter/flutter/issues/54507)?)

There _are_ ways to work around shadows’ performance risks, which might even be worth the effort.

![Coupons on Kroger.com, with shadows for each coupon and their primary action buttons.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6w053syissr3g9tyx7vx.png)

But… man, these `box-shadow`s just don’t seem _that_ worth it. They’re subtle! Intentionally! If I have to plan how to protect users from them, there needs to be _way_ more of a payoff. At least skeuomorphism was in-your-face.

If a design effect is only noticeable to young people with good vision, and it can unpredictably penalize people with cheap or overworked devices… then I don’t care how delightful it is. The risks conflicted with my goals and this was _my_ stupid passion project, dammit. **I didn’t trust them and I didn’t need them.**

So, I dropped the shadows. <small><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/drop-shadow()">…but not like that.</a></small>

### Homepage promotions

<table>
<thead><tr>
<th>Carousel
<th>Tiles

<tbody><tr>
<td><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1q5bkx4xui4lxywunjmk.png" alt="An autorotating carousel with a pause button and 3 dots indicating how many slides it has. The first dot is darker to indicate the current slide.">
<td><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zpasqqaxi5c48vqum2p5.png" alt="3 tiles, showing the same amount of promotions as the carousel via the ancient and mystical art of “put them next to each other”.">
</table>

Think about what code the carousel needs that tiles don’t:

- Next/previous swiping vs. buttons, and switching between them
- Position indicator
- Pause/Play (required for accessibility)
- `(prefers-reduced-motion)` check and mitigation
- Accessibly hiding offscreen slides
- Autoforwarding
- Animation timing
- Repositioning slides to the left when wrapping
- <kbd>Tab ↹</kbd> handling

No matter how efficiently those features are implemented, they’re still code users must download. ([If users actually _liked_ carousels](https://jhalabi.com/blog/carousels-no-one-likes-you) that could be worth it, but, uh…)

#### But the demo didn’t use tiles either

[Encoding each promotion as one big image has problems](https://hollypryce.com/text-images/):

- Scaled-down text becomes unreadable small viewports
- Doesn’t work with High-Contrast Mode or other [`forced-colors`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors)
- Takes too long to display _anything_ on the target connection, [which meant it was as good as not having it](https://www.nngroup.com/articles/website-response-times/#:~:text=Fancy%20Widgets%2C%20Sluggish%20Response)
- [Costs users more](https://whatdoesmysitecost.com/) than I was comfortable with. _Especially_ since I couldn’t aggressively compress the images without making their text look crusty.

So the demo went even further beyond, with an approach I’m calling “ribbons” just now. Here’s how those load over the target connection:

<figure>
<video src="https://assets.codepen.io/183091/ribbon-loading.mp4" controls width="400"></video>

<figcaption><p>The text and colored backgrounds are visible instantly, even though the images take a few seconds. (A media query rejiggers them into tiles on larger viewports.)</figcaption>
</figure>

### No modals, tooltips, toasts, etc.

Unlike the parts of this post where it felt like I was compromising, this was a user experience _improvement_. Do _you_ like dealing with [modals and pop-up banners and all their annoying friends](https://deathtobullshit.com/)? Me neither.

That’s an opinion, though. Some more objective reasons I eschewed widgets for boring alternatives:

- Their JavaScript and CSS eat away at the performance budget
- They make less sense on small/touch screens — in particular, modals take up nearly the entire page anyway
- They’re hard to make accessible (modals in particular have been called “the final boss of web accessibility”)

More on that last one… Even if I _did_ succeed in making widgets accessible, the [JavaScript required to do so](https://www.smashingmagazine.com/2021/06/css-javascript-requirements-accessible-components/) really adds up. Check out the sizes of some popular modules, each a reasonable choice for tooltips, modals, and toasts respectively:

<figure>
<figcaption><a href="https://bundlephobia.com/scan-results?packages=@popperjs/core@2.11.4,@reach/dialog@0.16.2,notistack@2.0.3">JS cost according to Bundlephobia</a>
</figcaption>
<center>
<table>
<thead>
<th>Module
<th>Minified
<th>.min.gz
<th>Slow 3G download

<tbody>
<tr><th><code>@popperjs/core</code> 2.11.4
<td>20.5 kB<td>7.2 kB<td>144 ms

<tr><th><code>@reach/dialog</code> 0.16.2
<td>27.3 kB<td>9.4 kB<td>188 ms

<tr><th><code>notistack</code> 2.0.3
<td>18.8 kB<td>6.4 kB<td>128 ms

<tfoot>
<tr><th>Total
<td>66.7 kB<td>23 kB<td>~½ second!
</table>

<p><b>✏️ Note:</b> this table doesn’t include <a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/javascript-startup-optimization#parsecompile">parse/execute time, which scales with minified kB</a>.
</center>
</figure>

(Yes, there are [more efficient ways to script these widgets](https://inclusive-components.design/#components). I just grabbed whatever looked well-maintained and popular, like the vast majority of devs.)

Finally, implementing complex interactivity would have taken a lot out of _me_. Check out what it took for Pedro Duarte to make an accessible dropdown:

<figure>

<img alt="Dropdown Menu: 2,000+ hours, 6 months, 50 reviews, and 1,000s of commits." src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/didkxbzarobe2xxzm0fe.png">

<figcaption><p>Source: <a href="https://medium.com/@nirbenyair/headless-components-in-react-and-why-i-stopped-using-ui-libraries-a8208197c268">Headless components in React and why I stopped using a UI library for our design system</a></figcaption>
</figure>

But if these UI patterns are annoying, not very accessible, and costly, **why do so many sites use them?**

- I think they’re easier to design, since they don’t have to care about the underlying page.
- I suspect analytics falsely report increased “engagement” because they need more interaction than less intrusive designs. Even if said interaction is, say, users trying to get the damn carousel to hold still.
- Sometimes, they can be the best solution for a problem — but as soon as you have that component, it’s tempting to reuse it for other, less-suited problems.

More on alternatives to these known user-aggravators:

- [Designing for actual performance § Simplify the interface](https://adamsilver.io/blog/designing-for-actual-performance/#1.-simplify-the-interface)
- [The good news: It doesn’t have to be a modal!](https://modalzmodalzmodalz.com/)
- [The problem with snackbars and toast messages (and what to use instead)](https://adamsilver.io/blog/the-problem-with-snackbars-and-toast-messages/) and [its companion on tooltips](https://adamsilver.io/articles/the-problem-with-tooltips-and-what-to-do-instead/), by Adam Silver

## Better design via speed

Unlike the other sections where design was in service to speed, this section is about how fast page loads can enable better design!

Our existing checkout flow had expanding/contracting accordions, intertwingled error constraints, and tricky `.focus()` management because of the first two. I wanted to avoid all that, so I broke up checkout into a series of small, quick pages:

![The pages of my checkout flow: 1. My shopping cart, 2. Order summary, 3. Contact information, 4. Thank you for choosing Pickup!](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l9ozmw8o2enwf579qjfe.png)

If you’ve heard of [One Thing Per Page](https://www.smashingmagazine.com/2017/05/better-form-design-one-thing-per-page/), that’s what I did. It didn’t take long to code, and was surprisingly easy.

But wait! There’s more!

> - low-confidence users find them easier to use
> - they work well on mobile devices
> - they’re better at handling things like errors, branches, loops and saving progress
>
> —&thinsp;[One thing per page · Design in government](https://designnotes.blog.gov.uk/2015/07/03/one-thing-per-page/)

I didn’t implement them for the demo, but functionality like user’s Account info and settings would also be well-served by this pattern.

> [W]hen we started user research with the general public, we saw a very positive response to the simple step by step approach, even on large screens. Though it added more clicks, people said it made the process feel simple and easy - there wasn’t too much to take in and process at any one time. So we stuck with the simpler screens for everyone.
>
> —&thinsp;[GOV.UK: Things we learnt designing ‘Register to vote’](https://designnotes.blog.gov.uk/2014/07/14/things-we-learnt-designing-register-to-vote/)

And thanks to [Paint Holding](https://developers.google.com/web/updates/2019/05/paint-holding) these page sequences looked and felt as seamless as SPA navigation. (_Lots_ more on that in the next post.)

## So what?

Like how woodworking involves sculpting along the grain, web design needs an understanding of what’s easy and natural, to save effort for things that _really_ need it.

Our current practices of pushing designers away from HTML & CSS thwarts [an intuitive understanding of what’s easy vs. what’s hard on the Web](https://frankchimero.com/blog/2015/the-webs-grain/). More cooperation and faster feedback loops could help — because I was designing and developing myself, the loop was as tight as possible as I switched hats on the fly.

But, uh, it’s also true my designs won’t win any beauty contests. So don’t trust me, listen to some actual designers:

- [A Beginner’s Guide to Performant Design Decisions](https://sparkbox.com/foundry/beginners_guide_to_performant_design_decisions) & [The Path to Performance](https://speakerdeck.com/katiekovalcin/the-path-to-performance) by Katie Kovalcin
- [Designing for Performance · Lara Hogan](https://designingforperformance.com/preface/)
- [What Web Designers Can Do To Speed Up Mobile Websites · Suzanne Scacca](https://www.smashingmagazine.com/2019/06/web-designers-speed-mobile-websites/)
- [Performance As Design · Brad Frost](https://bradfrost.com/blog/post/performance-as-design/)

In the next post, I reveal that while I may be a twentysomething paid to work in React, _I’m secretly a filthy progressive enhancement liker_.