Smashing Book 6
Smashing Book 6
The book has been written and reviewed by Laura Elizabeth, Marcy
Sutton, Rachel Andrew, Mike Riethmuller, Lyza Gardner, Yoav Weiss,
Adrian Zumbrunnen, Greg Nudelman, Ada Rose Cannon, Vitaly
Friedman, Andy Davies, Alla Kholmatova, Cesar Brod, Heydon Picker-
ing, Martin Splitt, Marko Dugonjić, Mike Sharp, Mike Pennisi, Alberta
Soranzo, Sashka Maximova, Lilia Zinchenko, Stefan Bucher, Benoit
Henry, Nils Mielke, Thord D. Hedengren, and Bertrand Lirette.
Table of Contents
by Vitaly Friedman
Well, we all know that the reality is slightly more nuanced and
complicated than that. (Sorry for crushing your dreams at this point.)
Yet why is it, when designing and building interfaces today, we often
shoot ourselves in the foot, assuming a bit too much about everything
from network conditions to screen resolutions to user behavior? Why
is it, as we craft those precious pixels on our ultra-wide screens and
fiber connections, we often fall into the trap of creating for a perfect,
spotless world – one not unlike the world depicted above?
It’s not for lack of willingness or empathy that we end up there. It’s
just infinitely difficult to keep all those unknowns and variabilities in
mind when being on a tight deadline.
8
Foreword by Vitaly Friedman
When setting out to create this book, our goal was to provide a highly
practical guide – for designers and developers alike – with actionable
insights to help all of us get better at our work right away. The book
explores new frontiers in web design: new challenges and opportunities
for more reliable and more flexible web experiences. But most impor-
tantly: it’s the book about problems in the fragile, inconsistent, frag-
mented and wonderfully diverse web we find ourselves in today.
We’ll explore how to make design systems work in the real world, and
how to keep single-page apps accessible. We’ll look into production-ready
CSS Grid layout, CSS custom properties and service workers. We’ll also
establish guidelines for better performance of our websites and applica-
tions. We’ll study how to design and build better conversational inter-
faces, chatbots, and virtual assistants, as well as AR/VR/XR experiences.
The last chapter will guide you through some practical strategies to
break out of generic, predictable, and soulless interfaces.
We might not be able to reach that perfect world we can only dream of,
but if this book manages to provide you with real value for your ongoing
projects today, it will have served its purpose. With this in mind, flip over
this page and dive in – just be sure to have your coffee ready next to it.
Happy reading!
9
chapter 1
Laura Elizabeth
Making Design Systems Work
CHAPTER
by Laura Elizabeth
Why, then, are we still struggling to make a design system stick? What
seems to be the main problem? Is it that working with a component-
driven workflow is too difficult, or that maintaining the design system
feels like too much work? I wanted to find the answers. I spoketo many
different companies, large and small, and there are a few key reasons
kept cropping up:
1 http://smashed.by/uxpinreport
12
Making Design Systems Work In The Real World by Laura Elizabeth
The interesting thing to note here is that none of these are technical
issues. Many people I spoke to during my research for this chapter
did articulate issues with the tech, but as it turned out, the tech wasn’t
really the problem. As we’ll find out in this chapter, despite their robotic
name, design systems are predominantly about one thing: people.
I thought the team would pick it up instantly. But I found there were
a lot of roadblocks stopping people from using it, like needing to
switch to different naming conventions. People were used to doing
things a certain way, and didn’t want to spend time changing their
process, which, for all intents and purposes, works perfectly fine.
13
It turned out he needed a way to make the design system easier for his
CHAPTER
This is something they’re still working on, but to understand why peo-
ple don’t use your design system, you first need to understand people
and their motivations. Una Kravets argues that we need to be more
empathetic to what people are being judged on at work.
We’re all being judged on different things. Most people aren’t being
judged on clean code. The engineers we’re building this for are being
judged on their ability to build the product and ship features quickly.
Ultimately, if your design system doesn’t help your team meet their
goals, you’re going to have a hard time convincing them to use it.
You could argue that every company would benefit from a design sys-
tem. And there’s truth in that. If we could flick a switch and suddenly
have a design system, it would no doubt be beneficial.
14
Making Design Systems Work In The Real World by Laura Elizabeth
So how can you find out whether a design system is really something
your team needs? Nathan Curtis from EightShapes recommends start-
ing every design system with a phase of discovery:
Before you even begin putting together your design system (or even if
you’ve already started), you need to understand how your team cur-
rently works and what the biggest struggles are.
2 http://smashed.by/startds
15
We can ask questions like:
CHAPTER
3. If you could get rid of one aspect of your job, what would that be?
4. What are the main inefficiencies in your design/development
process?
5. How can your company improve in terms of enabling people to
get their work done more effectively?
With this exercise, we’re looking for any commonalities that people are
struggling with. What do people want to fix in their jobs? What will
make them happier, more effective people?
Beneath these you can paste the exact words people have used. For
example, a developer’s problem might be that they’re under pressure to
keep building new features, but they’re getting complex and inconsis-
tent styling from the designers for each new component. The codebase
is becoming messy and they’re struggling to meet the deadlines.
16
Making Design Systems Work In The Real World by Laura Elizabeth
Feedback Lack of Need core Can’t build Lack of shared Lack of col-
efficiency styling fast enough vocabulary laboration
Every time we launch a new feature,
I have to create a new component. I x
can’t meet the deadlines!
We have to wait too long to launch
new features or they are sub-par
x
Developers are never happy with
what I give them but I don’t under- x
stand what they want from me
I’d be more effective if I could work
closely with designers on new x
features out
I never have enough time to get
everything done
x
The code base is a mess and it’s
making me inefficient
x
The shared workspace is count-
er-productive. I wish there was a
quite zone I could go when I need to
x
get some focused work done.
Inconsistent styling makes it hard
for me to know which bits of design x
is ‘our’ design.
Then you you could have a product manager who is frustrated because
they feel like they’re waiting too long to launch new features. They
also think that these features end up nothing like they had envisioned
or talked about at the start.
From these issues we can categorize them: whether it’s creating core
styles so developers don’t have to code new styles for every new fea-
ture; or saving time by being able to launch faster using previously
17
tested components; or even just creating a shared vocabulary so people
CHAPTER
This will give you a good overview of the current problems in your
1
company. The idea is that you’ll use this to decide whether a design
system is really what your company needs right now, or if there’s some-
thing else you need to fix first.
If the answers show you could benefit from a design system, it’ll also
give you reasons why. Some companies might feel pretty efficient
in their work, but team members struggle to collaborate with one
another. Maybe testing is a big roadblock for your company – you’re
having to spend too long testing every component, which means you
can’t push out features fast enough.
This exercise will help you build your design system because you’ll
have a better understanding of why you’re doing it. You’ll be working
towards what’s best for your team, making the design system work for
them and their needs, which will help with adoption.
18
Making Design Systems Work In The Real World by Laura Elizabeth
RESOURCES
People are busy. No matter how much technology advances to make
our lives more efficient, we always find ways to fill that time. Most
people aren’t sitting around at work, twiddling their thumbs, looking
for projects to work on. We have deadlines, meetings, and the allure of
Twitter vying for our immediate attention.
How can you demonstrate to your team members that you have the
resources available to put this into action without adding to their
already full plates?
19
INCENTIVES
CHAPTER
When it doesn’t work, the most likely cause is that you’re not solving
a problem painful enough for them. Sure, your coworkers want to be
efficient, but is that enough? After all, they’re getting paid the same
amount each month no matter how efficient they are.
People are typically resistant to extra work. As Scott Berkun points out:3
If you’re finding team members still aren’t motivated, you may need
to dig deeper. James Ferguson from Skyscanner knew that different
teams had each spent weeks or months creating various date pickers
for their website. He knew these were a massive pain for people to cre-
ate, and that it was unnecessary to create so many different variations
when one would do.
If James could take away the need for all these teams to create their
own date pickers and have a single, tested component, that would
solve a huge problem for them:
3 http://smashed.by/designprinciples
20
Making Design Systems Work In The Real World by Laura Elizabeth
Find out what people are really struggling with, the kind of thing
they’d pay someone to solve for them. Solve that first.
CONSEQUENCES
The last element mentioned is consequences. What will happen if a
team doesn’t get behind a design system?
Often, this is just a reverse of the incentives plus one additional caveat:
the longer you wait before starting to systemize your website, the
bigger the job is going to be.
21
Getting Your Team to Adopt the System
CHAPTER
Organically
1
It’s a valid question. But if there’s one theme that’s cropped up again
and again with companies that are successfully using a design system
it’s that you can’t police a design system. What this means is, you can’t
create a library of reusable components, for example, and expect every-
one to use and contribute it because you said so. Any type of design
system is only as good as the people using it; if the people using it do
so out of obligation, your design system is not going to be as stable.
Brent admits that this is a slower process, but he’s found it to be far
more lasting because through meeting people and talking to them, he’s
been able to create a deeper understanding of what a design system is
and why it will benefit them.
22
Making Design Systems Work In The Real World by Laura Elizabeth
Getting your team to use and contribute to a design system may not
happen quickly, but as long as you’re making their lives easier it will
happen. Instead of taking a top-down approach and requiring every
team member to use the design system, try taking a more collaborative
approach – show them the benefits and let them decide whether this is
something they want to get involved in.
Yes, on the face of it we’ve been hired to improve the user experience,
or to build out new features. But the end goal is still to make a profit. It
doesn’t have to be in a cutthroat, “we will make more money, no matter
the cost” way. Increasing revenue means creating more jobs (and thus,
supporting more families), better equipment and facilities, higher
paychecks, and so on.
23
Ryan Rumsey wrote a great article about how he managed to get
CHAPTER
buy-in for his design system at Electronic Arts. With previous proj-
ects he had struggled with this, so he understood the value of putting
together a case for a design system up front:
1
So how can we get them on board? From my research, I’ve seen two
approaches:
4 http://smashed.by/askbuyin
24
Making Design Systems Work In The Real World by Laura Elizabeth
Dedicating a set amount of time to try out a small style guide or pat-
tern library and measuring its effects across your team is the simplest
way to get buy-in. If you’ve already created something and it’s proving
to be effective, you’ll be hard-pressed to find a manager who isn’t will-
ing to help sustain the efforts.
25
WHAT IF YOU DON’T HAVE THE RESOURCES JUST YET
CHAPTER
be able to assemble a team, put one together and stay on top of your
workload.
Not only that, involving a small portion of your team can actually
impede adoption by alienating other people from the system. This is
what Donna Chan and Isaak Hayes from AppDirect found during their
early attempts at a design system:
In these cases, it’s more effective to spend some up-front time ensuring
you get buy-in from management so you get the resources you need to
create something useful for your team. Which leads us to…
26
Making Design Systems Work In The Real World by Laura Elizabeth
And when we try to talk about metrics, we usually want to see either
an increase in efficiency, saving money, or making a profit. In other
words, we want to see a return on our investment (ROI). Is the time
and money that we need to put in to create a design system going to be
less than what we get out of it? Not necessarily, says Alla Kholmatova
in her book Design Systems:5 “Some teams struggle for a while to see
a return on investment in a modular system, which makes it hard to
justify the investment in the first place.”
An ROI assumes that the design system is adopted by the team, used
consistently, and enables them to get results faster. None of this can be
guaranteed but, as we’ll see later in the chapter, there are things we can
do to dramatically increase its chances of success.
5 http://designsystemsbook.com/
27
When approaching management, it’s best not to go in with a full five-
CHAPTER
year roadmap for a design system. Instead, it’s easier to get approval
if you start small and low-risk. Remember, at this stage you’re still
researching whether a design system is the best option for right
1
now. For some teams, a style guide and basic pattern library might be
enough. A full-fledged design system might be too much work for too
little reward right now. And if that’s the case, don’t force it until your
company is ready.
The first three points are pretty straightforward to get together. It’s the
last three I’d like to focus on now.
28
Making Design Systems Work In The Real World by Laura Elizabeth
For the group who used OKRs we saw an increase in their aver-
age sales per hour from $14.44 per hour to $15.67, or an average
increase of 8.5%. This increase is not only statistically significant,
but practically significant.
— Chris Mason, Senior Director,
Strategic Talent Solutions at Sears Holding Company
6 http://smashed.by/sears
29
CHAPTER
1
You can use OKRs in any aspect of an organization (like the sales team
example above), including your design system. The reason OKRs are
great for design systems is because you’re treating the system as a
means to an end. In other words, the result isn’t having a design sys-
tem. The result is improving efficiency, or shipping more products.
30
Making Design Systems Work In The Real World by Laura Elizabeth
Their primary goal was to create a pattern library team members could
use to envision what they wanted to build. It also needed to contain the
code that could help their developers quickly bring their ideas to life.
That was their why. This will be different for every company. What’s
important is that they clearly defined what the goal was. They then
split this into three objectives that helped them achieve this goal and
laid out exactly how they’d measure it: 8
7 http://smashed.by/alps
8 http://smashed.by/adventist
31
3. “Involve the community in the creation and adoption of
CHAPTER
They kept track of each goal as they progressed with their design sys-
tem, and it gave them the motivation to keep going and get the results
they hoped for. Some they achieved, others they didn’t, but OKRs are
meant to be adapted as you learn more about what’s achievable. This
means if you set the bar too low or high, or are tracking the wrong
metrics, you can change them.
32
Making Design Systems Work In The Real World by Laura Elizabeth
A Twitter poll drew over 1,200 responses: a whopping 80% said they
think style guides and pattern libraries save money; 9% said it made no
difference; and 11% said they probably lose money.
But when I tried to dig deeper I found that nobody seemed to have any
proof that money was saved. Their answers were based on intelligent
reasoning, not evidence.
33
But there are other benefits to a design system, Including some that
CHAPTER
would appeal directly to management. Here are four benefits that will
get your managers or stakeholders on board:
1
34
Making Design Systems Work In The Real World by Laura Elizabeth
This all goes back to what we talked about earlier. Ask yourself, “What
is my manager being judged on?” Are they being judged on how well
designers and developers work together? Not exactly. They’re being
judged on how they fare next to their competitors; they’re being
judged on profit, jobs being created, growth.
Now we know the outcomes we’re looking for, we can get a better idea
of how to measure those effects. We can split these into two categories:
measuring based on shipping, and measuring based on time saved.
Let’s look at two case studies that have made these work.
35
Not only that, they found that with the design system they were
CHAPTER
the time.
We were able to sell and grow the team because of the already
apparent side effects from the simple UI components that we
developed in the first weeks. From the very beginning, there was
already customer value created. I would advise teams to start even
if they don’t feel like all the conditions are met. A small team and
system can already provide huge benefits.
36
Making Design Systems Work In The Real World by Laura Elizabeth
When you first start work on the system, there will probably be a ton of
excitement. People will be motivated, and you’ll feel like things are going
to move very quickly. Then you’ll get hit with roadblock after roadblock.
You’ll end up in these long, drawn out discussions about naming con-
ventions, or trying to work out some problem you hadn’t foreseen.
37
Before you know it, weeks have passed and your boss is asking to see
CHAPTER
progress on the design system. You don’t have anything and you’re
essentially spinning your wheels and the program risks getting axed.
1
What tends to happen is that we mix up these two tasks. We’re imple-
menting something and the conversation turns to strategy. We get on
tangents, we find new issues that we hadn’t considered before; and then
at the end of the day, week, or month, we realize we’re no farther along.
We’ve made no measurable progress, aside from a lot of conversations.
38
Making Design Systems Work In The Real World by Laura Elizabeth
These are questions that are probably going to require a lot of discus-
sion. Saving them up for when you have time to address them is going
to be a huge help for you and your team. It leaves you free to work on
making measurable progress with the design system, without being
held back by too many roadblocks. When you’re working on your
day-to-day stuff, if you find a conversation turns to strategy, you can
simply delay talking about it until your next strategy session.
If you still find yourself stalling and your team lacks motivation, Alan
Wilson, a designer at Adobe, suggests getting past a stall is often as
simple as taking a break:
Above all, we need to remember that stalls are natural, and they don’t
necessarily mean your system is broken. But they can also indicate
that there’s something wrong with the direction you’re going in. Some
companies find they can’t progress until they change how the system is
governed. Others find they can’t move forward until they change how
components are organized.
39
It doesn’t mean you’ve failed, but it does mean that you need to take a
CHAPTER
step back and try to figure out why your system is stalling. Is it because
the effort you’re putting in isn’t equal to the benefits? (And if so, why?)
Or is it just because you haven’t seen tangible progress for a few weeks?
1
It’s important not to gloss over this stage and come up with vague
principles. Dig deep into your company’s culture and find out whether
a design system is what’s needed right now. (If it’s not, don’t do it yet.)
As you’ll come to realize, if you haven’t already, a design system is not
about technology. It’s about people. A big part of this journey is going
to be spent talking to people and helping solve their problems.
40
Making Design Systems Work In The Real World by Laura Elizabeth
It’s much safer to test the waters with something small. Maybe there’s
a new feature you can test your system on? Perhaps there’s a particu-
lar flow that (if you did nothing else) would benefit a large amount of
people. Even a simple style guide is a good exercise to start seeing how
this could help your company.
When starting a design system, look for something small you can test
the system and its adoption on. The goal is to help people do their jobs
better and more efficiently. If you do that, you won’t get any backlash.
If you encounter resistance or people aren’t interested, you may need
to go back and assess why.
41
4. DON’T FOCUS ON WHAT EVERYONE ELSE IS DOING
CHAPTER
It’s easy to get caught up in what other companies are doing with
their design systems. Every day, it seems, bigger, better, shinier design
1
systems are unveiled. Focusing too much on this can end up crippling
your efforts to create a useful design system. You’ll see things other
companies are doing and think, “We need to do that too!” But remem-
ber that a design system’s success is measured by how many people it
helps, not how many claps you get on Medium.
A great example of a team who did their own thing despite everyone
else doing it another way is WeWork. When they built their design
system, Plasma, they assumed they would create a dedicated website
to show the system, specs, examples, and guidelines because it was
the done thing. They started a Google doc to get all their components
documented before pushing it to a live website, but over time they real-
ized the doc had everything they need to document a design system.
It has built-in navigation, the ability to add comments and collaborate,
it’s accessible to everyone on their team, and doesn’t need any special
skills to update it.
Andrew Couldwell, the design lead on Plasma said about the success of
the Google doc:
9 http://smashed.by/plasma
42
Making Design Systems Work In The Real World by Laura Elizabeth
5. BECOME AN EVANGELIST…
Having at least one person in charge of keeping the design system
front-of-mind is going to be a huge help when working on a design
system. When you’re starting out, it’s not likely that you’re going to
have a full-time role for a design systems lead (though as you grow,
this becomes a necessity), but you will benefit from somebody who
will act as an evangelist for the system.
43
The evangelists job is to be in charge of the design system, acting as
CHAPTER
a go-between for everybody using it. For that reason, it’s important to
have someone who can easily speak to designers, developers, market-
ers, editors etc. You’re looking for a people person who’s incredibly
1
These people are also in charge of educating the team about design
systems and keeping everyone updated with progress. Alan Lindsay
from Experian organized an entire conference called One UX for their
team members around the world to learn about design systems and get
excited about using them.
Even though Experian has incredible design talent and has always
been a very innovative company, prior to the conference they oper-
ated in disparate design teams that sometimes didn’t even know of
one another’s existence. Alan ended up becoming “chief evangelist.”
He uncovered pockets of amazing talent all across the globe, and
organized a community to discuss challenges. They eventually met
together at One UX to work out solutions, including creating the
global design system they enjoy today. Considering so many people
contribute to the design system, they found having at least one person
in charge of evangelism a huge help:
44
Making Design Systems Work In The Real World by Laura Elizabeth
If getting people to use your design system is like pulling teeth, it’s not
doing its job.
45
A Design System Is About People
CHAPTER
ecule or an organism? These are certainly valid questions but not the
most important ones. Design systems that succeed are not the ones
with the best tech, they’re the ones that help the most people.
“It’s not about your design skills, it’s not about your engineering
skills, it’s about your problem and people skills. Trying to find that
level of people skills and engineering is difficult. You need to get
out there and speak to people, help people out and be really proac-
tive. That’s the hardest problem. It’s always people.”
46
chapter 2
Accessibility in Times
of Single-Page Apps
Marcy Sutton
Accessibility in Times of
CHAPTER
Single-Page Apps
2
by Marcy Sutton
1 http://smashed.by/a11yscenarios
48
Accessibility in Times of Single-Page Apps by Marcy Sutton
2 http://smashed.by/a11ybasics
3 http://smashed.by/15perc
4 http://smashed.by/allbenefit
49
It’s critical for users with disabilities that collectively we do better at
CHAPTER
this. Fortunately, there are techniques and tools you can use to encour-
age better coding practices, catching accessibility violations before
they ship.
2
To help you work through these challenges and provide more acces-
sible user experiences, in this chapter we’ll dive in to the aspects of
single-page apps that impact accessibility, including HTML and ARIA
semantics, keyboard interactivity, focus management, screen reader
announcements, and tooling. By the end of this chapter, no matter which
framework you use (if any), you should have enough knowledge and
skills to work accessibility into a modern JavaScript web application.
5 http://smashed.by/srmodes
50
Accessibility in Times of Single-Page Apps by Marcy Sutton
ous includes and templates. When rendered, these come together with
headings to form a complete page structure that’s helpful for blind
users to understand and navigate.
// component-main.js
import SubnavView from "./subnav-view.js"
import ContentView from "./content-view.js"
export default function MainView (props) {
const { view, className } = props
return <main tabIndex="-1" {...props}>,
<h1>{i18n`My Single Page App`)}</h1>,
<SubnavView className="panel-subnav" />,
<ContentView className="panel-main" />,
</main>
}
6 http://smashed.by/html5ally
51
Customizable components can fit easily into any content hierarchy with-
CHAPTER
out a headache.
Semantics are all about choosing the best candidate element for the the
2
7 http://wtfforms.com/
52
Accessibility in Times of Single-Page Apps by Marcy Sutton
Interactivity
There’s a lurking misconception we should clarify right off the bat:
unless explicitly turned off by the user in their web browser, screen
reading software does use JavaScript when announcing content. In
fact, this is a helpful reality when coding JavaScript applications for
both blind users and sighted keyboard users.
8 http://smashed.by/domfactory
9 http://smashed.by/userinputangular
10 http://smashed.by/multimodal
11 http://smashed.by/affordances
12 http://smashed.by/a11ycards
53
carpal tunnel or developer elbow flares up and you can’t use a mouse
CHAPTER
13 http://smashed.by/ariacheckedstate
14 http://smashed.by/a11yname
54
Accessibility in Times of Single-Page Apps by Marcy Sutton
For custom widgets with ARIA, start with tabindex="0"15 and avoid
positive integer values unless you’re dying to manage an entire applica-
tion’s tab order (spoiler: it’s highly error-prone, and your footer should
absolutely not come first in the tab order). You can remove a keyboard-
interactive element from the tab order using tabindex="-1". This also
has the effect of making any element focusable by script, such as a focus
management target (see the upcoming section for more information).
FOCUS STYLING
No matter what element or technique you use for keyboard interactiv-
ity, it’s critically important to include visible focus states so users know
where they are on the screen. Resist using the horribly exclusionary
sledge hammer * { outline: 0; } in your CSS, which removes focus
outlines for everyone. Instead, for truly picky users16 you can employ
tools to be more specific with input styling, such as What Input,17 or
the proposed CSS4 pseudo-selector :focus-visible.18
Including :focus alongside your :hover CSS styles is also a good way
to go – it highlights that something interactive by mouse should
be focusable too. However, if you’re using custom :focus styles as a
replacement for the browser’s default focus style (often a blue ring or
dotted border) on everything, make sure you’ve tested every user inter-
action for a visible and contrasting focus style.
15 http://smashed.by/tabindex
16 http://smashed.by/buttonfocus
17 http://smashed.by/whatinput
18 http://smashed.by/focusvisible
55
// styling focus and hover at the same time
CHAPTER
a:link {
color: black;
text-decoration: underline;
}
2
a:hover, a:focus {
color: white;
background-color: black;
text-decoration: none;
}
You can bind a click event to any element, including a <div> (and in
rare cases, this is fine), but the problem is that a <div> isn’t focusable by
default. We have to do a significant amount of work to make it accessible.
<div role="button"
onclick="doTheThing()"
onkeydown="alsoDoTheThing()"
tabindex="0">
All this work just to make a DIV accessible
</div>
56
Accessibility in Times of Single-Page Apps by Marcy Sutton
Or, we can just use a <button> element and be done with it:
If styling is your reason for using a <div> button, here’s some common
reset CSS so you can put that argument to rest:
button {
background-color: black;
border-radius: 0;
border: none;
color: white;
-webkit-appearance: none;
}
Focus Management
In the absence of traditional page reloads, it’s critical to manage the
user’s keyboard focus to guide them through your JavaScript applica-
tion. Views are updating; UI components are toggling with the mouse;
modal windows are opening and closing; items are being deleted from
the DOM.
Can you guess what happens to the user’s keyboard focus in these
scenarios? If you don’t explicitly handle it, focus can be lost or do
absolutely nothing; these are huge barriers to someone relying on the
keyboard to navigate.
It’s worth mentioning that not every situation calls for managing
focus with JavaScript. Later in this chapter, we’ll discuss screen reader
announcements for the times a user’s focus shouldn’t be moved, but
they still need to be notified that things are happening.
57
These are two different tools at our disposal for creating accessible
CHAPTER
UPDATING VIEWS
As mentioned above, when a user clicks a link going to a different page
in your single-page app, they are usually taken there asynchronously,
without a traditional page reload. Sighted mouse users can see the
transition, navigate the page, and continue on their way. However, for a
keyboard or screen reader user, their focus is often left in the same place:
the link they clicked with the Enter key. If this link is inside of a naviga-
tion drawer that opens over the content, the drawer may stay open until
they tab through the page and click elsewhere. For these users, we can
create a more accessible keyboard experience by sending focus to the
new content and closing any side navs or component layers.
58
Accessibility in Times of Single-Page Apps by Marcy Sutton
}
render() {
return (
<Router>
<div className="App">
<div className="menu-wrap">
<Menu
isOpen={this.state.menuOpen}
onStateChange={ this.isMenuOpen.bind(this) }>
<ul>
<li><Link
to="/"
ref={this.firstMenuItem}
onClick={this.pageFocus.bind(this)}
>
<i className="fa fa-fw fa-home" aria-hidden="true" />
<span>Home</span>
</Link></li>
<li><Link
to="/gearlist"
onClick={this.pageFocus.bind(this)}>
<i className="fa fa-fw fa-suitcase" aria-hidden="true" />
<span>Gear Packing List</span>
</Link></li>
<li><Link
to="/trips"
onClick={this.pageFocus.bind(this)}>
<i className="fa fa-fw fa-plane" aria-hidden="true" />
<span>Trip Suggestions</span>
</Link></li>
</ul>
</Menu>
</div>
<div className="primary">
<main id="main" ref={this.main} tabIndex="-1">
<Route path="/" component={Home} />
<Route path="/gearlist" component={GearList} />
<Route path="/trips" component={Trips} />
</main>
</div>
</div>
</Router>
);
}
}
export default App;
59
In this example, our app instantiates a menu component with an unor-
CHAPTER
dered list and links inside. Those links hook into React Router, which
outputs accessible anchor elements with href attributes and proper
client-side routing. We can provide additional interaction support for
2
1. Close the open menu when the view changes (if not already
handled)
2. Send focus to the main element, putting keyboard and screen
reader users in the appropriate place in the page
Rather than leave them in the same spot without a page reload, we
know where their focus should be sent by the link they clicked. (The
new createRef API19 in React 16.3.1 makes this easy.) By handling focus
gracefully, users aren’t forced to tab through the entire app looking for
the right spot.
For widgets and overlays that appear over the content, managing focus
is especially important if the trigger (usually a link or a button) and the
widget contents are in different areas of the DOM. Hitting the Tab key
to reach something seemingly close by can have unexpected results,
19 http://smashed.by/reactref
60
Accessibility in Times of Single-Page Apps by Marcy Sutton
As a convention, use JavaScript to send focus into new layers when they
open, putting the user’s focus in the right place. Also be sure to wire up
the Escape key to provide an easy exit, sending focus back to the trigger-
ing UI control. This common navigation paradigm applies to multiple
patterns, including modal windows, custom listboxes, and tooltips.
You can read more about common keyboard navigation in the ARIA
Authoring Practices Guide.20
SKIP LINKS
A common convention of large applications is to provide one or two
skip links to the most important HTML sections. An unordered list
of links can point users to the major landmarks of your app, using
in-page links and the tabindex="-1" attribute (needed to catch focus in
some browsers):
20 http://smashed.by/waiaria
21 http://smashed.by/offerchoice
61
<ul class="skip-links>
CHAPTER
62
Accessibility in Times of Single-Page Apps by Marcy Sutton
• Walk the DOM23 for your inactive content and put tabindex="-1"
on each focusable item inside to remove it from the tab order.
(Check out the helpers from ally.js.)24
22 http://smashed.by/focustrap
23 http://smashed.by/walkthedom
24 http://smashed.by/focusable
63
• Put aria-hidden="true" on the inactive content to remove it from
CHAPTER
<body>
<main id="app" aria-hidden="true" class="dimmed">
<h1>My App</h1>
<p>You’ve come to the right place. <a href="/about" tabindex="-
1">About the author</a></p>
<button onclick="openModal()" tabindex="-1">Sign up for updates</
button>
</main>
<dialog aria-labelledby="modal-header">
<button class="close" aria-label="Close modal"
onclick="closeModal()">
<span aria-hidden="true" class="icon-close"></span>
</button>
<h2 id="modal-header">Sign up</h2>
<form></form>
</dialog>
<script>
var app = document.querySelector('main');
var triggerBtn = app.querySelector('button');
var dialog = document.querySelector('dialog');
function focusTrap(state) {
if (state.active) {
// enable focus trap
} else {
// disable focus trap
}
}
function openModal() {
app.setAttribute('aria-hidden', 'true');
focusTrap({ active: true });
modal.setAttribute('open', '');
modal.firstChild.focus();
}
function closeModal() {
modal.removeAttribute('open');
64
Accessibility in Times of Single-Page Apps by Marcy Sutton
Here’s the same example with inert, which requires less work than
handling both aria-hidden="true" and tabindex="-1" on each interac-
tive element:
<body>
<main id="app" inert>
<h1>My App</h1>
<p>You’ve come to the right place. <a href="/about">About the
author</a></p>
<button onclick="openModal()">Sign up for updates</button>
</main>
<dialog aria-labelledby="modal-header">
<button class="close" aria-label="Close modal">
<span aria-hidden="true" class="icon-close"></span>
</button>
25 http://smashed.by/inert
65
<h2 id="modal-header">Sign up</h2>
CHAPTER
<form>...</form>
</dialog>
<script>
var app = document.querySelector('main');
2
Some user interfaces provide a way to delete items inline; for exam-
ple, in a sortable list. If the user’s focus is on something to be deleted
(because that’s where the delete button is), their focus can get lost.
26 http://smashed.by/focusstart
66
Accessibility in Times of Single-Page Apps by Marcy Sutton
Basic sorting interface with items that can be deleted. © 2018 Rainier McCheddarton.
67
return api;
CHAPTER
});
// usage:
var list = new DeletableList();
list.init();
2
DEBUGGING FOCUS
A focus management tip: add a native focusin JavaScript listener to log
the currently focused element with document.activeElement. Open
your favorite browser’s developer tools console and tab through the
whole page with your keyboard. Since the focusin event is bound to the
document instead of focus on individual UI controls, this technique is
very effective for finding elements you forgot to disable for keyboard
and screen reader users. You might be surprised at what you find!
document.addEventListener('focusin', function() {
console.log('focused:', document.activeElement);
});
// focused: <button>Close Menu</button>
// oh, my focus went inside of the inactive sidenav! OOPS!
Notifying Users
Although focus management is effective at announcing content, some-
times changes occur in your app away from the user’s keyboard focus
point and it would be horribly disorienting to send them over there. An
example would be if a screen reader user was typing in an auto-saving
form, and a visual alert calmly popped up every so often saying, “Con-
tent saved!” – it would be pretty infuriating to have your focus moved
to announce that alert as you were typing. Or, if the view changes after
they click a link in your single-page app, you might want to send focus
to the main element and announce the overall page change (including
the header, above main in the DOM).
68
Accessibility in Times of Single-Page Apps by Marcy Sutton
In those scenarios, it might make more sense to use ARIA live regions
to announce new content in screen readers. They’re decently sup-
ported, although, by design, repeated announcements of the exact
same message (like “Content saved!”) won’t be announced. In those
cases (and in the event of stubborn, not-happening announcements),
you might try using two regions and trading off, like in ngA11y27 or
react-aria-live.28
Here are some guidelines for using ARIA live regions effectively:
• Don’t disrupt a screen reader user’s flow while typing with asser-
tive or too many alerts.
• Using live region roles along with aria-live properties will maxi-
mize compatibility.
• Try trading off between two live regions of the same politeness
level if different messages aren’t being announced.
27 http://smashed.by/whatinput
28 http://smashed.by/reactaria
29 http://smashed.by/liveregions
69
// Maximizing compatibility with aria-live and role=status
CHAPTER
30 http://smashed.by/vueform
70
Accessibility in Times of Single-Page Apps by Marcy Sutton
71
Putting an ARIA live region role on an existing element means it can
CHAPTER
act as a screen reader announcement center. Make sure the live region
exists when the page loads, and doesn’t have CSS hiding its display
from everyone. By using visually hidden (or screen reader-only) CSS31
2
instead, you can render content to screen reader users while not dis-
playing it for sighted users. Be sure to test in multiple screen readers,
including JAWS, NVDA, and VoiceOver for both macOS and iOS. Live
regions can be a little tricky sometimes, so test in the major screen
readers for browsers your team supports.
Here’s an example:
<div class="custom-input">
<label for="input0">
Content title
<input type="text" readonly placeholder="I love dogs" id="input0"
tabindex="-1">
</label>
<button aria-describedby="input0">
<span class="edit">Edit</span>
<span class="done">Done</span>
</button>
</div>
31 http://smashed.by/hiddencontent
72
Accessibility in Times of Single-Page Apps by Marcy Sutton
73
} else {
CHAPTER
disableInput(el);
}
} else {
customInputBtn.focus();
2
}
});
}
function enableInput(el) {
var input = el.querySelector('input');
input.removeAttribute('readonly');
input.removeAttribute('tabindex');
liveRegion.textContent = 'Now Editing';
input.focus();
}
function disableInput(el) {
var btn = el.querySelector('button');
var input = el.querySelector('input');
input.setAttribute('readonly', '');
input.setAttribute('tabindex', '-1');
btn.focus();
liveRegion.textContent = 'Done editing';
}
32 http://smashed.by/customedit
74
Accessibility in Times of Single-Page Apps by Marcy Sutton
The fun of using JavaScript means we can craft highly interactive web
applications and components. By truly recognizing how our HTML,
CSS, and JavaScript come together to improve web accessibility, we can
learn how to create higher-quality user interfaces that enable users
rather than get in their way.
33 http://smashed.by/brokenwindows
34 http://smashed.by/eslintplugin
75
tools like axe-core35 and Tenon.io36 can integrate into your automated
CHAPTER
It helps to have CSS and JavaScript applied to your markup when test-
ing for accessibility issues, so you don’t encounter red herrings. You’ll
want to write tests to assert behavior and quality in all of your app’s
various states, including opening menus and modals. Using headless
browsers like Chrome or PhantomJS (where there is no graphical user
interface), or real browser instances using Selenium WebDriver,37 you
can script a lot of this behavior. Write tests that assert keyboard func-
tionality in your own app, including focus management. You can cover
even more ground with common accessibility issues using an acces-
sibility API like axe-core, or its iframe-friendly Selenium integration,
axe-webdriverjs.38
By using an accessibility API in your tests, you can catch a lot of com-
mon accessibility issues without having to maintain the infrastructure
for them yourself:
35 http://smashed.by/axecore
36 http://smashed.by/tenon
37 http://smashed.by/selenium
38 http://smashed.by/webdriverjs
76
Accessibility in Times of Single-Page Apps by Marcy Sutton
Some items could be tested manually, and perhaps that’s the best place
to start for harder to assert items like visible focus states. However, at
some point it’s a better return on investment to automate testing so
you don’t have to spend human energy repeating the same tasks over
and over.
77
When writing your automated tests, some of the items above are best
CHAPTER
covered with small units, while others might be more suited to browser
integration tests; it depends on the task at hand. Are you asserting
something that can remain isolated with no dependencies in a unit test,
2
39 http://smashed.by/a11ytests
78
Accessibility in Times of Single-Page Apps by Marcy Sutton
• Be generous with color contrast; it not only helps people with low
vision and color deficiency, but also low-contrast projectors and
outdoor displays.
• Screen readers have ways of navigating other than using the Tab
key, so you don’t need to make everything focusable.
• Test your app with real users, including users with disabilities.
Organizations like Knowbility’s Access Works41 can help.
40 http://smashed.by/ariapractices
41 http://smashed.by/accessworks
79
Conclusion
Developers are capable of so much with JavaScript these days – we can
learn new APIs and build complex applications that look great on our
resumés. But who are you building those apps for? If you’re selling a
product online or trying to expand reach, doesn’t it make more sense
to include as many users as you can? People with disabilities want to
participate in life online along with everyone else. By building inclusive
design into your apps and really baking it in with automated tests, you’ll
raise the bar for quality software and be the hero of so many users.
Rachel Andrew
Production-Ready CSS Grid
CHAPTER
Layouts
3
by Rachel Andrew
I’ll cover some of the main things that trip people up when starting to
use Grid Layout. I’ll also look at some fundamental parts of CSS that
become very important when using layout methods such as Grid and
Flexible Box Layout (Flexbox). Key to both of these methods are con-
cepts of sizing, space distribution and alignment. My hope is that by
reading this chapter, and building the examples, you’ll have a better
understanding not only of Grid, but of CSS layout as a whole.
82
Production-Ready CSS Grid Layouts by Rachel Andrew
.wrapper {
max-width: 980px;
margin: 0 auto;
padding-right: 2.093333%;
}
.col {
margin-bottom: 1em;
margin-left: 2.093333%;
width: 6.20%;
float: left;
}
.row::after {
content: "";
display: block;
clear: both;
}
.col.span2 { width: 14.493333%; } /* total of 2 column tracks plus 1
margin-left */
.col.span3 { width: 22.786666%; } /* total of 3 column tracks plus 2
margin-left */
.col.span4 { width: 31.079999%; } /* total of 4 column tracks plus 3
margin-left */
.col.push2 { margin-left: 18.679999%; } /* total of 2 column tracks
plus margins */
83
We then gained Flexbox. However this one-dimensional layout method
CHAPTER
.wrapper {
max-width: 980px;
margin: 0 auto;
padding-right: 2.093333%;
}
.col {
margin-bottom: 1em;
margin-left: 2.093333%;
width: 6.20%;
flex: 0 0 auto;
}
.row {
display: flex;
}
.col.span2 { width: 14.493333%; } /* total of 2 column tracks plus 1
margin-left */
.col.span3 { width: 22.786666%; } /* total of 3 column tracks plus 2
margin-left */
.col.span4 { width: 31.079999%; } /* total of 4 column tracks plus 3
margin-left */
.col.push2 { margin-left: 18.679999%; } /* total of 2 column tracks plus
margins */
84
Production-Ready CSS Grid Layouts by Rachel Andrew
Once we have our track sizing, all that needs to be added to rules on
the items is a description of how many column or row tracks the item
should span. That might start from a fixed position on the grid, such as
the item in the code below with a class of special, which starts on line
3 and ends on line 7. Or we might use auto-placement, asking items to
start where they would be placed with auto and span a certain number
of tracks.
.wrapper {
max-width: 980px;
margin: 0 auto;
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(12, minmax(0,1fr));
}
.col.span2 { grid-column: auto / span 2; } /* start where grid would
place the item, span 2 tracks */
.col.span3 { grid-column: auto / span 3; }
.col.span4 { grid-column: auto / span 4; }
.col.special { grid-column: 3 / 7; }
85
the CSS Values and Units specification.1 You can create tracks with
CHAPTER
In the following example, I have created a grid with one column set
to 20ch; a second column is defined in pixels and a third as ems. The
first column will be as wide as 20 characters of the character 0 (zero) in
the font size used for the grid container. The second is 400 pixels wide
and the third 8em. I have added a second row of grid items to demon-
strate that this sizing continues to the next row of the grid. It is vital to
remember that Grid is a two-dimensional model. If you set sizing for a
track, it takes effect all the way down the columns and along the rows.
.grid {
display: grid;
grid-template-columns: 20ch 400px 8em;
grid-gap: 20px;
}
1 http://smashed.by/lengths
86
Production-Ready CSS Grid Layouts by Rachel Andrew
You can also use percentages, just as with a float-based or flex “grid.”
This approach can be useful if you are trying to ensure that a grid com-
ponent introduced into an existing design lines up with other elements,
which likely have percentage-based sizing. As we shall see, however,
using percentages for sizing and having to work out the math yourself is
something we’ll probably need to do less of in the future.
.grid {
display: grid;
grid-template-columns: 22% 50% 22%;
grid-column-gap: 3%;
grid-row-gap: 2em;
}
87
With row gaps, what the percentage resolves against is less obvious.
CHAPTER
Should it use the width, so that a gap of 10% is the same for rows and
columns, or should it use the height? Currently, results across brows-
ers are not the same, so you should avoid using a percentage value for
3
In the example below I have a grid container with one 1fr track, and
two 2fr tracks. This means that the available space in the grid con-
tainer is divided into five: one part given to the first track, and two
parts each to the second and third tracks.
.grid {
display: grid;
grid-template-columns: 1fr 2fr 2fr;
}
2 http://smashed.by/frunit
88
Production-Ready CSS Grid Layouts by Rachel Andrew
The first track is 1fr, the next two are each 2fr.
To achieve a similar layout using Flexbox, you would use the CSS
below. Using the flex properties I set my first item to a flex-grow factor
of 1, and the next two items to have a flex-grow factor of 2:
.flex {
display: flex;
}
.flex > :nth-child(1) {
flex: 1 0 0;
}
.flex > :nth-child(2),
.flex > :nth-child(3){
flex: 2 0 0 ;
}
This example also highlights the point made earlier about Grid work-
ing on the container and Flexbox on the items. With Flexbox you target
only that individual item; we have to go to each item and decide how
much it can grow or shrink in proportion to the others. With Grid we
size the full track, which means that every item in that column will go
into the track created by the fr unit sizing.
89
We can see a very simple example of this if we add 20-pixel gaps to our
CHAPTER
grid. Before the fr units are calculated, the amount of space required
for the gaps is taken away from the total available space.
3
.grid {
display: grid;
grid-template-columns: 1fr 2fr 2fr;
grid-gap: 20px;
}
The fr units share out the space after accounting for the gaps.
The fr unit shares out available space, which means you can ask it only
to share out space left after fixed-size tracks have been created. So if
your layout requires some fixed-size elements in it, this is not a prob-
lem. In the next example, I have a first track of 10ch, one of 200px, and
then two flexible tracks of 1fr and 2fr. The available space the flexible
length tracks have to play with is whatever is left after the two fixed
tracks have been laid out.
This available space is then shared into three parts: one part given to
the first track, and two parts to the second track.
.grid {
display: grid;
grid-template-columns: 10ch 200px 1fr 2fr;
grid-gap: 20px;
}
90
Production-Ready CSS Grid Layouts by Rachel Andrew
Content-Sized Tracks
In addition to tracks using familiar length units, and those using
a flexible length, we also have ways to allow the content to dictate
the track sizing. When we start to do this, we have to keep in mind
the two-dimensional nature of a Grid layout. If an item in a row or a
column track is able to change the size of the track, it will change the
size of that track all the way down the column or along the row. This is
essentially the big difference between Grid and Flexbox. If you do not
want items to change the size of entire rows in both dimensions, a flex
layout is probably what you’re looking for.
91
If auto is used for column track sizing and you have not used any of
CHAPTER
the CSS Box Alignment properties discussed later in this chapter, the
column will stretch to take up available space in the grid – so the track
may end up larger than is required for the content inside. In the next
3
.grid {
display: grid;
grid-template-columns: auto auto auto;
grid-gap: 20px;
justify-content: start;
}
92
Production-Ready CSS Grid Layouts by Rachel Andrew
These keywords are not specifically for Grid layout. They can be used
anywhere you might use a width or a height (although browser sup-
port is currently limited outside Grid). For example, we can look at how
min-content behaves by giving a div which contains a string of text a
width of min-content.
.min-content {
width: min-content;
}
The element displays on the page with a width defined by the longest
word in the string. The text takes all possible soft-wrapping opportuni-
ties, becoming as small as it can be.
.max-content {
width: max-content;
}
3 http://smashed.by/minmaxcontent
93
CHAPTER
3
The first box has a width of min-content, and the second a width of max-content.
.grid {
display: grid;
grid-template-columns: min-content max-content min-content;
grid-gap: 20px;
}
94
Production-Ready CSS Grid Layouts by Rachel Andrew
Using max-content to force a track not to wrap at all has the potential
to cause overflows. There is a final new keyword of fit-content that
we can use to get the best of both worlds. It provides a defined limit to
the maximum size a track can reach.
.grid {
display: grid;
grid-template-columns: fit-content(200px) fit-content(200px) fit-
content(15ch);
grid-gap: 20px;
}
95
In the first column track there is nothing that makes the track wider
CHAPTER
than 200 pixels, so the track uses max-content sizing. The second track
has a long string of text, so when it hits 200 pixels, the track stops
growing and the content wraps. The same is true for the third track
3
sized at 15ch (15 characters). There is a string in that track longer than
15ch and so the track maxes out and the content wraps.
96
Production-Ready CSS Grid Layouts by Rachel Andrew
This allows some interesting use cases. For example, I might want to
have a media object that sometimes contains an icon and other times a
larger image, which might need to be scaled down inside the container
depending on the available width.
If I use fit-content for the track size of the image, I can show the
image at full size if it is smaller than the maximum specified, but
clamp the track at a maximum so a large image does not spread out too
much, leaving no space for my text. The column for the text is set to
1fr so takes up whatever space is left.
.media {
width: 500px;
display: grid;
grid-gap: 20px;
grid-template-columns: fit-content(250px) 1fr;
}
Don’t forget these content-sizing keywords as you think about the grid
for your design: they can be incredibly useful. In production, I tend
to use these for small components, like our media object above. The
keywords mean I can have fewer individual design patterns for compo-
nents, allowing Grid to do a lot of the work when figuring out how big
things need to be.
97
CHAPTER
3
minmax()
The content-sizing keywords – and fit-content in particular – help
you define tracks that base their size on their content. We also have
another flexible way to define track sizing: using the minmax() func-
tion. This track-sizing function defines a track with a minimum and
maximum size.
98
Production-Ready CSS Grid Layouts by Rachel Andrew
grid-auto-rows (the default value) then row tracks will be created and
content will not overflow the track.
.grid {
display: grid;
grid-auto-rows: auto; // this is the default value
}
In some designs, you might need to create row tracks that are always a
minimum height. If you were to use a fixed-length unit for that height
(for example, 100 pixels), then content would overflow the fixed-height
track if more content than you designed for was added. If we know
anything about designing for the web, it is that at some point more
content will always be added!
This type of situation is one that minmax() deals with very well. In the
example below, I have set up a grid with rows created in the implicit
grid with a minimum of 100 pixels and a maximum of auto.
.grid {
display: grid;
width: 500px;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
grid-auto-rows: minmax(100px, auto);
}
Rows which contain content that is shorter than 100 pixels will be 100
pixels tall. Rows with more content than can fit in 100 pixels will be
taller as they go to auto. When you design a layout to be built using
Grid, you can think about this kind of functionality and be more pre-
cise in your layouts, knowing that in the situation where the content
might overflow, Grid can help you deal with it.
99
CHAPTER
3
Rows created using minmax() can be a fixed height, but still grow if there is more content added.
100
Production-Ready CSS Grid Layouts by Rachel Andrew
Repeating
There is another use of minmax() that means you can specify a track list-
ing that will create as many tracks as will fit, with a specified minimum.
To do this, you need to use repeat notation when declaring your grid.
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
You can replace the number of times to repeat (4 in the example above)
with one of two keywords: auto-fill or auto-fit. You will then need to
use a length unit as the value to repeat, and Grid will create as many
column tracks of that length as will fit into the container.
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, 200px);
}
We can’t use 1fr as the value to get as many columns as will fit,
because 1fr will stretch to take up all available space: the number of
1fr tracks you can have auto-filled in a container is one. However, we
can use 1fr as a maximum with minmax.
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px,1fr));
}
101
With this statement as our track definition, the browser will fill as
CHAPTER
as many flexible tracks as will fit, with a minimum size of 200 pixels.
The auto-fill and auto-fit keywords work in the same way as each
other unless you do not have enough grid items to fill the container. In
that case, if you use auto-fill, any completely empty tracks will still
have space reserved for them – there would be a space at the end. With
auto-fit, after laying out the items any completely empty tracks are
collapsed to zero, and the space redistributed to the tracks that contain
grid items.
As you can see, you have a range of solutions to draw on when sizing
tracks. When you develop components for your sites, keep in mind
these different options. Use them in combination with one another
and you will probably find that you need to resort to media queries far
less, and are able to create more reusable components.
Box Alignment
We frequently look at grid layout examples where elements on the grid
are stretched over the entire grid area they have been placed into. The
default behavior of an element without an intrinsic aspect ratio is to
stretch in both directions.
.grid {
height: 80vh;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
102
Production-Ready CSS Grid Layouts by Rachel Andrew
.grid :first-child {
grid-row: 1 / 3;
grid-column: 1 / 3;
}
103
.grid {
CHAPTER
height: 80vh;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
3
align-items: start;
}
.grid {
height: 80vh;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
align-items: start;
justify-items: start;
}
104
Production-Ready CSS Grid Layouts by Rachel Andrew
It’s much easier to see what is happening when you align and justify
items if you use Firefox’s Grid Inspector. You’ll then be able to see the
extent of each grid area and the alignment of the item inside it.
105
The available values for align-items and justify-items are as follows:
CHAPTER
• auto
3
• normal
• start
• end
• center
• stretch
• baseline
• first baseline
• last baseline
Note: Specifying baseline as the value will align items with the
first baseline in the alignment group – this will be the same as
specifying first baseline. The value last baseline aligns the
items with the last baseline of the group. All Grid-supporting
browsers support baseline. At the time of writing, only Firefox
supports first baseline and last baseline. Check the page on
MDN for up-to-date support information and examples.4
4 http://smashed.by/alignitems
106
Production-Ready CSS Grid Layouts by Rachel Andrew
.grid :nth-child(2) {
align-self: end;
}
.grid :nth-child(3) {
justify-self: center;
}
Note that the default stretching behavior only applies to items without an
intrinsic aspect ratio. Anything with an aspect ratio, such as an image, will
be aligned to start on both axes. This is to prevent an image being pulled
out of shape. (If that is what you actually want, then use align-self:
stretch and justify-self: stretch on the item concerned.)
107
In the following code, I’m creating three grid column tracks, each
CHAPTER
100px wide, and three row tracks that are 100 pixels tall. The grid con-
tainer is 500 pixels wide and 500 pixels tall. This means we have more
space in both dimensions than is needed to display the tracks.
3
.grid {
height: 500px;
width: 500px;
border: 2px dotted #fff;
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
grid-gap: 20px;
}
.grid :first-child {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
The properties used to align and justify tracks are align-content and
justify-content. If you do not provide a value, they default to start,
which is why our grid tracks line up at the start of the grid container
for both rows and columns.
• normal
• start
108
Production-Ready CSS Grid Layouts by Rachel Andrew
With more space in the container than needed for the tracks, items line up at the start.
• end
• stretch
• space-around
• space-between
• space-evenly
• baseline
• first baseline
• last baseline
109
There are no -self values of align-content and justify-content
CHAPTER
Note that any items spanning more than one grid cell will
need to absorb the extra space created if you use values such as
space-between, space-around or space-evenly.
110
Production-Ready CSS Grid Layouts by Rachel Andrew
.grid {
height: 500px;
width: 500px;
border: 2px dotted #fff;
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
grid-gap: 20px;
justify-content: space-between;
align-content: space-between;
}
In 2017, these properties were moved into the Box Alignment speci-
fication, to sit alongside the alignment properties. At this point they
were also renamed to make them generic properties that can be used
in other specifications, not just Grid. The renamed properties are
column-gap, row-gap and a shorthand of gap. Currently you need to
use the grid-* prefixed properties; browsers will be aliasing these to
the new properties, so should maintain support. To be bulletproof, you
could add both properties, in the same way we add the real property
name after vendor-prefixed properties.
.grid {
display: grid;
grid-gap: 10px;
gap: 10px;
}
111
If you combine grid gutters and alignment of tracks with align-content
CHAPTER
and justify-content, you should be aware that this can make the gut-
ters larger than expected, as seen in the previous example.
3
Responsive Design
Grid has been designed to be responsive by default, and because of this
we generally find we can use far fewer media queries and breakpoints
in our work. For example, we don’t need to work out percentages, as
the fr unit means we can let the computer do that work. We can dis-
play as many columns as will fit, with a minimum size being assigned.
The second approach is very similar to how existing grid systems work
in frameworks like Bootstrap. You always have a twelve-column grid, but
at narrower breakpoints items span more tracks of the grid. With a sim-
ple grid layout, laying out three boxes, we could have the following code:
112
Production-Ready CSS Grid Layouts by Rachel Andrew
.grid {
display: grid;
grid-template-columns: repeat(12, minmax(0,1fr));
grid-gap: 20px;
}
.col1, .col2, .col3 {
grid-column: 1 / -1;
}
@media (min-width: 500px) {
.col2 {
grid-column: 1 / 7;
}
.col3 {
grid-column: 7 / 13;
}
}
@media (min-width: 700px) {
.col1 {
grid-column: 1 / 5;
}
.col2 {
grid-column: 5 / 9;
}
.col3 {
grid-column: 9 / 13;
}
}
113
CHAPTER
3
Once we reach 500 pixels, I change the second and third box to display
as a split layout of half the number of columns each.
A medium breakpoint: the bottom two items each span six columns.
With more screen real estate, I have a one-row layout of three equal-
width boxes. In this way, we keep our twelve-column grid; where the
columns are narrower, we span more of them.
114
Production-Ready CSS Grid Layouts by Rachel Andrew
At the widest breakpoint there are three items each spanning four columns.
An alternate approach to redefining the grid can work well if you are
relying on auto-placement to lay out your items.
.grid {
display: grid;
grid-template-columns: 1fr;
grid-gap: 20px;
}
@media (min-width: 500px) {
.grid {
grid-template-columns: 1fr 2fr;
}
}
@media (min-width: 700px) {
.grid {
grid-template-columns: 1fr 2fr 1fr;
}
}
115
CHAPTER
3
We get slightly wider and have two columns of 1fr and 2fr.
The widest breakpoint has three columns. In this case, then, we have
different numbers of column tracks at the different breakpoints. You
can, of course, combine both approaches and redefine the grid, as well
as where items sit on the grid.
116
Production-Ready CSS Grid Layouts by Rachel Andrew
In general, you should find your use of media queries reduces – but
don’t be afraid to experiment. Sometimes adding a breakpoint is what
the design needs to provide the best experience.
At the time of writing, Edge, Chrome, Firefox, Opera, Safari, and iOS
Safari all support Grid Layout. IE10 and IE11 have support for the origi-
nal spec with an -ms prefix. Older browsers you might consider include:
117
• Safari and iOS Safari older than version 10.1
CHAPTER
Instead of searching for a polyfill, consider how using Grid Layout can
actually provide a better experience to people whose browsers don’t
support it. Grid allows you to create complex layout experiences for
supporting browsers with minimal CSS, while still offering a good
experience to those without support. Yes, it will be a little more work
than just throwing a polyfill at the problem, but by doing so you are
ensuring that support means providing a good experience, rather than
making the most important goal getting the site to look the same.
118
Production-Ready CSS Grid Layouts by Rachel Andrew
It turns out that CSS itself has evolved to a point where you can pro-
vide that good experience just by writing CSS, and you can do so in a
way that doesn’t involve completely replicating your code.
This means that you can use some old CSS, like floats or display:
table-cell to provide a Grid-type layout for older browsers, just as you
would in the past. The browsers that do not support Grid will use this
layout and ignore all the grid instructions. Browsers supporting Grid
Layout will continue, discover the grid instructions and apply those.
So we need to consider what happens if an item using another layout
method becomes a grid item.
• floats
• display: inline-block
• display: table
119
• the vertical-align property, along with
CHAPTER
• multi-column layout
Floated items or those that use the clear property, and which then
become a grid item, no longer exhibit any floating or clearing behavior,
as though they were never applied. This means that in the example
below, if the browser does not support Grid Layout, the user will see
the floated layout. If the browser supports Grid they get the grid layout
without any float behavior intruding.
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
.grid > * {
float: left;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
.grid > * {
display: inline-block;
}
120
Production-Ready CSS Grid Layouts by Rachel Andrew
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
.grid > * {
display: table-cell;
vertical-align: top;
}
When you use display: table-cell to create columns, CSS will create
what are known as anonymous boxes. These are the missing parts of the
table: a table cell in a real HTML table will be inside a <tr> element,
and that will be inside a <table> element. The anonymous boxes essen-
tially fix these missing parents. If your table-cell item becomes a grid
item, however, this happens before the boxes are generated, and so once
again the item will act as if the CSS tables display had never happened.
You can also use Flexbox as a fallback. If you have used the flex prop-
erty or individual flex-grow, flex-shrink or flex-basis properties on
the item, these will be ignored once it becomes a grid item.
.grid {
display: flex;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
121
}
CHAPTER
.grid > * {
flex: 1 1 auto;
}
3
Finally, don’t forget that Multi-column Layout can be used in some cases
as a fallback; for example, when laying out a list of card components or
images. Items will be displayed in columns rather than across the row,
but in some circumstances this can be useful. Apply column-count or
column-width on the container to make it a multi-column container. If you
then apply display: grid the column-* behavior will be ignored.
.grid {
column-count: 3;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
column-gap: 20px;
grid-gap: 20px;
}
122
Production-Ready CSS Grid Layouts by Rachel Andrew
Feature Queries
Because you can simply overwrite one layout method with another
means that, in many cases, you won’t need to fork your code and can
provide two different ways of creating the layout: a simple one using
old methods enhanced with your more complex design. There are cer-
tain situations, however, where things you need to create your layout
method for non-Grid browsers will also be interpreted by Grid-sup-
porting browsers. A key situation like this is when you have applied
widths to items.
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
.grid > * {
float: left;
width: 33%;
}
123
CHAPTER
3
The items are now one-third of the width of each grid area.
In this situation we need a way to tell the browser that if it supports Grid
Layout, it should not use the width, as the size of the item will now be con-
trolled by the grid area it is in. This is where CSS feature queries are useful.
Feature queries (using @supports) act very much like media queries, but
instead of querying the size of the viewport or device orientation, we check
whether the browser has support for a certain feature. If it does, we can do
things like override the width, setting it back to auto.
.grid > * {
float: left;
width: 33%;
}
@supports (display: grid) {
.grid > * {
width: auto;
}
}
124
Production-Ready CSS Grid Layouts by Rachel Andrew
5 http://smashed.by/cssgridstory
6 http://smashed.by/autoprefixer
125
The key points to note are as follows:
CHAPTER
If you have a high number of users with these browsers, you may
find that this old spec is helpful. It is definitely worth knowing it
exists, even if you only use it to solve a couple of small issues that
are real showstoppers for you.
7 http://smashed.by/iecssgrid
126
Production-Ready CSS Grid Layouts by Rachel Andrew
<ul class="gallery">
<li class="landscape"><img src="myimage.jpg" alt="alt text"></li>
<li class="portrait"><img src="myimage.jpg" alt="alt text"></li>
</ul>
img {
max-width: 100%;
display: block;
}
.gallery {
list-style: none;
margin: 0;
display: grid;
grid-gap: 2px;
grid-template-columns: repeat(auto-fill,minmax(200px, 1fr));
grid-auto-flow: dense;
}
.gallery img {
object-fit: cover;
height: 100%;
width: 100%;
}
.landscape {
grid-column: auto / span 2;
}
127
CHAPTER
3
Gallery component.
In a browser that does not support Grid Layout the images will display
in regular block flow, one after another.
I can then make a decision about how to display these images for other
browsers. Maybe I simply leave it alone: images displaying one after
another isn’t a terrible experience. It is likely, though, that you will feel
there are enough non-Grid browsers visiting your site to want to do a
bit more than that.
128
Production-Ready CSS Grid Layouts by Rachel Andrew
Which layout method I choose will depend on the browsers I know are
visiting my site. With an existing site you have this knowledge, which
can be pretty helpful! If I know that most browsers have Flexbox, then
my fallback could be Flexbox.
.gallery {
list-style: none;
margin: 0;
display: flex;
flex-wrap: wrap;
display: grid;
grid-gap: 2px;
grid-template-columns: repeat(auto-fill,minmax(200px, 1fr));
grid-auto-flow: dense;
}
.gallery > * {
flex: 1 1 200px;
}
.gallery img {
object-fit: cover;
height: 100%;
width: 100%;
}
.landscape {
flex: 1 1 400px;
grid-column: auto / span 2;
}
129
CHAPTER
3
The Flexbox version of the layout. The fallback requires six extra lines of code.
In this case, if a browser does not support Grid or Flexbox they will
still see the images displayed one after another in block layout. This
isn’t a disaster and is better than a completely broken layout.
If I know some very old browsers visit the site, I might decide to add
as my fallback a method supported by pretty much everything. So I am
picking an inline-block layout. This will never be as neat as the Grid,
130
Production-Ready CSS Grid Layouts by Rachel Andrew
or even Flexbox, version of the layout, but with a small amount of code we
can make a reasonable fallback, and as soon as those items become flex or
grid items, all inline-block behavior is removed.
For this layout, I also need to add a CSS feature query, with @supports, to
remove the width applied to items once we are in Grid Layout.
.gallery {
list-style: none;
margin: 0;
display: flex;
flex-wrap: wrap;
display: grid;
grid-gap: 2px;
grid-template-columns: repeat(auto-fill,minmax(200px, 1fr));
grid-auto-flow: dense;
}
.gallery > * {
display: inline-block;
vertical-align: top;
width: 33%;
flex: 1 1 200px;
}
@supports (display: flex) or (display: grid) {
.gallery > * {
width: auto;
}
}
.gallery img {
object-fit: cover;
height: 100%;
width: 100%;
}
.landscape {
flex: 1 1 400px;
grid-column: auto / span 2;
}
131
CHAPTER
3
132
Production-Ready CSS Grid Layouts by Rachel Andrew
133
has a version greater than 3.5, as support for bracketed lists was added
CHAPTER
in this version.8
IE11, with an -ms prefix, you might wonder if using that old version of
Grid is as simple as running Autoprefixer. The team at Autoprefixer
have made efforts to get this to work, and if you have very simple grid
layouts it may well do. However, as detailed earlier in this chapter, new
Grid Layout is fundamentally different to old Grid Layout. Test the
result of using Autoprefixer, if you try it, as it could well be that the
experience is worse than it would be if you served IE10 and 11 the same
fallbacks as used for other browsers. You always have the option of dis-
abling Grid in Autoprefixer if you find it makes a mess of your layouts,
by setting grid: false.
If you want to track the bugs we know about, you can keep an eye on my
GridBugs site.9 Everyone wants the Grid implementations to be as bug-
free as possible, so things are being fixed very quickly as they are found.
8 http://smashed.by/sassrelease
9 http://smashed.by/gridbugs
134
Production-Ready CSS Grid Layouts by Rachel Andrew
10 http://smashed.by/gridbyexample
11 http://smashed.by/mdncssgrid
12 http://smashed.by/smashingcss
13 http://smashed.by/gridbyexample2
135
chapter 4
A Strategy Guide to
CSS Custom Properties
Mike Riethmuller
A Strategy Guide to CSS
CHAPTER
Custom Properties
4
by Mike Riethmuller
CSS custom properties (also known as CSS variables) are
now supported in all modern browsers, and people are
starting to use them in production – great! But they’re
different from variables in preprocessors, and I’ve already
seen many examples of people using them without considering what
advantages they offer.
BASIC SYNTAX
With SCSS we use a dollar symbol to denote a variable:
$smashing-red: #d33a2c;
@smashing-red: #d33a2c;
138
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
We can give the var() function a second value to use as a fallback for
when the custom property is not defined:
.smashing-text {
color: var(--smashing-red, #d33a2c);
}
139
$breakpoint: 800px;
CHAPTER
$smashing-red: #d33a2c;
$smashing-things: ".smashing-text, .cats";
@media screen and (min-width: $breakpoint) {
#{$smashing-things} {
4
color: $smashing-red;
}
}
Custom properties have the same rules about where they are valid
as normal CSS properties. In fact, it’s far better to think of them as
dynamic properties rather than variables. Custom properties can only
be used inside a declaration block. That means custom properties are
tied to a selector. This can be the :root pseudo-class selector, or any
other valid selector.
You can retrieve the value of a custom property anywhere you would
otherwise assign a value to a CSS property. This means they can be
used as a single value, as part of a shorthand statement, or even inside
calc() equations.
.smashing-text, .cats {
color: var(--smashing-red);
margin: 0 var(--margin-horizontal);
padding: 0 calc(var(--margin-horizontal) / 2);
}
140
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
VALID VALUES
Almost any string can form a valid custom property value. There are a
few small exceptions to this, all of which you are unlikely to encounter.
(Examples include strings containing unmatched closing brackets or
an exclamation mark anywhere other than at the end of a property,
where it will be ignored.)
Custom property values are made of one or more tokens that describe
to the browser how the custom property should be interpreted. These
tokens can comprise numbers, units, color functions, and calc() state-
ments, as well as values from other custom properties.
.example {
--one: 5;
--two: var(--one, 2);
--three: calc(var(--two) * 2);
--color: hsl(var(--three), 50%, 50%);
--gradient: linear-gradient(to top, var(--color), tomato);
}
141
Custom properties are parsed and determined to be valid, but their val-
CHAPTER
ues are not computed until runtime. Therefore, changing the value of
the custom property --one in the example above will change the value
of all other custom properties.
4
.example {
--example-width: 50;
width: calc(var(--example-width) * 1%);
}
This will resolve to calc(50 * 1%) and a final used value of 50%. We can
do the same with almost any unit type. This can be useful for con-
verting values from JavaScript to CSS units, or where we want to add
values together, such as:
:root {
--animation-speed: 0.75;
}
.my-animation {
--seconds: 3;
animation-duration: calc(var(--seconds) * var(--animation-speed) * 1s);
}
142
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
This will result in calc(3 * 0.75 * 1s) which will resolve to 2.25s.
Techniques like this can be especially useful for theming and applying
user settings, particularly in combination with color functions and
CSS transitions.
$background: blue;
.blue {
background: $background;
}
$background: red;
.red {
background: $background;
}
results in:
.blue {
background: blue;
}
.red {
background: red;
}
143
Once rendered to CSS, the variables are gone. We could potentially
CHAPTER
read an entire .scss file and determine all of its output without knowing
anything about the HTML, browser, or other inputs; this is not the case
with custom properties.
4
$background: red;
.example {
$background: blue;
background: $background;
}
.example {
background: $background;
}
results in:
.example {
background: blue;
}
.example {
background: red;
}
144
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
a {
--link-color: black;
}
a:hover,
a:focus {
--link-color: tomato;
}
@media screen and (min-width: 600px) {
a {
--link-color: blue;
}
}
a {
color: var(--link-color);
}
CSS is similar, we have some things that global, and some things that
are local. Brand colours, vertical spacing, and typography are all things
you want applied globally and consistently across your website or
145
application. CSS also has local things. A button component might have
CHAPTER
small and large variants, and you wouldn’t want the sizes from these
buttons being applied to all input elements, or even every element on
the page.
4
146
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
places, and variables help with consistency. But it doesn’t make sense
for them to be dynamic. The value of these variables does not change
in any dynamic way.
147
My SCSS might look something like this:
CHAPTER
$button-sml: 1em;
$button-med: 1.5em;
4
$button-lrg: 2em;
.btn {
// Visual styles
}
.btn-sml {
font-size: $button-sml;
}
.btn-med {
font-size: $button-med;
}
.btn-lrg {
font-size: $button-lrg;
}
Obviously, this example would make more sense if I was using the
variables multiple times, or deriving margin and padding values from
the size variables. But the ability to quickly prototype different sizes
might be sufficient reason to use them.
148
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
Note: Did you know that $var is a valid value for a custom
property? Recent versions of Sass recognize this, and therefore
we need to interpolate variables assigned to custom properties,
like this: #{$var}. This tells Sass you want to output the value
of the variable, rather than just $var in the style sheet. This is
only needed for situations like custom properties, where variable
names can also be valid CSS.
If we take the button example above, and decide all buttons should use
the small variation on mobile devices, regardless of the class applied in
the HTML, this is now a more dynamic situation. For this, we should
use custom properties.
149
$button-sml: 1em;
CHAPTER
$button-med: 1.5em;
$button-lrg: 2em;
.btn {
--button-size: #{$button-sml};
4
}
@media screen and (min-width: 600px) {
.btn-med {
--button-size: #{$button-med};
}
.btn-lrg {
--button-size: #{$button-lrg};
}
}
.btn {
font-size: var(--button-size);
}
150
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
:root {
--font-scale: 1.2;
--font-size-1: 1rem;
--font-size-2: calc(var(--font-scale) * var(--font-size-1));
--font-size-3: calc(var(--font-scale) * var(--font-size-2));
--font-size-4: calc(var(--font-scale) * var(--font-size-3));
}
1 http://smashed.by/designtime
151
The code above generates a modular scale: a series of numbers that
CHAPTER
relate to one another through a ratio. They are often used in web
design to set font sizes and spacing.
4
The font-sizes are calculated at runtime, and you can change them by
updating only the value of the --font-scale property. For example:
This is clever, concise, and much quicker than calculating all the values
again should you want to change the scale. It’s also something I would
not do in production code. Although useful for prototyping, in produc-
tion I’d much prefer to see something like this:
:root {
--font-size-1: 1rem;
--font-size-2: 1.2rem;
--font-size-3: 1.44rem;
--font-size-4: 1.728rem;
}
@media screen and (min-width: 800px) {
:root {
--font-size-1: 1rem;
--font-size-2: 1.333rem;
--font-size-3: 1.777rem;
--font-size-4: 2.369rem;
}
}
152
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
I find it helpful to see what the actual values are, and global values
such as font scales don’t change frequently in production.
The example above is still not perfect. It violates the principle I men-
tioned earlier, that global values should be static. I’d much prefer
to use a preprocessor for these variables and convert them to locally
dynamic custom properties using the techniques demonstrated in the
section: “When to Use Custom Properties”.
153
:root {
CHAPTER
--font-size-small: 1.2rem;
--font-size-large: 2rem;
}
.example {
4
font-size: var(--font-size-small);
}
@media screen and (min-width: 800px) {
.example {
font-size: var(--font-size-large);
}
}
.example {
--example-font-size: 1.2rem;
}
@media screen and (min-width: 800px) {
.example {
--example-font-size: 2rem;
}
}
Finally, in a single place, retrieve the value of this custom property
and assign it to font-size:
.example {
font-size: var(--example-font-size);
}
So far in this chapter, media queries have only been used to change
the value of custom properties. You might also notice there is only one
place where the var() statement is used and regular CSS properties
are applied. This separation of variable declarations from property
declarations is intentional. There are many reasons for this, but the
benefits are most obvious when thinking about responsive design.
154
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
CSS custom properties can help us organise some of this logic related to
responsive design and make working with media queries a lot easier.
155
Many of us became very good at reading and interpreting CSS quickly,
CHAPTER
Above the fold are all the preprocessor variables and custom properties.
This includes all the different values a custom property can have, mak-
ing it easy to trace how it changes.
.row {
--row-display: block;
}
@media screen and (min-width: 800px) {
.row {
--row-display: flex;
}
}
156
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
.row {
display: var(--row-display);
flex-direction: row;
flex-wrap: nowrap;
}
.col-1, .col-2, .col-3,
.col-4, .col-5, .col-6 {
flex-grow: 0;
flex-shrink: 0;
flex-basis: var(--col-basis);
}
This example is fairly simple, but you could try expanding it to include
a flexible-width column that fills the remaining space. To do this, it’s
likely the flex-grow and flex-shrink values would need to be converted
to custom properties.
157
Strategies for Theming
CHAPTER
In this chapter I’ve mostly argued against using custom properties for
global dynamic variables and, I hope, implied that attaching custom
4
Theme changes can also be more localized, such as choosing the color
of a note in the Google Keep application.
158
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
159
:root {
CHAPTER
--THEME-COLOR: #d33a2c;
}
4
Capitalizing variables like this often signifies global constants. For us,
it signifies that the property is set elsewhere in the application, and we
should probably not change it locally.
:root {
--THEME-COLOR: var(--user-theme-color, #d33a2c);
}
In the next example, you might expect that the background will
be green. However, the value of --user-theme-color is set on the
body rather than the :root selector, and since we are inheriting
--THEME-COLOR from :root, its value has not changed.
160
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
:root {
--THEME-COLOR: var(--user-theme-color, #d33a2c);
}
body {
--user-theme-color: green;
background: var(--THEME-COLOR);
}
* {
--THEME-COLOR: var(--user-theme-color, #d33a2c);
}
body {
--user-theme-color: green;
background: var(--THEME-COLOR);
}
You can see some more detailed examples of this pattern in the section
“Manipulating Color with Custom Properties.”
161
UPDATING CUSTOM PROPERTIES WITH JAVASCRIPT
CHAPTER
This is not a new API: it’s the same JavaScript method for updating
styles on any element. These are inline styles, so they will have a
higher specificity than regular CSS. This means it’s easy to apply local
customizations:
.note {
--note-color: #eaeaea;
}
.note {
background-color: var(--note-color);
}
Here I set a default value for --note-color and scope this to the .note
component. I keep the variable declaration separate from the property
declaration, even in this simple example.
162
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
Not only can we set custom properties in style attributes, we can also
retrieve the value of custom properties using the var() function in any
HTML attribute.
<svg>
<path fill="currentColor" ... />
</svg>
This means they can have the same stroke or fill as the surrounding
text. If you use currentColor like this you can make the CSS color
property to change the fill of an SVG.
163
CHAPTER
4
The Smashing Cat image is an inline SVG that always has the same fill color as the
surrounding text color.
This technique is great for single color icons, but what if we want to
use more colors? That’s where custom properties can help.
<svg>
<path fill="var(--svg-color-1)" ... />
<path stroke="var(--svg-stroke)" fill="var(--svg-color-2)" ... />
</svg>
This means we can use the same inline SVG and set different color
variations with HTML and CSS. This is great for icons, logos, and other
UI elements that use SVG.
.smashing-logo-1 {
--svg-color: #000;
--svg-background: #888;
}
.smashing-logo-2 {
--svg-color: #bc3428;
--svg-background: #27aae1;
}
.smashing-logo-3 {
--svg-color: #264a9c;
--svg-background: #c95d5ac3;
}
164
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
The Smashing Cat image is an inline SVG with fill colors inherited from custom properties.
:root {
--hue: 25;
}
body {
background: hsl(var(--hue), 80%, 50%);
}
This is useful, but some of the most widely used features of prepro-
cessors are color functions that allow us to modify a base color using
functions like lighten, darken or desaturate. For example:
darken($base-color, 10%);
lighten($base-color, 10%);
desaturate($base-color, 20%);
165
It would be useful to have some of these features in browsers and luck-
CHAPTER
ily the color-mod() function is coming to CSS soon, but until we have
this, custom properties can fill some of that gap.
4
We’ve seen that custom properties can be used inside existing color
functions like rgb() and hsl() and that they can be used in calc().
This means we can convert a real number to a percentage by multiply-
ing it; for instance, calc(50 * 1%) = 50%.
:root {
--lightness: 50;
}
body {
background: hsl(25, 80%, calc(var(--lightness) * 1%));
}
:root {
--lightness: 50;
}
body {
--l: calc(var(--lightness) * 0.8);
background: hsl(25, 80%, calc(var(--l) * 1%));
}
We could even abstract away more of the calculations and create some-
thing like color modification functions in CSS using custom properties.
166
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
:root {
--HUE: 0;
--SATURATION: 80;
--LIGHTNESS: 50;
}
* {
--color: hsl(
calc(var(--HUE) + var(--hue-rotate, 0)),
calc(var(--SATURATION) * (1 - var(--desaturate, 0)) * 1%),
calc(var(--LIGHTNESS) * (1 - var(--darken, 0)) * 1%)
);
}
This allows us to create our own set of custom properties that modify
global values locally. Although the initial setup is a bit complex, it results
in highly semantic CSS that clearly describes the color modifications:
.complementary {
--hue-rotate: -180;
background: var(--color);
}
.dark-split {
--hue-rotate: -150;
--darken: 0.2;
background: var(--color);
}
.dark-split-2 {
--hue-rotate: 150;
--darken: 0.2;
background: var(--color);
}
167
This example is probably too complex for most practical cases of them-
CHAPTER
ing. You should only use something like this if you really need it. In his
article “Dark Theme in a Day,”2 Marcin Wichary says:
4
Be sure to read this article if you are considering using custom proper-
ties for theming or color modifications in any way.
<div class="notes">
<div class="note"> - Get Milk </div>
<div class="note"></div>
...
</div>
2 http://smashed.by/darktheme
168
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
The CSS has some Sass variables to control the background color and
the color of the notes in the application:
$background-color: #263238;
$note-color: #FFF9C4;
body {
background: $background-color;
}
.note {
border-top: solid 40px darken($note-color, 10%);
background: $note-color;
}
The layout is controlled using CSS Grid and media queries. The appli-
cation will have between one and four evenly spaced columns, depend-
ing on the viewport width.
169
.notes {
CHAPTER
display: grid;
grid-template-columns: 1fr;
grid-gap: 20px;
max-width: 1200px;
4
margin: 0 auto;
}
@media screen and (min-width: 600px) {
.notes {
grid-template-columns: 1fr 1fr;
}
}
@media screen and (min-width: 800px) {
.notes {
grid-template-columns: 1fr 1fr 1fr;
}
}
@media screen and (min-width: 1000px) {
.notes {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}
170
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
Let’s do this and move all the logic to the top of the document:
$note-color: #FFF9C4;
$background-color: #263238;
.notes {
--notes-grid-col-layout: 1fr;
}
@media screen and (min-width: 600px) {
.notes {
--notes-grid-col-layout: 1fr 1fr;
}
}
@media screen and (min-width: 800px) {
.notes {
--notes-grid-col-layout: 1fr 1fr 1fr;
}
}
@media screen and (min-width: 1000px) {
.notes {
--notes-grid-col-layout: 1fr 1fr 1fr 1fr;
}
}
.notes {
display: grid;
grid-template-columns: var(--notes-grid-col-layout);
}
At this point, our Sass variables have remained the same. They are
static, and there is no reason to convert them to custom properties.
However, if we want to extend this application to allow notes with
custom colors (local theming), we need to convert $note-color to a
custom property.
171
We keep the global Sass variable, as this will now be the default color.
CHAPTER
Then within the .note selector, we convert the static variables to two
custom properties: --note-color and --note-border-color.
4
.note {
--note-color: #{$note-color};
--note-border-color: #{darken($note-color, 10%)};
}
.note {
border-color: var(--note-border-color);
background: var(--note-color);
}
We can then change the value of these custom properties for individ-
ual notes. We can do this with CSS, inline styles, or JavaScript:
#note-3 {
--note-color: #FFCCBC;
--note-border-color: #ffa589;
}
note.style.setProperty('--note-color', '#DCEDC8');
note.style.setProperty('--note-border-color', '#c4e0a2');
172
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
:root {
--THEME-COLOR: var(--user-theme-color, #263238);
}
body {
background: var(--THEME-COLOR);
}
:root {
--user-theme-color: #ddd;
}
<html style="--user-theme-color: #ddd;">
const elm = document.documentElement;
elm.style.setProperty('--user-theme-color', '#ddd');
173
CHAPTER
4
The specific conditions and experience users with low vision have
varies, so it’s best to provide options. Custom properties make this far
less challenging.
For Windows users, there are built-in high-contrast options that can
be enabled via the user settings menu.
174
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
You can detect whether any high-contrast theme is active, or if the user
has selected a preference for a black-on-white or white-on-black theme.
Note that these settings also change the default user-agent style sheet,
so always test them in the Edge browser.
For other browsers, it’s necessary for users to set these preferences
within the application. When a user has selected these options you can
add a classname to the HTML element. For example:
<html class="high-contrast">
...
</html>
/* Default */
:root {
--text-color: #555;
--body-color: #ddd;
}
/* Windows\Edge detect high-contrast preference */
@media screen and (-ms-high-contrast:active) {
:root {
--text-color: #000;
--body-color: #fff;
}
}
/* User preference */
.high-contrast {
--text-color: #000;
175
--body-color: #fff;
CHAPTER
}
body {
background: var(--body-color);
color: var(--text-color);
4
REDUCED MOTION
Some people are particularly sensitive to certain types of movement
and animation on the web. It can make them feel dizzy or nauseous, or
simply give them a headache. People with certain types of vestibular
disorders (issues that affect balance) can have more immediate and
severe reactions.
Other people just don’t like animations. They may have performance
issues on older devices, can be battery-draining and resource intensive.
:root {
--ANIMATION-DURATION: var(--user-duration, 1);
}
Since the user preference has not been set, the value of
--ANIMATION-DURATION will be 1. We can then apply this value to our
transitions with calc():
176
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
.fade-out {
transition: opacity calc(var(--ANIMATION-DURATION) * 1s) ease-out;
}
The result of calc(1 * 1s) will be 1s, so the duration of the animation
remains unchanged.
.prefers-reduced-motion {
--user-duration: 0;
}
This selector must target the <html> element because the global
--ANIMATION-DURATION is looking for this value on the :root selector.
Now that we have set this value to 0, the calc() equation will be calc(0
* 1s), which results in 0s and therefore, the transition will be instant.
Any value between 0 and 1 could be used to reduce animation durations.
177
Strategies for Using Custom Properties
CHAPTER
Today
4
Even if you’re supporting IE10 and 11, you can start using custom prop-
erties today. Most of the examples in this chapter are about how we
write and structure CSS. The benefits are significant in terms of main-
tainability; yet most of the examples only reduce what could otherwise
be done with more complex code.
3 http://smashed.by/postcssvar
178
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
This results in two CSS files: a regular one with custom properties
(styles.css); and one for older browsers (styles.no-vars.css). I want IE10 and
11 to be served styles.no-vars.css, and other browsers to get the regular
CSS file.
4 https://postcss.org/
179
Normally, I’d advocate using feature queries, but IE11 doesn’t support
CHAPTER
<head>
<style> /* inlined critical CSS */ </style>
<script> loadCSS('non-critical.css'); </script>
</head>
5 http://smashed.by/supports
6 http://smashed.by/criticalcss
180
A Strategy Guide To CSS Custom Properties by Mike Riethmuller
If you follow these conventions, you will find working with custom prop-
erties is a whole lot easier. This might even change how you approach CSS
in general.
181
chapter 5
Lyza Gardner
Building an Advanced Service
CHAPTER
Worker
5
by Lyza D. Gardner
h, yes! Service workers! They can make sites and apps work
offline. They’re an integral part of progressive web apps
(PWAs). They’re big boons for network performance. You’ll
need service workers if you want to get at some of the nifty new web
APIs like Web Notifications or Web Background Synchronization.
Yep: they enable a wide array of great features and are beginning to
feel like a lynchpin of the modern web. Yet even folks who’ve cooked
up a few service workers of their own can be hard-pressed to explain
exactly what a service worker is.
The canonical use case for a service worker is as a proxy, intercepting net-
work requests for resources within its scope and deciding how to handle
them. That’s vague, so let’s break it down. A service worker sits between
your site or app and the network, and can decide what to do whenever a
resource (like an HTML page or an image) is requested by the browser.
For any given request, the service worker can opt to go to the network and
fetch fresh content from a server, or it might retrieve a resource from a
cache (where the service worker has previously stashed the resource).
184
Building an Advanced Service Worker by Lyza D. Gardner
A service worker acts as a proxy, sitting between the client – your site or app’s web page
JavaScript – and the network.
A service worker is only able to intercept requests that fall within its
scope: a range of URLs within the service worker’s control.
Fetch handling – responding to requests for resources that fall within its
scope – is a fundamental functionality of service workers. However, if
you read the Service Worker spec2 (I highly encourage this, by the way;
it’s fairly short and concise), you’ll see it doesn’t define how fetch works.
That’s because fetch, though critical even to the simplest service worker,
is its own separate standard, and is not part of Service Worker itself.
1 That, if you can believe it, is not the scientific term. JavaScript in a web page exe-
cutes in a browsing context (global object: window), while service workers have a
different context, specifically ServiceWorkerGlobalScope.
2 W3C Service Workers 1 Working Draft (http://smashed.by/serviceworkersw3)
185
Note: The Service Worker API makes heavy use of JavaScript Prom-
CHAPTER
Service workers are not too hard to work with. There are two hard-and-
fast rules, however:
3 http://smashed.by/mdnpromise
4 See Jake Archibald’s “Is service worker ready?” for the latest details of who supports
what: http://smashed.by/isserviceworkerready/.
186
Building an Advanced Service Worker by Lyza D. Gardner
UNDERSTANDING CONTEXT
To understand a bit more about how service workers tick and how
their lives work, let’s talk about their API parentage. Service workers
are a kind of web worker. Service workers inherit from web workers
the trait of running in the background, on a different thread from the
pages displayed in a browser’s tabs or windows.
5 http://smashed.by/browsingcontext
6 iframe elements also get their own browsing contexts.
187
As such, a web page and its controlling service worker are in separate
CHAPTER
bubbles. They can’t manipulate each other directly,7 and each context
has different stuff in its global scope.
5
Web workers, including service workers, execute on a different thread and in a different context
than the clients (web pages) they control.
From the moment it’s created until it’s done and retired, a service
worker’s capabilities change in predictable ways. That is, a service
worker has a life cycle, and its position in its overall life cycle at any
given moment is indicated its by its state attribute. state has one of
the following values:
7 They can, however, absolutely communicate – see the “Channel Messaging API”
section later in this chapter.
188
Building an Advanced Service Worker by Lyza D. Gardner
• parsed
• installing
• installed
• activating
• activated
• redundant
The installation phase is used to get the service worker ready, typically
by stuffing some things into a cache that it’ll need to access later (such
as core, application shell resources). The activation phase is for house-
cleaning, providing an opportunity to clean up after old service worker
versions and caches (like deleting crufty cache entries).
189
Just because a service worker registration is maintained in the browser
CHAPTER
It can be confusing: the word scope has two different meanings! There’s
scope as it pertains to execution context: all JavaScript executing in the
browser has a scope; in Service Worker’s case this is represented by
ServiceWorkerGlobalScope. But service workers are also associated
with a scope: a range of URLs they can control.
Events are what make service workers go. A service worker’s func-
tionality resides primarily in handling several key events: two life cycle
events (install and activate), as well the functional event fetch :
9 Browsers will likely shut down a service worker if it hasn’t done anything for about
thirty seconds.
190
Building an Advanced Service Worker by Lyza D. Gardner
<script>
navigator.serviceWorker.register('service-worker.js', './');
</script>
191
<script>
CHAPTER
</script>
In index.html, a service worker at service-worker.js is registered against the scope ‘./foo’. Once
activated and in control, that service worker can respond to fetches for resources in the shaded
area – within the foo directory – but could not respond to fetches outside of that range of URLs.
192
Building an Advanced Service Worker by Lyza D. Gardner
Note: Scope is relative to the service worker script’s location, not the
registering web page.
A service worker’s highest allowed scope is the location of its script file.
If this proves problematic, there’s an Service-Worker-Allowed HTTP
header you can take advantage of.12
The other piece of the puzzle is, of course, the service worker script
itself. Let’s start with something very simple inside of service-worker.js:13
There are several ways to respond to a fetch, but the most common
techniques are:
193
• using the fetch API to retrieve a resource from the network
CHAPTER
Not all browsers support arrow functions (() => {} notation), but every
browser that supports Service Worker does. So you can use that syntax
with impunity within service workers. The same goes for the const
keyword and other modern JavaScript features used in examples here.
Fetch API
The Fetch API lets you go out and get resources from the network
asynchronously. It’s not too different from XMLHttpRequest but it does
more things and is certainly easier to pronounce.
Fetch’s bread and butter are Request and Response objects. You feed
fetch() a Request object and it returns a Promise which – if every-
thing goes OK and the network is available – resolves to a Response
object. Requests in, Responses out:
194
Building an Advanced Service Worker by Lyza D. Gardner
Conceptually, Request objects are fed to fetch and Response objects result.
FETCHEVENT OBJECTS
We’ve already created an (empty) event handler for fetch events inside
of our service worker:
195
CHAPTER
5
If you crack open a FetchEvent object, you'll find, among other things, a relevant Request and a
vital method, respondWith.
This explicitly fetches the given Request from the network, using the
Fetch API, and returns the result (a Promise which we hope will resolve
to a Response).
196
Building an Advanced Service Worker by Lyza D. Gardner
Guess what? Still functionally useless. All we’ve done is what the
browser would do by default anyway: fetch things from the network.
Don’t worry: it’s not always pointless to use the Fetch API. We’ll
encounter some good reasons to use it within fetch handlers as we
continue on our journey.
A service worker can create, add items to, and remove items from an
arbitrary number of caches. A Cache object is, in a nutshell, a map: it
relates Request objects to Response objects.
197
CACHE OBJECTS AND THE CACHESTORAGE INTERFACE
CHAPTER
All of the available Cache objects are accessed through the CacheStorage interface. Cache objects
are keyed by the names you give them.
You create and manage your own caches – these Cache objects are
completely distinct from the browser’s built-in caching mechanism.
They are entirely under your control.
To add to or retrieve items from a Cache, you first need to get a hold of
it by asking the CacheStorage interface to open it:
198
Building an Advanced Service Worker by Lyza D. Gardner
The value of a Cache object’s key (name) is up to you: any string will do.
myCacheIHope.then(cache => {
// Do something with this cache!
});
199
The behavior of Cache.put is a little different from Cache.add and
CHAPTER
200
Building an Advanced Service Worker by Lyza D. Gardner
The way to do this is to take advantage of the install life cycle event,
stashing resources you know you’ll need later into cache. This is a
technique used in a common service worker recipe for pre-caching
application shell files (images, styles, scripts, and so on, used on all or
most pages of your site or app).
EXTENDABLEEVENT OBJECTS
The reason that this is potentially problematic is that the service worker
may keep marching along through its life cycle; installation may com-
plete before this handler has successfully added the items to cache.
201
Fortunately, ServiceWorker life cycle events – install and activate – are a
CHAPTER
Altering the code to read like the above will cause the install phase to hang
on, to wait until the Promise given to event.waitUntil resolves before
moving on.
Let’s talk about what this does. For every fetch that occurs within the service
worker’s scope, it will respond with a Response that looks like a web page.15
15 The first argument to the Response constructor is the body of the Response – here an
HTML paragraph. Setting a Content-Type header to text/html in the second, options
argument makes the Response look like a web page to browsers.
202
Building an Advanced Service Worker by Lyza D. Gardner
Inspect and debug tools for developing service workers are getting
more mature and fully featured. The service worker features in
Chrome’s Developer Tools16 are especially powerful. Some key ser-
vice-worker-related tips:
16 As I write this, I’m using Chrome 68. By the time you’re reading this, that will prob-
ably seem laughably obsolete.
203
CHAPTER
5
We’ve seen how we can use the Fetch API and Response objects to
respond to fetch events in useless or even ridiculous ways, but let’s
now look at a simple example that combines both in a way that could
potentially be beneficial: providing an offline message. Let’s say we
want to display a simple web page that lets users know when they are
offline. This is what we want to do:
We can detect that a fetch didn’t work out – the network is unavail-
able – because the Promise returned by fetch will reject instead of
resolving. We can provide a handler for that by chaining a catch
onto the Promise:
204
Building an Advanced Service Worker by Lyza D. Gardner
This is close to being sensible, with one wrinkle: it will respond to all
failed fetches with an HTML page. Let’s dial this back so it only does so
if the request is a navigation request; that is, a request for a full HTML
document:
205
PROVIDING AN OFFLINE PAGE
CHAPTER
It’s necessary to pre-cache the offline page during the service worker’s
installation so that it will be available later, when needed. The follow-
ing recipe pre-caches the offline page (install handler) and responds
to failed navigation fetches with it (fetch handler):
You’re not limited to offline (HTML) pages. You can also handle failed
requests for other resource types with a similar approach. Images are a
good example – the next recipe responds to failed image requests with
a custom offline image.
206
Building an Advanced Service Worker by Lyza D. Gardner
SVG is a good choice for a fallback image because it will scale to fit
wherever it’s needed. What’s more, an SVG is defined by an XML-
based markup language, meaning that its source can be included in the
service worker itself. The following defines a simple SVG that displays
the text “offline” – it can go at the top of the service worker file:
Unlike with the offline.html page, the SVG here won’t need to be pre-
cached in the install handler, as the meat of the Response – the body
– is all contained within that offlineSVG string, generating an image
that looks something like this:
207
Next, expand the fetch handler. Detect image requests and respond with
CHAPTER
the SVG if the fetch Promise rejects. But don’t do this for every request!
This is a good example of when it’s useful (and not just a parlor trick)
to create your own Response object.
There are many variations on this theme: you could return a different
kind of offline image for different types of image request, and you could
create more fallbacks for more resource types – videos, for example.
208
Building an Advanced Service Worker by Lyza D. Gardner
const appShellURLs = [
'thing1.jpg',
'thing2.jpg',
'thing3.jpg'
];
self.addEventListener('install', event => {
event.waitUntil(caches.open('static-assets')
.then(cache => cache.addAll(appShellURLs))
);
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (appShellURLs.indexOf(url.pathname) !== -1) {
// url.pathname is a string relative to the site's root, e.g.
'/foo/img/bar.jpg'
event.respondWith(caches.match(event.request));
}
});
209
The above fetch handler behaves just fine if everything works as
CHAPTER
out caches (it’s not typical, but it could happen), or the browser might
decide to ditch stuff. There’s no contract that guarantees that things
you put in the cache will remain there.
210
Building an Advanced Service Worker by Lyza D. Gardner
Always use URLs relative to web root and you’ll protect yourself:
211
DEFINING NETWORK STRATEGIES
CHAPTER
With network strategies, you can tune which types of requests should
get the freshest content possible and which should try to get responses
from cache. That helps improve online performance by avoiding
unnecessary network round trips for static assets.
For the purpose of this recipe example, let’s say we’re working with a
site that has significant text content (in HTML pages) that changes
often, but has images that rarely change. In this case, a reasonable
approach can be sketched out as:
212
Building an Advanced Service Worker by Lyza D. Gardner
The priorities used for navigation requests – getting the freshest con-
tent possible – constitute a network-first strategy, while the cache-pre-
ferring priorities for images represent a cache-first strategy.
213
return response;
CHAPTER
});
}
function fallbackImage () {
return new Response(offlineSVG,
5
214
Building an Advanced Service Worker by Lyza D. Gardner
Once a service worker is installed and active, the browser will, from
time to time, check to see if the service worker file from the server is
identical to the one it has already installed and activated. If there are
any differences between the two files, the browser will download the
new, updated service worker and install it.
215
Put another way, making changes to the service worker script file
CHAPTER
will prompt the browser to download and install the new version.
The (new) service worker will go through its life cycle phases –
install and activate event handlers – with any changes intro-
5
// Really, this string could have any value; the important thing is that
// you change it every time you update your service worker script.
// Personally, I use the names of lesser-known Greek deities on my own site
// (recent versions include Corus, the spirit of surfeit and disdain; and
// Stheno, an immortal gorgon).
const SWVERSION='39b4893a9f';
Doing this may initially seem extraneous or pointless, given that any
changes to the service worker file will cause the browser to download
and install the new version, but there are a few reasons that having a
version string is useful.
For one thing, there are some situations in which you might want
browsers to download and install a service worker afresh – kicking
off a new install-activate cycle – without making any changes to the
service worker script itself. Bumping the version string will make
browsers see the service worker as changed. We’ll run into this with
the “Listing Application Shell Assets in JSON” recipe.
Version strings also give you a handy, version-specific string for nam-
ing caches. By prefixing all cache names (keys) with the string, you can
216
Building an Advanced Service Worker by Lyza D. Gardner
The activation life cycle phase is intended for cleanup of stale service
worker assets. It is especially handy for cleaning out crufty caches. Its
associated event is activate.
First, make sure that whenever you create or reference a cache in your
service worker, you involve the version string:
By doing this, you know that any cache name that does not begin with
the current version string is old and crufty. It’s good practice to clean
up – delete – those old caches within an activate event handler. In the
handler function:
217
All of these asynchronous steps should be completed during the
CHAPTER
The activation phase comes after the installation phase, but the timing
of when those phases occur depends on a few things.
If the service worker is new new – that is, there is no already active
service worker for the current scope – it will move immediately from
installation into activation (and any registered activate handlers will
be invoked).
218
Building an Advanced Service Worker by Lyza D. Gardner
219
LISTING APPLICATION SHELL ASSETS IN A JSON FILE
CHAPTER
lined to maintain that list in an external JSON file. A JSON file could be
integrated with automated build tools, updating itself when your site
is built or deployed, for example, or even being generated dynamically
on the server when it is requested. A build script could even update
the version string in the service worker file automatically. Even if you
don’t do any sophisticated integration like that, a JSON file might sim-
ply feel cleaner, like a better separation of concerns.
[
"/aFile.html",
"/anotherFile.html",
"/images/someImage.png",
"/css/essentialCss.css"
]
220
Building an Advanced Service Worker by Lyza D. Gardner
Any time the contents of the JSON file change, you’ll want browsers
to update what’s in the app-shell cache to the latest list of files – you’ll
need browsers to redownload and reinstall the service worker so its life
cycle begins anew and the install handler is invoked again.
function sendMessage () {
navigator.serviceWorker.controller.postMessage('Identify yourself!');
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(function () {
if (navigator.serviceWorker.controller) {
postMessage();
} else {
navigator.serviceWorker.addEventListener('controllerchange',
postMessage);
}
});
}
221
navigator.serviceWorker.controller returns a reference to the ser-
CHAPTER
The controllerchange event will fire when a new service worker takes
5
A view of Developer Tools → Console in Chrome shows the service worker logging from the
message event handler.
In the service worker script, you can listen for message events:
17 It is technically possible in some cases for the service worker to initiate commu-
nication, but it’s dependent on being able to derive the ID of the client in question,
which isn’t always available. It’s easier (and more appropriate) to use the Channel
Messaging API for exchanging two-way messages.
222
Building an Advanced Service Worker by Lyza D. Gardner
function postMessage () {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = event => console.log(event.data);
navigator.serviceWorker.controller.postMessage(
{cmd: 'identify'},
[messageChannel.port2]);
}
223
CHAPTER
5
18 For brevity, this code assumes the existence of event.data.cmd, but you would want
to be more careful, of course, in production code.
224
Building an Advanced Service Worker by Lyza D. Gardner
Chrome Developer Tools console showing the client's code logging of the message returned by the
service worker. Of course, you could do more useful things with the event's data than logging it!
• Web Push: an API that allows a web worker to receive messages from a
server, with an associated event, push, that the worker can handle.
225
You can have Notifications without Push, but it’s rare (albeit possible) to
CHAPTER
Generating a legit key pair is beyond the scope of this example, but you
can cook up a fake local public key by cobbling together a Uint8Array
that has the right kind of structure – this is ferociously hacky, but it
will allow you to try out push notifications locally in the browser:20
19 More details about applicationServerKey can be found in the Push API spec:
http://smashed.by/pushsub
20 If this grosses you out too much, you could instead use a public key generated by
Google’s Push Companion (https://web-push-codelab.glitch.me/), though you’ll still
need to convert it to a UInt8Array. You can find a utility function to do so in the
Google Web Push Code Lab repository: http://smashed.by/mainjs
226
Building an Advanced Service Worker by Lyza D. Gardner
// ALERT! ONLY USE THIS TO TRY OUT PUSH API LOCALLY IN YOUR BROWSER!
ALERT!
function fakestKeyEver () {
const fakeOutputArray = new Uint8Array(65);
fakeOutputArray[0] = 0x04; // First octet must be 0x04
return fakeOutputArray;
}
const appKey = fakestKeyEver(); // The kind of UInt8Array required by Push
navigator.serviceWorker.register resolves to a
ServiceWorkerRegistration when successful. That registration gets
passed to subscribeUser, where it provides access to PushManager.
Before invoking PushManager.subscribe, the code validates that a
subscription doesn’t already exist (it’s null) and that the user has not
denied permission previously.
227
CHAPTER
5
If you load a web page containing the code above, you'll see that the PushManager.subscribe
invocation causes a (notification) permission dialog to appear.
Even though the Push API is responsible for the subscription here,
the user permission is associated with the Notification API. The
userVisibleOnly options property indicates that the intended purpose
of this push subscription is to show notifications to the user for push
events. Chrome requires this property to be present and only accepts
the value of true. Thus, the subscribe here will cause a permission
prompt for the user.
228
Building an Advanced Service Worker by Lyza D. Gardner
The service worker can also handle the notificationclick event, which
is triggered when a user clicks on the notification UI itself:
21 This very detailed post from JR Conlin at Mozilla digs deeper into
generating key pairs and working with server-side pieces of Push:
http://smashed.by/mdnpushnotes
229
CHAPTER
5
In this view of the Chrome Developer Tools Application tab, the Service Workers section is
active (left side channel), displaying information about service workers at this origin. Note the
“Push” link at the far right.
You can try out this local Push example in Chrome: clicking on the
Push link for this service worker in the Developer Tools → Application
→ Service Workers section will trigger a push event and show the
notification.
A notification
shown by the service
worker’s push event
handler function.
Background Sync
The Background Sync API (supported in Chrome since spring 2016)
allows you to put off doing things until a user’s connectivity is assured
or restored. Background Sync is not on the standards track at this time
and is only supported by Chrome.
230
Building an Advanced Service Worker by Lyza D. Gardner
Note: As with Push, you can emulate sync events from within
Developer Tools → Application → Service Worker. Try it out!
You can register a sync with the same tag multiple times; only one
sync event for that tag will get fired. If you want multiple events, use
multiple different tags.
231
The Future
CHAPTER
Advances in what you can do with streams on the web could make
custom-built Responses more performance-friendly. Currently, if you
respond to a fetch event with a Response from the network (via fetch)
or from cache, those responses will stream without any effort on the
developer’s part. That’s a good thing: the whole response body doesn’t
need to be present before the browser can start doing things with
it. However, Response objects you construct yourself require some
intervention – currently rather convoluted – to make them stream.
API improvements here could make it a lot easier to cobble together
streaming Responses.
232
Building an Advanced Service Worker by Lyza D. Gardner
The future for the service worker landscape seems to coalesce around
two main themes: performance and stability improvements to Service
Worker itself, as well as the rise of more APIs and features that require
a service worker to access – like push notifications. As Service Worker
gets more mature, your code can be simpler and more concise, and you
can rely on it in a larger share of browsers. And the new APIs add more
compelling, app-like functionality to the web platform. The combined
advantages of performance and features make service workers an
increasingly vital piece of the web puzzle.
233
chapter 6
Yoav Weiss
Loading Assets on the Web
CHAPTER
by Yoav Weiss
6
This is a question I’ve spent a lot of time on in the last few years, think-
ing about what ideal performance would take, on the browser side as
well as on the server/content side.
• Full CPU use: The CPU and the browser’s main thread should
be fully used, as long as processing is required to transform the
delivered content into pixels on the screen, application behavior,
and application logic.
236
Loading Assets on the Web by Yoav Weiss
At the same time, due to limitations in size, this chapter won’t be able
to cover all the conditions above. Specifically, we’ll focus on the ins and
outs of resource loading, and leave aside CPU use, main thread block-
ing avoidance, and performance measurement.
237
How Browsers Load Resources
CHAPTER
A typical web page is built from multiple resource types and often
many resources that make sure the user’s visual experience is a pleas-
6
ant one.
The HTML resource gives the browser the structure of the document;
CSS styles the document; and JavaScript provides functionality and
interactivity. Beyond those, fonts make sure the reading experience is
optimal, and image, video, and audio resources provide visuals, as well
as the audio often required to convey the full context of the page.
But all these resources are not declared in a single place when the
browser initially fetches the HTML, when the user clicks on a link or
types something into their address bar. All the browser has to go on at
that point is the HTML itself, and only once fetched does it know of
some of the other resources required for the full site experience.
238
Loading Assets on the Web by Yoav Weiss
HTML PROCESSING
One of the great things about HTML as a format is that it’s progressive.
It can be processed as it comes off the network, so even if the HTML
the browser has is incomplete (as is often the case), the parts that have
already arrived can be processed and acted on.
Now the browser needs to process that HTML to start building the
DOM tree from it, and most importantly for our resource-loading
interests, to figure out which resources are needed for it to fully con-
struct and render the page. The HTML processing phase starts by the
tokenization phase: the browser breaks apart the HTML string into
tokens, where each token represents a tag start, tag end, or the text
between the tags.
Invalid HTML gets tokenized: the </img> tag creates an “EndTag” token, despite it being invalid.
239
After the tokenization phase, the browser can start using those tokens
CHAPTER
to kick off resource loads. Since blocking scripts can cause the DOM
creation, it’s better not to wait to load resources until the DOM is ready.
Instead, the browser uses its preloader1 to scan through the tokens and
6
figure out which resources are highly likely to be requested later on,
and kick off those requests ahead of time.
After the preloader runs its course, the browser uses those same
tokens and parses them according to HTML’s parsing rules. The
result is DOM nodes, interconnected as a DOM tree. Any tokens
which represent invalid markup will be processed and used to create
a perfectly valid DOM.
We’ve seen how the browser discovers resources, but not all resources
are created equal. In particular, there’s a set of resources the browser
requires to initially render the web page’s content.
We talked about the creation of the DOM tree, and it is required for the
browser to be able to render the page to screen, but unfortunately it is
not sufficient. To render the page with its appropriate styling to the user
(and avoiding a "flash of unstyled content"), the browser also needs to
create the render tree and, in most cases, the CSS Object Model.
The CSS Object Model (CSSOM) is a set of APIs that enable scripts to
examine the styles of various DOM nodes, as well as setting them to
change their appearance.
1 http://smashed.by/bigbadpreloader
240
Loading Assets on the Web by Yoav Weiss
To create both the CSSOM and the render tree, the browser needs to
download all the external CSS resources and evaluate them. Then it
processes those downloaded rules, along with any inline style tags and
style attributes to calculate which styles (if any) are applied to each
node in the DOM tree.
This is where blocking scripts come in. Blocking scripts (as their name
suggests) block HTML parsing as soon as they are encountered, and
HTML parsing does not continue until they finish being downloaded,
parsed, and executed. Furthermore, in many cases at least, they cannot
start executing until all CSS that preceded them has been downloaded
and processed.
The reason is that those scripts can access the bits of the DOM that are
already generated and query their CSSOM. If they do that, the CSSOM
state must be stable and complete. Hence browsers must make sure
this is the case, and they do that by downloading and processing all
CSS before any CSSOM reads. The result can be a lot of delay to Java-
Script execution, which in turn delays DOM creation. Sadness.
Now, once the browser has built a sufficiently large DOM tree and cal-
culated the styles for each DOM node, it can walk over the DOM and
create the render tree, by creating an equivalent tree for each node that
actually partakes in the page’s rendering and is displayed on screen.
After that, the browser can proceed to laying out the different elements
on the screen and eventually paint them.
241
CHAPTER
6
Request Destination
The request’s destination2 indicates what type of resource we’re expect-
ing to receive from the server and how that resource will be used. Dif-
ferent destinations include “style,” “script,” “image,” “font,” and more.
A request’s destination has several implications for resource loading.
As we’ll discuss later, it determines the priority of the request. It is
also used in various internal browser caches to make sure a previous
response can be served to a current request only when their destina-
tions match.
2 http://smashed.by/requestdest
242
Loading Assets on the Web by Yoav Weiss
Credentials Mode
A request’s credentials mode3 determines whether the request gets
credentials information (cookies, in the typical case) sent along with it
to the server. Possible values can be:
3 http://smashed.by/credmode
4 http://smashed.by/crossorigin
243
Note that there’s no current value which results in an “omit” creden-
CHAPTER
First of all, you may expect cookies on the server for a certain request,
6
ALL SET?
Early Delivery
When it comes to web performance, our goal is usually seemingly
obvious: we want meaningful content to be accessible as quickly as
possible. As part of that, we need resources on the critical path to be
delivered early.
How early is early enough? Probably shortly after the browser sent out
a request for it, typically after the user clicked on a link or typed some-
thing in their address bar. (Or even before that, if the application can
have high enough confidence that they would; for example, when the
user moves their mouse pointer within a certain proximity of a button.)
244
Loading Assets on the Web by Yoav Weiss
Protocol Overhead
Network protocols introduce overhead to network communication.
That overhead is manifested in extra bytes – while most of our net-
work traffic is content, some part of it is just protocol information
enabling the delivery of that content. But the overhead is also man-
ifested in time: establishing a connection or passing along critical
information from client to server or vice versa can take multiple
round-trip times (RTTs), resulting in those parts of the delivery being
highly influenced by the network’s latency.
The consequence is a delay until the point where the browser starts
receiving the response from the server (also known as time to first
byte, TTFB). Recent advances in the underlying protocols make that
less of an issue, and can reduce TTFB significantly.
0-RTT
TCP SYN
TCP SYN+ACK
ClientKeyExchange
Server Finished
245
Protocol advancements such as Quick UDP Internet Connection
CHAPTER
(QUIC) on the one hand, and TCP Fast Open (TFO) and TLS 1.3 on the
other, made most of that obsolete. These protocols rely on previously
established connections to keep cryptographic “cookies” that remem-
6
246
Loading Assets on the Web by Yoav Weiss
This new protocol has several shiny new improvements over the
protocols of yore:
247
• Tighter prioritization: Because QUIC is implemented over
CHAPTER
UDP, the transport layer logic and queues are all in the server’s
full control, where with TCP that logic is implemented as part
of the operating system. As a result, it’s easier for the server
6
PRECONNECT
While 0-RTT protocols enable us to forgo the four typical RTTs it takes
the browser to establish a connection, preconnect enables us to get
them out of the browser’s critical path. Preconnect is a markup hint
which indicates to the browser that resources would be downloaded
from a certain origin, so the browser would do well to resolve that
host’s address, and create TCP and TLS connections to it ahead of time,
before the resource is actually needed.
248
Loading Assets on the Web by Yoav Weiss
When the server starts sending data to the user, it doesn’t know how
much bandwidth it will have for that purpose. It can’t be sure what
amount of data will overwhelm the network and cause congestion.
Since the implications of congestion can be severe, a mechanism called
slow start was developed to make sure it does not happen. It enables
the server to gradually discover the connection’s limits, by starting to
send a small amount of packets, and increasing that exponentially. But
owing to its nature, slow start also limits the amount of data that we
can initially send on a new connection.
Congestion Collapse
The reason slow start is needed is to avoid congestion collapse.
So now the server is trying to send all the retransmits for the dropped
packets, but they are still more than the network can handle, so they
get dropped.
249
What does the helpful transport layer do? You guessed it, retransmit
CHAPTER
again.
That process happens over and over, and we end up with a network
6
In the past, the recommended amount of packets for the initial conges-
tion window – the initial amount of packets the server can send on a
fresh connection – was somewhere between two and four, depending
on their size.5
If you’re familiar with the rule stating you should send all critical
content in the first 14 KB of HTML, that’s the rule’s origin. Since the
maximum ethernet packet size is 1,460 bytes of payload, 10 packets (or
approximately 14 KB) can be delivered during that first initial con-
gestion window. Those packets need to include both the headers and
the critical content payload if you want your site to reach first paint
without requiring extra round-trips.
5 http://smashed.by/tcpfourpackets
6 http://smashed.by/tcptenpackets
250
Loading Assets on the Web by Yoav Weiss
The browser itself can also, in many cases, take an educated guess
regarding the quality of the network it’s on. It can base that on the net-
work radio type and signal strength, as well as past browsing sessions
on that network.
The advent of QUIC as the transport protocol will also make this easier to
implement on the server, as there’s no easy way to increase the conges-
tion window for an in-flight connection in TCP implementations today.
WHAT TO DO?
How can you make sure that you’re minimizing your protocol over-
head? That depends on your server’s network stack. Moving to QUIC,
TLS 1.3 or TFO may require upgrading your server’s network stack, or
turning on those capabilities in its configuration. Alternatively, you
can use a CDN that supports those protocols, and have servers which
sit between your users and your servers.
7 http://smashed.by/netinfoapi
8 http://smashed.by/connectype
251
updates as the Chrome implementation evolves. At the same time,
CHAPTER
When using preconnect, you also need to pay close attention to the
credentials mode of the resources will use that connection. Since
Chrome uses different connections for different credential mode
requests, you need to let the browser know which connection type it
needs to preconnect to. You can do that with the crossorigin attribute
on the preconnect <link> element.
252
Loading Assets on the Web by Yoav Weiss
Server-Side Processing
The other hurdle to overcome when it comes to early delivery is
server-side processing. HTML content is often dynamic and, as such,
generated on the server rather than simply delivered by it. The logic for
its generation can be complex, might involve database access, and can
also include different APIs from different services. That processing is
also often done using interpreted languages such as Python and Ruby,
which are not always the fastest choice, at least not by default.
As a result, generating the HTML once a request has hit our servers
is a time-consuming activity, and one which wastes our users’ time.
Worse than that, it is time spent before the browser can do anything to
advance the loading of the page.
EARLY FLUSH
One way to avoid forcing our users to wait for our potentially slow
database responses is to flush the HTML’s <head> early on. As part of
the HTML generation process we can make sure that once the <head>
is ready, it will be sent to the browser, enabling it to start processing it,
issue the requests defined in it, and create the relevant DOM tree.
253
Committing an HTTP Response Code
CHAPTER
end up having a different status code, perhaps a 404 or a 500 error if the
server failed to find the required information in the database. In such
a case, you would have to redirect or modify the content using Java-
Script, to show the error. But because that content was served with a
200 response code, search engines may not recognize it as error content,
and that error content may find itself in search results. It also means
we need to bake that eventual error logic into our client-side application
logic, in order to make sure the negative user impact of that redirection is
as unnoticeable as it can be.
Alternatively, you can try to build in application logic that converts these
header-based browser instructions into content-based ones (like setting
the cookies using JavaScript, or setting some headers using meta tags as
part of the content itself).
254
Loading Assets on the Web by Yoav Weiss
Compression Buffering
Even if your application logic enables early flushing of your HTML’s
<head>, misguided gzip or Brotli settings can result in undesired
buffering. When done wrong, they can nullify your attempts to send
content early to the browser. Gzip and Brotli have various settings to
control the trade-off between buffering content and compression ratio:
the more buffering, the better compression ratio these algorithms
can achieve, but at the cost of extra delays. When flushing early, you
should make sure that your server (and whatever other components
applying compression in your infrastructure) is set accordingly.
SERVER PUSH
That often means by the time the HTML is generated and starting to
be sent to the browser, the browser already has all the critical resources
in its cache. Furthermore, sending those critical resources starts the
9 http://smashed.by/gzip
10 http://smashed.by/brotliflush
255
TCP slow start process earlier and ramps up TCP’s congestion window.
CHAPTER
HTML request
Loading process without Push: HTML download and slow start kick off only after HTML is
generated.
HTML request
14KB pushed CSS+JS
Acks
28KB pushed CSS+JS
Acks
56KB pushed CSS+JS
Acks
Got HTML!!
HTML
Loading process with Push: critical resources and slow start kick off before HTML is generated, so
once it is ready it can be sent down in a single RTT.
One of the biggest problems with Server Push today is that the server
cannot see the browser’s cache state, so when done naively the server
is likely to send down resources that the browser already has. A recent
256
Loading Assets on the Web by Yoav Weiss
proposal called Cache Digests11 aims to solve that, by sending the server
a condensed list of all the resources the browser has in its cache for
that particular host, enabling the server to take that into consideration
before pushing down resources.
In practice, because of the caching issues, you don’t want to use Server
Push naively. One easy way to implement Server Push is to make sure
it’s only in effect on your landing pages, and only working for first
views. You can use a cookie to distinguish first views from repeat
visits, and use the request’s Accept headers to distinguish navigation
requests from subresource ones.
Actually triggering Server Push varies per server: some servers use
Link preload headers as a signal to push resources. The problem with
that approach is it doesn’t take advantage of the server’s think time.
Alternatively, configuring the server to issue pushes based on the arriv-
ing requests can prove more beneficial, as it triggers the push earlier.
EARLY HINTS
11 http://smashed.by/cachedigest
12 http://smashed.by/swcachedigest
13 http://smashed.by/earlyhints
257
indications regarding the resources a browser should load, while still
CHAPTER
not locking down the final response code. That would enable servers
to send down Link preconnect and preload headers in order to tell
the browser as early as possible which hosts to connect to and which
6
The main downside here is that Early Hints is not yet implemented by
any browser, and its implementation may not be trivial. Because the
hints are received before the final HTTP response, the browser doesn’t
necessarily have in place the rendering engine process that will end up
processing that document. So supporting Early Hints would require
supporting a new kind of request that is not necessarily triggered by
the rendering engine, and potentially a new kind of cache that will
keep these resources around until the rendering engine is set up and
has received the final headers and document.
PREFETCH
Finally, there’s also the option of working around the server’s think
time by kicking it off significantly earlier, before the user has even
expressed explicit interest in the page. Browsers use past user activity
to predict where the user is likely to go next and prefetch those pages
while they are typing in the address, or even before that.
Those fetches remain alive when the user actually navigates to the
next page, and remain in the cache for a limited amount of time even if
the resource is not cacheable.
258
Loading Assets on the Web by Yoav Weiss
If you make a reasonable guess of where your users will head next,
prefetch can be a good way to make sure that when they do, the
browser will be one step ahead of them.
Priorities
Since the download process of a web page comprises downloading doz-
ens of resources (and sometimes more), properly managing the down-
load priority of these resources is extremely important. We discussed
the resource loading process earlier. Part of that process is determining
each resource’s priority.
REQUEST PRIORITIES
Once the browser detects a resource that it needs to load, it kicks off a
request that will fetch that resource. We saw earlier that some requests
may be more important than others. Resources that are in the critical
path should ideally reach the browser before those that are not, and
that are required at a later phase of the rendering process.
How do browsers make sure these critical requests get higher priority
over less critical ones?
259
Different browsers use different schemes: some hold off all non-criti-
CHAPTER
cal requests while critical ones are in progress, while others let through
some non-critical requests to go out in parallel to critical ones, to
improve image performance.
6
260
Loading Assets on the Web by Yoav Weiss
PRIORITY HINTS
14 http://smashed.by/priohints
261
STREAM INTERNAL PRIORITIES
CHAPTER
If we consider HTML, its first few bytes, which contain the <head>,
enable the document to be committed, and the critical resources to be
requested are of the highest priority. At the same time, especially for
long HTML files that go beyond the initial viewport, the last few bytes
required to finish constructing the DOM, but not required for the ini-
tial rendering, are of lower priority.
Similarly, the first bytes of images, which contain the image’s dimen-
sions, are extremely important as they enable browsers to reserve
space for the image and allow layout to stabilize. For progressive
images, the byte range containing the first scan is significantly more
important than the last bytes of the last scan. The former enable the
browser to display the rough image (in low quality), while the latter
provides small quality improvements.
262
Loading Assets on the Web by Yoav Weiss
Loading only the critical parts of your CSS up front, while lazy-loading
the rest can be one of the highest impact optimizations you can apply to
speed up your first paint and first meaningful paint-rendering metrics.
263
Your CSS can probably be divided into three parts:
CHAPTER
When the browser’s preloader sees such tags, or when the equivalent
elements get added to the browser’s DOM tree, this CSS is downloaded
at a high priority as it is considered critical.
Obviously, it’s best to avoid sending down CSS you simply don’t need.
264
Loading Assets on the Web by Yoav Weiss
Another option is to inline critical CSS into your HTML so it will get
delivered as part of the same resource, saving an RTT. That’s a better
option, especially if your critical CSS is fairly small. At the same time,
your caching will suffer. Delivering the CSS inline means you’ll be
sending it for every repeat visit to every page on your site. If the CSS
is small enough, it could be worth your while, but it’s a trade-off you
should be aware of.
265
initial render happens, the first requestAnimationFrame event15 of the
CHAPTER
Loading CSS at that point will not block the page’s initial rendering, so
6
Preload
An alternative technique to decouple CSS execution from loading is to
use <link rel="preload"> to trigger the CSS loading, and only process
it and take its rules into account once it’s loaded. To do that, we can
take advantage of HTMLLinkElement’s load event.
You can also combine the two methods above to trigger a download of
the preload resource, only after the document has been painted. This
avoids having it contend for bandwidth with more critical resources.
15 http://smashed.by/requestanimationframe
266
Loading Assets on the Web by Yoav Weiss
The downside of these two methods is that the resource will be down-
loaded with high priority, so it runs a real risk of competing with other
resources on the page.
That would let us load our CSS whenever we actually need it,16 and
browsers would just do the right thing.
Up until recently, that was (more or less) the way Firefox, Safari, and
Edge loaded CSS, but not Chrome. Chrome and other Chromium-based
browsers blocked the rendering of the entire page as soon as an exter-
16 http://smashed.by/progcss
267
nal CSS resource was discovered, even if it was in the <body>. That
CHAPTER
But starting from Chrome 69, Chrome aligned its behavior with other
browsers. Developers can now include non-critical CSS, using <link
rel="stylesheet"> tags inside their content, without it having a
negative impact.
One caveat is that Firefox doesn’t always block rendering for in-<body>
style sheets, which can result in a flash of unstyled content (or FOUC).
A way to work around that is to include a non-empty <script> tag after
the <link> tag, which forces Firefox to block rendering at that point
until all styles have finished downloading.
<html>
<head>
<style>/* Critical styles */</style>
</head>
<body>
<!-- Critical content -->
<link rel="stylesheet" href="foo.css">
<script> </script><!-- Note the space inside the script -->
<!-- Content which is styled by foo.css -->
</body>
</html>
268
Loading Assets on the Web by Yoav Weiss
In the end, I believe the easiest and cleanest technique to load non-crit-
ical styles is to include them as <link> tags in the <body>, and have
them block only the content that relies on them. If you have styles that
are needed only for follow-up pages, you could use the inapplicable
media technique, but arguably <link rel="prefetch"> may be a better
tool for the job.
Like CSS, blocking JavaScript also holds off rendering. Unlike CSS,
JavaScript processing17 is significantly more expensive than CSS and
its render-blocking execution can be arbitrarily long.
17 http://smashed.by/costofjs
269
What’s the middle ground? How can we enable performant JavaScript
CHAPTER
JAVASCRIPT-ENHANCED EXPERIENCE,
AKA PROGRESSIVE ENHANCEMENT
But if you’re delivering content that the user then interacts with,
it’s likely better for you to deliver that content as HTML, and then
enhance it with JavaScript.
270
Loading Assets on the Web by Yoav Weiss
While that sounds great in theory, in practice it can result in race con-
ditions and have performance implications:
• async scripts run whenever they arrive. That means they can run
out of order so must not have any dependencies on any other
script in the page.
• Because they run whenever they arrive, they can run either before
or after the page is first painted, depending on network condi-
tions and network optimizations. This creates a paradox: optimiz-
ing your async scripts (better compress them or make sure they
arrive earlier using Server Push or preload) can often result in
performance regressions! Arriving sooner to the browser means
their parsing and execution (which on mobile can be hefty) is now
render-blocking,18 even though their download was not.
For that reason, async is not a great download mechanism and should
be avoided most of the time.
18 http://smashed.by/deferoverasync
271
One more thing: to even consider making certain scripts asynchro-
CHAPTER
are in a specific state when they run. (Avoid, for example, append-
ing nodes to the bottom of the <body> element, or relying on certain
styles to be applied)
defer
As far as native script mechanisms go, that leaves us with the defer
attribute. defer has the following characteristics:
• Deferred scripts execute in the order they are included in the docu-
ment, which makes it easier to have dependencies between them.
272
Loading Assets on the Web by Yoav Weiss
sarily the case, and deferred and asynchronous scripts have the same
priority as blocking scripts.
Using <script defer> only became a realistic option in the last few
years. IE9 and earlier had a fatal issue with it, causing content that used
defer to be broken under certain race conditions19 if the scripts had
interdependencies. Depending on the download order, deferred scripts
that added HTML to the DOM (using innerHTML, for example) could
have triggered the parser to start executing later scripts before the first
script had finished running. That was a huge blocker for the adoption of
defer scripts for many years.
But since the usage of IE9 and older is very low nowadays, unless your
audience is very much old IE-centric, it is probably safe for you to use
defer and ignore potential issues.
19 http://smashed.by/deferbroken
273
That said, it’s not necessarily the best option and has some downsides:
CHAPTER
• While not as bad as blocking the HTML parser and DOM cre-
ation at the top of the document, blocking scripts at the bottom
6
20 http://smashed.by/asynchfalse
274
Loading Assets on the Web by Yoav Weiss
275
• Unlike async, with preload you can make sure the script
CHAPTER
doesn’t run until some milestone is hit (for example, first paint
happened)
6
A simple version of this, mixing both preload and rAF, might look
something like this:
<script>
var firstPaintHappened = false;
var scriptPreloaded = false;
var addScriptElement = () => {
var script = document.createElement("script");
script.src = "non-blocking.js";
document.body.appendChild(script);
};
var preloadOnloadHandler = () => {
scriptPreloaded = true;
if (firstPaintHappened) {
addScriptElement();
}
};
if (window.requestAnimationFrame) {
window.requestAnimationFrame(() => {
firstPaintHappened = true;
if (scriptPreloaded) {
window.setTimeout(addScriptElement, 0);
}
}
} else {
window.addEventListener("DOMContentLoaded", addScriptElement);
}
</script>
<link rel="preload" as="script" href="non-critical.js"
onload="preloadOnloadHandler">
276
Loading Assets on the Web by Yoav Weiss
This is a problem that’s being worked on21 in Chrome, where the prob-
lem is being defined and various solutions have been outlined. Once
that work is done, the solution will hopefully be standardized and
implemented in all other modern browsers.
JAVASCRIPT-RELIANT EXPERIENCE
Many websites today rely on JavaScript for their basic rendering, add-
ing those scripts to the website’s critical rendering path.
21 http://smashed.by/scriptschedule
277
If you’re starting out building a content site, I’d suggest avoiding build-
CHAPTER
However, that doesn’t necessarily mean that you can’t develop your
6
site using your favorite language and tools. Many frameworks today
enable server-side rendering, where the first page the user sees is ren-
dered with good old-fashioned HTML and CSS, and JavaScript kicks in
later, enabling the single-page app experience from that point on.
The best way to tell would be to test existing sites that use the frame-
works you’re considering for your project, and look at their interac-
tivity metrics on mobile. If the page takes a large number of seconds
to render on mobile, or if it renders quickly but then freezes for what
seems like forever to the user, that’s a big red flag warning you against
that framework.
You can run such tests, on real devices, at webpagetest.org. You could
also run a Lighthouse audit of those sites,22 identifying the site’s Time
to Interactive (TTI) metrics, among others.
22 http://smashed.by/wptlighthouse
278
Loading Assets on the Web by Yoav Weiss
There are, of course, some trade-offs, as that lighter weight may come
at a cost to functionality you rely on. But often it just removes features
you don’t use, resulting in a faster experience at no particular cost.
23 http://smashed.by/webpack
24 http://smashed.by/rollupjs
25 http://smashed.by/parceljs
279
the code you need for your current route. Then you can use prefetch to
CHAPTER
cation, so that users can get them rendered and working relatively fast,
and then use them to bootstrap the rest of your app.
• You need to make sure that the content is not relaid out when
the images have downloaded, as that will introduce jank to the
scrolling process.
280
Loading Assets on the Web by Yoav Weiss
The first two points are hard to get right, as to include the in-viewport
images in markup and lazy-load the rest, you would need to automati-
cally generate your HTML based on the device’s viewport dimensions.
The third requires you to pay particular attention to your markup
and styles, and make sure your images’ containers have well-defined
widths and heights.
INTERSECTIONOBSERVER
26 http://smashed.by/lazyloadbelowthefold
27 http://smashed.by/paddingtophack
28 http://smashed.by/intersectionobserver
281
Lazy-loading implementations achieve their goal, then, without spam-
CHAPTER
PLACEHOLDERS
You can also include a low-quality placeholder for your image until it
gets loaded. This has a couple of advantages:
• If your users scrolled fast and the image has not yet loaded, a
placeholder gives them a better experience.
In its simplest form, you could create a thumbnail of your image and
incorporate it in your markup as a data URI. You’re probably better off
doing that through a build-time automated solution than manually.
There are also more advanced techniques29 available that can help you
get a nicer blurred placeholder image, without paying more in bytes.
WHAT TO DO?
29 http://smashed.by/svgplaceholder
282
Loading Assets on the Web by Yoav Weiss
FONTS
The default behavior for font loading varies between browsers: IE and
Edge show the user a fallback font while they wait for the fonts to down-
load; others prefer to block font rendering for three seconds, waiting
for fonts to arrive, and only then render the fallback font. The default
font-loading behavior of most browsers can be considered to be blocking,
then, or at least “blocking for three seconds,” which is not ideal.
What’s the best way to control your font loading and make sure your
users don’t find themselves staring for seconds at a fully laid out page
with no text to read?
There are various strategies30 you can take to tackle this, but you have
two major options.
30 http://smashed.by/webfontsloading
283
FONT-DISPLAY CSS RULE
CHAPTER
The font-display CSS rules are fairly widely supported (all modern
browsers except for Edge), and can enable you to control browser
6
• If you can live with the flash of unstyled text (FOUT), where
the fallback fonts are rendered first and then replaced by the
loaded fonts once they arrive, font-display: swap is probably
the way to go.
One major caveat with CSS font-display is that different fonts may
trigger the swap at different times; if you have multiple font files that
represent different styles or weights, they may come in at different
times, resulting in a potentially jarring experience to your users.
284
Loading Assets on the Web by Yoav Weiss
The Font Loading API allows you to explicitly load fonts from Java-
Script, and perform certain actions when they finish loading. If you
have several font files that need to be rendered together, you can load
programmatically and change the CSS rules to apply them (by adding a
class on their container, for example, or adding the fonts dynamically)
only once all of them have finished loading.
285
EARLY DISCOVERY
CHAPTER
H2 Push, discussed earlier, is one way to make sure that by the time
critical resources are discovered by the browser, they are already
downloaded and are safely stowed in its cache.
PRELOAD
You can also use preload’s onload event to create more sophisticated
loading patterns, as we’ve seen previously.
286
Loading Assets on the Web by Yoav Weiss
HTTP/2 priorities are fairly complex and not all servers fully respect
them. Also, because HTTP/2 is built over TCP, prioritization is even
trickier. It’s possible that the server would start sending low-priority
resources, then switch to high-priority ones, but have the low-priority
resources fill up the TCP queues, blocking more critical content.
287
For Link headers, there’s work underway31 to resolve that in Chro-
CHAPTER
about contention.
Minimal Content
We talked earlier about properly splitting and prioritizing critical and
non-critical content, but it turns out on the web there’s a third class of
content: unneeded content.
Puppeteer coverage API32 enables you to detect such unused content and
take action on it. It is very easy to use such tools to see how much
unused CSS and JavaScript you have in your page. It may not be as
trivial to take action on those unused parts and delete them automat-
ically, and I’m not aware of any current tools that do that. But at the
very least you should be able to monitor the amount of unused JavaS-
cript in your application.
With that said, it’s a bit tricky to distinguish unused code from code
that will be used later on in some user scenario. That’s probably the
31 http://smashed.by/linkheaders
32 http://smashed.by/classcoverage
288
Loading Assets on the Web by Yoav Weiss
One caveat with tools that use headless Chrome for unused code
detection: polyfills for features implemented in Chrome and not imple-
mented elsewhere will be declared as unused code. While that is techni-
cally true, that code is likely to be needed in non-Chromium browsers.
That is something worth keeping in mind when removing unused code.
As always, it’s significantly easier to avoid unused code when you build
a site from scratch than to weed out unused code from a legacy code-
base. Finding out where each piece of code comes from and where it
might be used can be a tiresome manual process. Make proper coverage
checks part of your continuous integration and deployment process.
That helps to make sure you don’t add unneeded bloat whenever you
incorporate a new library for that shiny new feature.
Well, not really. First of all, that content will not be cached for first-
time visitors. That could be a large chunk of your users, depending on
your audience, and as they say, you only get one chance to make a first
impression. The content will also not be cached whenever you update
it, which you should do fairly regularly to avoid any known security
vulnerabilities33 in popular libraries.
33 http://smashed.by/jslibvulnerability
289
On top of that, caches get evicted. Particularly on mobile devices
CHAPTER
(where it matters most), unless your site is the most popular one a
user visits, your precious resources may have made room for others.
Having bloated resources (which take up more cache space) actually
6
For JavaScript, extra code also means extra parsing costs. Some JavaS-
cript engines cache their parsing products, making processing faster
in repeat views. For example, V8 uses code caching to make sure
JavaScript bytecode can be reused in repeat views, reducing parsing
costs there. But that may not be true for all JavaScript engines and all
JavaScript content (as some content may not be eligible for caching).
There are also no guarantees that the cached parsing products won’t
get evicted before your content does.
Finally, unused CSS and JavaScript increases your site’s memory foot-
print for no good reason, as the browser has to maintain the unused
rules and code in memory for as long as your site is in memory. In
low-memory environments like mobile, that could cause your site’s tab
to be kicked out of memory when the user is not looking.
COMPRESSION API
290
Loading Assets on the Web by Yoav Weiss
Combining these two efforts will let us use compression to get rid of
most of our unused code and not transport it to our users every time.
• Both These efforts are still in their early phases, so it may take
a while before you can take advantage of them.
34 http://smashed.by/sharedbrotli
35 http://smashed.by/nativezipapi
291
WEB PACKAGING
CHAPTER
The primary use case for that format is to be able to bundle pieces
of content and enable users to pass them along between themselves
(without going through a server), or enable content aggregators (such
as Google Search) to serve such content from their servers, while the
browser still considers the content provider’s origin as the real origin
(from a security perspective, as well as from a browser UI one).
But a secondary use for such a format could be to improve the web’s
bundling capabilities.
But in practice, developers soon found out37 that bundling still has a role:
36 http://smashed.by/webpackage
37 http://smashed.by/jspackaging
292
Loading Assets on the Web by Yoav Weiss
• Even though HTTP/2 has no extra cost per request over the net-
work, that doesn’t mean that requests have no extra overhead in
the browser. In Chrome, at least, that overhead can make a differ-
ence when added up among hundreds of requests.
So, bundling still has a role and can improve overall performance if
your site has many different JavaScript or CSS resources. But cur-
rently, bundling also has a cost. We’ve already discussed the fact that
both JavaScript and CSS are resources that must be executed in their
entirety. When we bundle resources together, we effectively tell brows-
ers that they cannot be executed separately, and none of them starts
executing until all of them have been downloaded and parsed.
38 http://smashed.by/streamingparser
293
Current advice for developers is to find the right trade-off between
CHAPTER
these two approaches: bundle scripts, but avoid creating huge bundles
that delay the scripts’ execution and increase the probability of unnec-
essary cache invalidation.
6
But let’s go back to Web Packaging and how it can help us. It can
enable us to create a bundle comprising many smaller files, with
each one of them maintaining its identity as an independent entity.
Each one of them can be cached separately (based on its own caching
lifetime directives) and processed separately. Therefore, we would no
longer have to download the entire package to update a single resource
within it, and we won’t have to invalidate all of it if one resource has
changed or needs revalidation. We also would be able to compress the
entire bundle as a single resource, providing us the same compression
benefits as today’s bundling would.
This is all in its early days, and there’s currently no concrete proposal
for it, but almost all the building blocks are in place or in motion, so
hopefully this will become a reality sooner rather than later.
Images
Images are another source of unnecessary content bytes. Too often,
images are sent either oversized or under-compressed.
39 http://smashed.by/sb5
40 http://smashed.by/hiperfimagesbook
294
Loading Assets on the Web by Yoav Weiss
RESPONSIVE IMAGES
The sizes attribute can make the matching even tighter. When the
browser requests the images, it isn’t aware of their display dimen-
sions (as layout may not have happened yet). sizes tells the browser
the image display dimensions for different breakpoints of the design,
so the browser can calculate the ideal resource provided by srcset
and w descriptors.
295
The sizes value could be relatively simple, just stating a percentage of
CHAPTER
Client Hints
Client Hints is a content negotiation mechanism that can be also be
used to serve responsive images, among other resource types.
With Client Hints, once the server has opted in (using Accept-CH and
Accept-CH-Lifetime headers), the browser can start sending the server
information using request Headers: DPR (device pixel ratio), indicates
the screen density of the device. Viewport-Width indicates the width of
the browser’s viewport. Width gives the server the display width of the
image (if the image has a sizes attribute). It can also indicate whether
the user has opted in to data savings mode, using the Save-Data Client
Hints header. The server can then serve the right resource to the client
without requiring markup changes (or while requiring smaller ones).
296
Loading Assets on the Web by Yoav Weiss
IMAGE FORMATS
If you have control over your server, it’s probably easier to use the
image request Accept headers to detect if the browser supports
WebP, JPEG-XR, or just the older (but reliable) JPEG and PNG. Then
you can use your server-side logic to pick the ideal resource for that
particular browser.
If you have no server-side control, you can use another variant of the
responsive images markup to let the browser pick which format it
supports.
<picture>
<source type="image/webp" srcset="president.webp">
<source type="image/vnd.ms-photo" srcset="president.jpxr">
<img src="president.jpg" alt="The president fistbumps someone.">
</picture>
297
Font Subsetting
CHAPTER
Web fonts are another type of content where you can send your
users excessive data. Fonts often contain full sets of characters
6
When you can make assumptions about the content at build time,
there are tools (like subfont41 or glyphhanger42) that can help you sub-
set fonts to the minimal subset you need.
Contention Avoidance
We talked earlier about resource priorities and the way that brows-
ers handle priorities in HTTP/2 by sending requests to the server
and letting it send the high-priority resources first. However, there’s
one big problem with that scheme: on the web today there is no single
server. Most sites are served from a multitude of servers, as different
first-party resources are often served from different hosts (static files
served from static hosting providers, images served from an image
optimization service, and so on). On top of that, third-party resources
are served from servers outside of the publisher’s control.
41 http://smashed.by/subfont
42 http://smashed.by/glyphhanger
298
Loading Assets on the Web by Yoav Weiss
CONNECTION POOLS
43 http://smashed.by/connectpools
299
by the navigation connection’s certificate Server Alternate Name (SAN)
CHAPTER
• A DNS request is still required (to verify that these hosts map
to a single IP), adding some latency to the process.
SECONDARY CERTIFICATES
44 http://smashed.by/coalescing
300
Loading Assets on the Web by Yoav Weiss
DELAYED REQUESTS
While doing that mostly works for HTTP/1, for HTTP/2 it seems we
can do better. Delaying the requests means they will hit the servers
half an RTT after we decide it’s OK to send them, because the risk of
contention is gone (for example, the critical resources have down-
loaded). Also, by default, if we hold back the requests, no connection
will be established. We could, then, implicitly preconnect to those
hosts. That’d be better, but maybe still not perfect.
301
If done well (with some heuristics, since we’re not certain we know
CHAPTER
the size of the critical resources coming in), that can lead to perfect
network stack-based orchestration of content coming in from multiple
connections.
6
SAME-CONNECTION H2 CONTENTION
302
Loading Assets on the Web by Yoav Weiss
Sending the full files one after the other means they can execute faster.
Browsers can control the way that H2 servers send their resources and
try to influence them, using H2’s dependencies and weights. While
weights represent the resource’s priority, dependencies can tell the
server to prefer to send a resource only after the entire resource it
depends on was sent down. In practice, that translates to the server
sending resources one-by-one, if it’s possible.
303
WHAT CAN YOU DO ABOUT IT?
CHAPTER
Unfortunately, most of the items listed above are things that browsers
would need to improve in order for you to take advantage of them. But
6
there are still a few things you can do to minimize the negative impact
of third-party downloads on your main resources.
Another reason to make sure these resources are public is that if they
depend on cookies in any way, the cookies will be lost when rewriting
the URLs.
304
Loading Assets on the Web by Yoav Weiss
If you do, you need to make sure that two conditions apply for the
browser to coalesce those connections:
Those are tricky conditions, and cannot always be satisfied (for IT/
infosec reasons), but if you can make them work, you can enjoy con-
nection coalescing today.
305
The way you would do that for XHR would be something like:
CHAPTER
xhr.withCredentials = true;
xhr.send(null);
For fetch(), you’d need to add the credentials parameter to the init JSON:
Minimizing Latency
One of the most important factors on resource loading performance is
network latency. There are many aspects of loading that are affected
by it: connection establishment, resource discovery, and the delivery
itself. While we cannot reduce the latency of the physical network,
there are various ways in which we can get around it.
PHYSICAL LOCATION
One way is to bring the content closer to the user. That can be done by
hosting the content at various locations around the planet and serving
users from the location closest to them. This will require you to syn-
chronize the content between those different locations.
It turns out there are commercial services that will do that for you.
Content delivery networks enable you to host your content at a single
location, while their edge servers take care of distributing it all over
306
Loading Assets on the Web by Yoav Weiss
the world. That enables significantly shorter latencies for both con-
nection establishment and content delivery, assuming you set your
content’s caching headers correctly.
CACHING
IMMUTABLE
Any public content which you refer to from your pages and can
change the reference to once the content changes, should be
considered immutable. You can achieve that by having a con-
tent-addressable URL: a URL which contains either a hash or a
version of the content itself, and which changes by your build
system once the content changed. You would also need to anno-
tate the content with something like the following headers:
Cache-Control: public, immutable, max-age=315360000.
That would tell the cache server or the browser that the content will
never change (or tell them that the content will not change in the next
ten years, if they don’t support the immutable keyword). It would
enable them to avoid content revalidation, and know that if it’s in the
cache and needed by the page, it can be served as is.
307
ALWAYS FRESH
CHAPTER
Any content which users navigate to directly through links or from the
address bar (usually your HTML pages) should have a permanent URL that
6
does not change if the content does. Therefore, we cannot declare such
resources to be immutable, as it will be impossible to modify them if we
found an error in the page, a bug, or a typo.
You could argue that such content can be cacheable for relatively short
times (probably hours). Unfortunately, that would make it very hard for
you to change the content within that time window if you need to ship
unpredicted changes. As such, the safest choice is to make sure the content
gets revalidated with the server every single time. You can do that by using
headers such as: Cache-Control: no-cache.
OFFLOAD
The problem with the previous approach is that it prevents origin and edge
caches from offloading the content from your origin. If the cache gets hit
with 1,000 requests per second, it needs to relay those 1,000 requests to the
origin server, not providing much benefit as a cache to that type of content.
That will make sure your content is publicly cacheable for five seconds,
and gets revalidated at the origin after that. So if your cache server gets hit
with 1,000 requests per second, only one request in 5,000 will get revali-
dated at the origin, providing significant offload and time savings benefits.
308
Loading Assets on the Web by Yoav Weiss
The content is then present and valid in the cache for long periods of
time, does not get revalidated at the origin, but gets evicted from the
cache as soon as the origin explicitly indicates (through an API or other
proprietary means) that it should be.
SERVICE WORKERS
Another great way to reduce the impact of latency and increase the
power of caching in browsers is to use service workers.
309
OFFLINE
CHAPTER
The basic use case which service workers cover is offline support.
Since they have access to requests and responses, as well as to the
6
browser’s cache API, they can use that to cache responses as they come
in, and later use them if the user is offline. That enables sites to create
a reliable offline experience, and serve their users even in shaky or
missing connectivity conditions.
One of the patterns that has emerged is “offline first,” where the previ-
ously cached content is served to the user before going to the network,
providing them with a near-instant experience, while the resources
fetched from the network are used to update the content displayed to the
user once they arrive.
That lets browsers start processing and displaying the static pieces
of the HTML immediately, and fill in the gaps later with the content
fetched from the server.
COMPRESSION DECODING
310
Loading Assets on the Web by Yoav Weiss
Such pages would need to build in some refresh mechanism to let them
update themselves if they discover they have been purged after being
served from the server. While not being ideal, that’s the closest we can
get to purge mechanisms on the web today.
The web’s ability to mash-up content from different sources and origins
is what makes it a powerful, expressive platform. But it comes at a secu-
rity and performance cost. Third parties on the web are often incentiv-
ized to maximize their engagement metrics, which don’t always align
with your users’ performance and experience.
311
The Accelerated Mobile Pages (AMP) project was created, at least
CHAPTER
But if you’re not using AMP, how can you make sure your third-party
content doesn’t cause too much damage to your user’s experience?
LAZY LOADING
Another useful way to get third-party content out of the way is to com-
partmentalize it in its own iframe. That is good for security (because it
prevents it from accessing your page’s DOM without your permission),
as well as for CPU performance (as cross-origin iframes have their own
main thread).
312
Loading Assets on the Web by Yoav Weiss
Unfortunately, not all third parties are amenable to being iframed, and
some require access to the main page’s DOM to function properly. Past
initiatives such as SafeFrame, which enabled controlled DOM access
to iframed third parties, did not take off, and not many third parties
support them.
If you have to run your third-party scripts in the main page’s context,
you can restrict what content they can download and where they can
download it from using Content Security Policy. Including CSP direc-
tives can help you make sure that, for example, image-based ads don’t
turn into video and audio ads without your permission. At the same
time, you should note that CSP doesn’t yet allow you to control what the
iframes that load in your page are doing, so it cannot be used to enforce
restrictions on ads loaded inside iframes, for instance.
45 http://smashed.by/subfont
313
FEATURE POLICY
CHAPTER
Finally, one of the great promises of AMP is that it only enables the page
and third parties a subset of the web’s functionality. That doesn’t seem like
6
One effort to bring the same type of enforcement to the web is the Feature
Policy specification.46 It allows developers to explicitly turn off features
they know they won’t need on their page, and prevent third parties from
(ab)using those features. Features like video and audio autoplay, use of
synchronous XMLHttpRequest, size of loaded images and their compres-
sion ratios, and more. Feature policies are inherited by iframes so the same
restriction will apply to them as well. You can also define iframe-specific
feature policies, so you can restrict your iframed third parties further than
you restrict the main context.
In Summary
Loading resources on the web today is hard. At the very least, it’s hard to
do in an optimal way. But it’s getting better. And browsers are investing
heavily in improving it significantly, too.
WHAT TO DO?
We’ve covered many different subjects throughout the chapter, so it’s easy
to lose track of what is theoretical and what is actionable. Below is a check-
list you can go over to refresh your memory, and find things to focus on to
improve your site’s performance.
46 http://smashed.by/featurepolicy
314
Loading Assets on the Web by Yoav Weiss
315
5. Avoid bandwidth contention between your resources
CHAPTER
316
Loading Assets on the Web by Yoav Weiss
• Feature Policy combined with iframed third parties will enable devel-
opers to take back control over their user’s experience.
• Early delivery and resource discovery will be fixed using Server Push
(with Cache Digests), preload and Priority Hints. Build tools will auto-
matically create those instructions for developers at build time.
• Common build processes will also help make sure completely unused
JavaScript and CSS are never sent to browsers, and rarely or late used
code is loaded later on.
317
• Web Packaging and packaged content will enable browsers to
CHAPTER
• And finally, measurement APIs will enable us to keep track of all the
above and make sure our users’ experiences are as snappy as we want
them to be.
These are all things that are currently being discussed and are in different
phases of the standardization process. Some might not make it, and be
replaced with more mature incarnations. But the aim is to solve all these
problems in the next few years.
318
chapter 7
On Designing Conversations
Adrian Zumbrunnen
On Designing Conversations
CHAPTER
by Adrian Zumbrunnen
7
In the pages ahead I’m going to share what I’ve learned from turning
my website into a chat, and combine it with insights from working on
the Google Assistant. The goal is to give you a bird’s-eye view of the
craft of conversational design, and outline a simple process you can
follow to create your own conversational experiences. Let’s get started.
•••
Think about the last time you had an engaging conversation. When
was it, and what made it engaging to begin with? We often have a
hard time remembering why certain conversations made us feel the
way they did. Since conversations typically happen spontaneously,
and because they often evolve in unpredictable ways, we can’t easily
pinpoint their qualities to a few specific attributes. On top of that, we
all value slightly different things when it comes to talking to people.
Some of us appreciate conversations that help us feel connected, while
others appreciate them for their unique ability to entertain and inform.
No matter what our individual preferences, there are underlying rules
and principles that affect whether we feel a conversation is effective or
whether it’s a waste of time. Take this interaction as an example:
1 https://azumbrunnen.me/
320
On Designing Conversations by Adrian Zumbrunnen
2 http://smashed.by/dining
3 Rick van Baaren, Rob Holland, Brejge Steenart, Ad van Knippenberg:
“Mimicry for money: Behavioral consequences of imitation”
Journal of Experimental Social Psychology 39, no. 4:393-98 (July 2003)
321
In simple terms, we tend to feel more comfortable being around people
CHAPTER
This helps explain why the more time couples have spent together,
the more they behave and look alike. It’s why we can easily tell from a
distance whether someone’s date is going well. And it’s why waiters get
a bigger tip when they repeat a customer’s order.
In his bestselling book How to Win Friends and Influence People, Dale
Carnegie shares an insightful story of meeting a distinguished bota-
nist at a dinner party in New York. Instead of talking to a wide range of
attendants, Carnegie mostly focused on the botanist who was eagerly
sharing his thoughts about his experiments with plants and indoor
gardening. At the end of the night, as people started leaving, the bota-
nist wouldn’t stop praising Carnegie for being a great conversational-
ist. The interesting fact was that throughout the whole evening, Carne-
gie barely engaged in any talking. Instead, he just listened, kept asking
questions, and was genuinely interested in the botanist’s stories.
322
On Designing Conversations by Adrian Zumbrunnen
MAXIM OF QUALITY
The quality maxim describes that every contribution should be true
and supported by evidence. Ambiguous responses and statements you
don’t think are true should therefore be avoided.
MAXIM OF QUANTITY
The quantity maxim is about providing the right amount of infor-
mation at the right time. I once ran a user study to test a new design
exploration in Google Maps. When I asked Lotta (not her real name)
how she would navigate to a specific coffee place shown in the pro-
totype, she didn’t just describe her navigation strategy in the most
detailed way humanly possible, she also went on to explain how her
nephew loved coffee and how he recently moved to Orlando but strug-
gled to find a nice apartment. From that moment on, it became incred-
ibly challenging to shift the conversation back to the original ques-
tions the study aimed to answer. Somehow, Lotta’s nephew suddenly
appeared in the answers to almost every question we asked.
4 Grice, Paul (1975). "Logic and conversation". In Cole, P.; Morgan, J. Syntax
and semantics. 3: Speech acts. New York: Academic Press. pp. 41–58.
323
Even when the intention of sharing more information is to be helpful,
CHAPTER
it often ends up having the opposite effect. That’s what leads us to the
the maxim of relevance.
7
MAXIM OF RELEVANCE
Every contribution should be as relevant as possible. When ask-
ing someone if it’s going to rain today, we expect a simple yes or
no answer. Saying something like: “San Francisco is going to be
sunny today with highs of 50 degrees and lows of 43 degrees” is well
intended, but will most likely feel less natural.
MAXIM OF MANNER
Both parties should express themselves in a clear, brief, and orderly
way. That also means we should avoid unnecessary technical jargon or
words users might not understand.
The French mathematician Blaise Pascal famously said “If I had more
time, I would have written you a shorter letter”. As we will see later, it’s sur-
prisingly difficult to design conversations in a succinct and clear way.
324
On Designing Conversations by Adrian Zumbrunnen
he might very well get some brownie points from the waiter. But the
fact that customers don’t get any tip or a rating obviously provides
less initiative for them to be more cooperative and invested in the
conversation.
In this section, we’ll discuss some of the key insights I had from turn-
ing my website into a chat, and discuss why they are relevant for any
kind of conversational interaction.
325
CHAPTER
7
The screenshot above shows one of my very first drafts. This specific
part of the text remained unchanged till the very end. Everything
else would get highly redacted, edited, or removed. As I kept writing
conversations, I realized I automatically started to use my own syntax
to represent how a conversation could evolve. I used the vertical bar
symbol “|” to separate suggestions. Tapping on a suggestion like “Know
more” would cause the bot to say what’s specified under “Know more.”
326
On Designing Conversations by Adrian Zumbrunnen
This might seem like a quite primitive way to build conversations, but
to my surprise it worked.
Sceptics were quick to say that this is not an actual conversation. Jared
Spool, founder of User Interface Engineering and a well-respected
thought leader in the industry, was very skeptical when we talked
about it. According to Jared, these types of interfaces don’t even come
close to an actual conversation. I immediately understood where Jared
was coming from and yet, to this day, I fundamentally disagree with
him. I’d argue that no matter what we create in our field, it is far more
important to think about how something feels, rather than how it
technically works. It’s not logical. It’s psychological. The way users think
about an experience is not the way we do.
After a few drinks, I headed home to prepare for an upcoming trip. The
smile on my face soon vanished when I realized that my pockets felt
awfully empty. I braced myself. Three seconds later, I panicked: where
is my wallet?!
327
I was just about to call my bank to block my credit card when an
CHAPTER
up meeting later that same day. When he gave me the wallet, I said:
“Thanks so much! You saved my life! By the way, just out of curiosity, how did
you get in touch with me?”
That’s when it hit me. This benevolent savior of my wallet had thought
he was having a real conversation with me on my website. What was
even more remarkable is that we used three different communication
channels: we went from website, to email, to voice call. That didn’t seem
to matter to him. All that mattered was the thread: a continuous line of
thought that seamlessly carried on across different media channels.
Make no mistake, I’m not arguing that we should lie to users. This is
not about deception. It’s about keeping the mental model of conversa-
tion intact.
328
On Designing Conversations by Adrian Zumbrunnen
database, that your like is official for others to see. What’s the benefit
of doing this? In simple terms: perception of speed. The user interface
assumes the action of liking a photo will succeed in almost every
scenario (unless the user has an incredibly slow or flaky internet con-
nection), and so immediately responds as if the request had already
been successful. The application is optimistic about the user’s action
successfully making its way to the server.
329
As I kept writing the main part of the dialogue, I realized that it felt
CHAPTER
to, but the using the conversational model was the first time it felt like
I was answering those questions in a literal way. Structuring conversa-
tions proved to be just as challenging as classic information architec-
ture on the web, and for the structure to make sense, I needed to first
understand what I actually wanted to convey.
Maya Angelou famously said “people won’t remember what you said, but
they will always remember how you made them feel”. That was a thought-pro-
voking idea that deeply affected how I approached writing the entire
dialogue. Instead of going deeper and deeper, and meticulously script-
ing every single response, I took a step back and first focused on how I
wanted visitors to feel. After some (deep) introspection, I identified two
key emotions: curiosity and delight. Those became my guiding principles,
my compass, in how I would write the conversation.
330
On Designing Conversations by Adrian Zumbrunnen
What was so difficult about it? In short, making sure users never felt
like the conversation got in their way and slowed them down. To avoid
this, I started mentally engaging in a dialogue with myself. I would go
through dozens of scenarios and think about what I might want to ask
and what I would possibly say next. I even pictured myself sitting on
a train and meeting a complete stranger, contemplating how such a
conversation could start and evolve. Saying this was a good lesson in
practicing empathy would be a major understatement. Even though
all of this seemed silly at first, it greatly helped me improve my under-
standing of what I was trying to convey. Thinking back, I wish I had
discovered the technique of imagining a conversation earlier in my
career. It didn’t just help me better understand how to write conversa-
tions, but showed me a new way of approaching any design problem I
had ever encountered. After all, any form of design is communication;
it’s just that conversation happens to be the most literal form of it.
The conversation starter therefore kicked off with this exact question:
“Want to know more, or get in touch?” This ensured that users could
always instantly get in touch, and it’s why I have my wallet with me as
I’m writing down these thoughts.
331
CHAPTER
7
Conversational I.A.
When users tapped on “Get in touch,” the bot would invite them to
share what they had in mind. Once users sent a response, the bot
would compliment them for their intriguing message and then follow
up by asking about their email. The bot didn’t just bluntly ask for a
user’s email address, though. It explained why it was needed to make
sure users understood why they were sharing something personal so
early on in an interaction.
This eventually closed the loop: the bot confirmed that it received the
message, and based on the insights from the study about waiters, it
mirrored what users had said by repeating and confirming their email.
Many visitors only realized at the very end that they had in fact just
filled out a form and by the point they noticed it, it was already sent.
Writing Predictively
One cold winter morning in January, I forgot to take my laundry out
of the dryer. It wasn’t the first time, and my neighbor was quick to
pick up on my negligence. That morning, as I was rushing out of the
laundry room to make it to a meeting at work, he caught me and con-
fronted me right away. He was quite serious about it. “Next time your
laundry is in the tumbler, I will just take it out and leave it on the floor.”
332
On Designing Conversations by Adrian Zumbrunnen
When I was at work later that day, I thought about the way this encoun-
ter had unfolded. I was angry, not because of what he said, but because
of how I reacted. Worst of all, I knew he was right. He had every reason
to be annoyed. There was an opportunity to acknowledge, apologize,
and change my behavior, but I instead decided to act like an idiot.
We all occasionally run into situations like these. Whether it’s quarrels
with our neighbors, arguments with our children, or toxic conversations
with the people we work with, conflict seems unavoidable. Whenever
it happens to me, I often can’t help but imagine what would have hap-
pened if I had reacted in a different way. The ability to travel back in time
and alter events has captured my imagination since the first time I saw
Back To The Future. It would feel like a magic trick, a superpower. That
superpower, as it turns out, is something everyone who creates scripted
bots has. After all, we’re in control of the entire conversation.
We control what users can ask and we can craft the perfect response for
whichever suggestion users pick. The limitation of what users can say is
a blessing and a curse at the same time. Limitation makes our lives eas-
ier, but users will mind if they feel like they can’t express what they have
in mind. That’s why predicting and trying to anticipate what users might
want to say next in any given conversational junction is so critical.
Our ability to correctly predict what users want to say next defines
whether users will mind that there is no free-speech option. In other
words, as long as we can offer responses that feel like something users
most likely want to say next, the less they will mind that we’re taking
away their ability to freely talk about whatever they have on their minds.
333
Obviously, we can’t always correctly predict what users have in mind.
CHAPTER
that are highly relevant for that given conversational moment. Take
this as an example:
[Projects] [Writing]
The Boolean choice is the bot trying to clearly convey what it can talk
about and what it can not. By establishing clear boundaries, the bot
guides the conversation to where it can succeed. It’s very likely that
suggestions will never perfectly match what users envisaged, but
the closer we get, and the more thoughtful we are in how we curate
those responses, the better the conversation will flow as a result of
our efforts.
After wiring up the contact form and writing the main part of the
dialogue, over the course of a few weeks, I turned it into an interactive
experience. I started to connect every single response to a previous
topic, and so on. I was surprised: the conversation all of a sudden felt
quite different from what I had originally imagined. The simple act of
writing a conversation and then experiencing it felt like two funda-
mentally different things. Let’s look at some of the things that stood
out the most.
334
On Designing Conversations by Adrian Zumbrunnen
2. DELIGHTFUL DETAILS
Delightful details are about flirting with users. Details in writing and
the use of technology can make a critical difference between a conver-
sation that feels lame and a conversation that sparks users’ interest.
335
For example, refreshing the page changed what the bot said. Instead of
CHAPTER
just saying “Hi,” the bot would now say: “Welcome back! 🙌”. It would
then even subtly change the conversation path – something we’ll
cover in more detail in the section about context awareness.
7
When people noticed this for the first time, they understood that the
bot was smarter than they had initially assumed. It changed the way
they thought about the interface. They suddenly started wondering:
what else can this thing do?
336
On Designing Conversations by Adrian Zumbrunnen
a user would say. The blob could technically even mirror the user’s
emotion by changing its color and adapting its movement based on
emotional cues in the conversation. Eventually, I came to a somewhat
sobering conclusion, though. Unless most of my visitors were a bunch
of suckers for sci-fi interfaces, they would most probably not want to
talk to a blob. So I stuck with a traditional chat interface that clearly
emphasized the fact that visitors were talking to me instead of a blob.
R.I.P. blob.
337
Instead of opening a conversation with something ambiguous like
CHAPTER
“Hey, how’s is it going?” you might say something that’s slightly more
appropriate within the context, such as, “Hey, did you see Paul’s talk
today?”
7
338
On Designing Conversations by Adrian Zumbrunnen
This small adjustment didn’t just improve the conversation’s flow and
make it more human – it also made some of the chatbot’s features
more discoverable. Rather than relying on the conversation to evolve
to ultimately cover the topic of design, the bot could now talk about
design right from the start.
339
In a study from 1944, experimental psychologists Fritz Heider and
CHAPTER
340
On Designing Conversations by Adrian Zumbrunnen
341
Many people asked me which natural language technology I used. At
CHAPTER
first, I didn’t understand why anyone would ask that question until it
dawned on me: observing how their choice became part of the conver-
sation changed how people felt about it. Users suddenly felt like those
7
were their own words, their own choices, whereas without animation,
there was a disconnect. Without animation, the conversation would
have felt scripted and unnatural. Animation instilled a sense of interac-
tivity and nowness to it. Put differently, animation made the conversa-
tion come to life.
My website’s design is what some would call very Swiss. It has a lot of
white space, it’s pretty minimalistic, and there are almost no colors in
it. In short, it’s not exactly what anyone would describe as emotional.
My chatbot, on the other hand, sends emojis, laughs, and makes jokes.
342
On Designing Conversations by Adrian Zumbrunnen
•••
343
After working on this project for a couple of months, one user ran into
CHAPTER
This would shortly become one of the most tweeted interactions. The
7
344
On Designing Conversations by Adrian Zumbrunnen
•••
Let’s now look at a simple design process you can follow to create your
own conversational experience.
345
The jobs to be done (JTBD) framework provides a great way to identify
CHAPTER
Notice how this use case provides enough context for us to understand
what the problem is and in which situation it occurs. Sometimes it
can be helpful to create a more specific user description by adding job
title, age, and so on. When we do that, we need to make sure we don’t
exclude groups of users who should be part of our target audience.
Countless books have been written about personas, user stories, story
mapping, and so on, and while I appreciate the thoughtfulness of a
well-crafted persona, I find that plain and simple user stories usually
serve as a great starting point. They help us think about the problem
space without losing ourselves in overspecifying a type of user that, in
reality, might not even exist.
346
On Designing Conversations by Adrian Zumbrunnen
347
rowing down the set of relevant results. There is an element of control
CHAPTER
Let’s consider the first use case: getting in touch. If someone wants to
reach out and chooses “Get in touch,” the system will track whether the
user successfully makes it to the end of the thread. If the drop-off rate
is over 50% (that is, users give up or leave the website before sending
their message), I have a strong indicator that I need to take a closer
look at the dialogue.
348
On Designing Conversations by Adrian Zumbrunnen
Metrics can be very different depending on scope and the type of expe-
rience you create. The important part is making it measurable so you
can get new insights and take action where necessary.
349
The rise of messaging led to a big change in consumer expectations.
CHAPTER
Today, companies are just one more person we can talk to in our favor-
ite messaging apps. It’s the first time in history when companies can
have thousands of simultaneous one-to-one conversations with clients.
It’s this captivating idea that incentivizes many businesses to invest in
the conversational space.
4. DEFINE PERSONALITY
You have a personality, I have a personality, and as we saw earlier, prod-
ucts have a personality as well. One of the biggest misconceptions about
designing personality is that people think it’s a choice. It’s not. Every-
thing we create inherently has personality, whether we want it or not.
A few years ago, I met Don Lindsay, the former VP of design at Apple.
During his time at Apple, Don led the design of the brand new Mac OS
X (codename Aqua), a complete overhaul of Apple’s OS 9 operating sys-
7 More than half of consumers prefer businesses that use chat apps.
http://smashed.by/chatapps
350
On Designing Conversations by Adrian Zumbrunnen
tem. His team was also responsible for a feature, a visual effect really,
that is most commonly referred to as the genie effect.
The genie effect is one of the most iconic animations in user interfaces
of all time. It reinforced the idea that the new Mac OS X was about flu-
idity. The genie effect didn’t just make for a great animation; it empha-
sized the system’s personality while explaining to users where mini-
mized applications and windows would go. Everything in Mac OS X
Aqua was designed around that same idea, whether it was the buttons,
the window behaviors, or the wallpaper itself. The surface of the user
interface nicely complemented the iMac’s unique industrial design
and resulted in a product where hardware and software came together
beautifully to create an experience that transcended the screen.
351
decisions are nicely orchestrated, it results in a personality that makes the
CHAPTER
difference between a good product and a product people remember for its
consistency and ease of use.
7
352
On Designing Conversations by Adrian Zumbrunnen
For instance, if your brand is playful and fun, you might be more
inclined to use a more visual style of communication by using emojis,
pictures, and gifs. On the other hand, if your brand is serious, that type
of communication might feel less appropriate. When an experience
is solely based on voice, we have to rely on words (and intonation and
rhythm) to imbue the experience with an individual personality. Con-
sider a simple weather use case:
versus
353
5. DEFINE A CONVERSATION STARTER
CHAPTER
Set Expectations
The number of topics chatbots can cover is limited. It’s therefore
crucial to clearly establish where your bot can succeed, and what users
can expect. Don’t pretend to be human, or fool people into believing
they are talking to an actual person. A bot should clearly outline that
it’s a bot. This can happen both explicitly and implicitly.
354
On Designing Conversations by Adrian Zumbrunnen
Opener A:
Opener B:
355
established in the response itself, the number of words can be reduced
CHAPTER
Instead of writing “I want to get in touch,” we can simplify and write “Get
7
356
On Designing Conversations by Adrian Zumbrunnen
• About me
• Projects
• Past
• Present
• Contact
• Portfolio
• Articles/Writings
357
GREETING
CHAPTER
Let’s see how a follow-on response to the above greeting could look.
LEARN MORE
• I feel humbled!
Notice how we again try to set the direction of what users can talk
about next. The most ambiguous word in the above sentence is the
word “speak.” Its mere presence is enough to invite users to ask about
358
On Designing Conversations by Adrian Zumbrunnen
First, the bot politely greets users as the website finishes loading.
When users pick “Get in touch,” they can specify what they want to
talk about. After that, the bot asks for their email to allow me to get
back to them. After validating and confirming it, the bot encourages
users to explore the rest of website. When a user finally reaches “Bye
Bye!” without interruptions, they flawlessly reach the end of the
happy path.
359
EXTEND THE HAPPY PATHS
CHAPTER
free speech, we need to verify and parse user input. This is not just
incredibly error-prone, but also requires us to extend the conversation
with additional repair flows.
Reprompting
• That doesn’t look like a valid email. Do you want to try again or
get in touch later?
What should happen when a user picks “Try again”? Should it repeat
the previous sentence or should it be slightly different?
360
On Designing Conversations by Adrian Zumbrunnen
A wide array of input types can quickly turn the design of conversations
into a daunting task. Some of the most prolific designers struggle to
translate complex topics to conversational interactions. This is why we
emphasized the importance of picking the right use cases earlier.
361
IDENTIFYING REPETITIONS THROUGH PROTOTYPING
CHAPTER
• (user speaks)
• (user speaks)
• (user speaks)
User tests are invaluable because we get to observe the struggle and
the joy our designs create. It makes the job personal, and once things
get personal, it becomes hard to design something that isn’t great.
362
On Designing Conversations by Adrian Zumbrunnen
Parting Words
Design and the field of UX are in constant flux. As technology matures
and changes, so do the requirements and possibilities in our field. The
proliferation of conversational interfaces isn’t as much about replacing
old and traditional media as it is about complementing them. It’s an
opportunity to design intentionally to solve the right kind of problems,
in the right kind of context, with the right kind of interface.
363
As William Gibson said:
CHAPTER
“The future is already here – it’s just not very evenly distributed.”
7
364
chapter 8
UX Design of Chatbots
and Virtual Assistants
Greg Nudelman
UX Design Of Chatbots And
CHAPTER
Virtual Assistants
8
by Greg Nudelmann
here has never been a more exciting time for designing and
building digital products! As an example, here are just a few
of the projects that I was involved with last year:
And this just scratches the surface: the range of conversational bot
applications is wide, far-reaching and exciting. Today, consumer
sophistication is at an all-time high, and people in the consumer
and enterprise space demand polished, context-aware, personalized
cross-platform experiences fully integrated into their work and leisure
activities. Tech giants are rising to this challenge, competing with one
another by providing extraordinarily sophisticated frameworks and
366
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
For the more design-inclined readers, a word warning: there are some
simple snippets of code that support design explanations. Even if
you’ve never coded before, I encourage you to read the code snippets
and follow along with the exercises. Being able to read the code and
understand practical engineering considerations will make you a bet-
ter designer, particularly in the area of conversational bots, where the
nature of the medium makes it hard to represent UIs conventionally
(using mock-ups or diagrams). To be a competent conversational UI
designer, you have to know enough about how bots work to be able to
ask the right questions.
367
This chapter aims to give you enough background to comfortably
CHAPTER
Amazon Alexa is newer and, in many ways, easier to setup and config-
ure than LUIS, and offers many useful shortcuts. However, there is one
crucial difference: Alexa is best set up as an Alexa Skill, whereas LUIS
is a standalone chatbot. Which means that to invoke our bot – let’s call
it GUPPI (more on naming in a moment) – and pass it a command
(called an invocation), you have to invoke Alexa first. Compare the two
invocations below:
368
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
While Alexa Skill invocation works fine for simple commands, it cre-
ates awkward verbal constructs for more complex queries. Fortunately,
bot developers have some flexibility in invoking their Alexa Skill.
In principle, all of the utterances below would work equally well to
launch GUPPI and successfully pass the command along:
1 http://smashed.by/gassistant
369
parameters to the Google Assistant app during an invocation. Thus,
CHAPTER
during the initial user response phase (called fulfillment), the user is
typically greeted with a Default Welcome Intent message variant,
which launches a separate conversation with the bot. Once the user
8
Lastly, it pays to keep in mind that Alexa uses intent-based bot archi-
tecture (similar to Microsoft LUIS), so the developer has to add a dialog
construct later in the development process using a chunk of custom
code (using an AWS Lambda function,2 for example). In contrast, Goo-
gle Assistant assumes everything happens by default within a dialog,
using the Dialogflow wrapper, which also handles intents and entities.
Thus, Google Assistant gives developers access to powerful visual dia-
log builder features (shown in Figure 1), allowing developers to set up
simple applications almost entirely using the UI, with minimal edits of
the underlying code. If a developer requires additional configurability,
the Google Assistant framework offers access to the raw JSON conver-
sation webhook format which communicates directly to the Assistant.
Figure 1: Google Assistant provides powerful visual builder features for creating dialogs.
2 http://smashed.by/lambdafunction
370
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
In contrast to Alexa and Google Assistant (where the user invokes the
custom bot from within the primary digital assistant), IBM Watson3 is
a standalone bot framework, which makes it useful for autonomous
tasks. IBM Watson is in many ways similar to Microsoft Bot Frame-
work with LUIS. One nice improvement over LUIS is that while both
frameworks use intents and entities as a base, Watson also has a pow-
erful visual builder for the dialog construct, similar to that of Google
Assistant, as shown in Figure 2.
Microsoft LUIS only stores intents and entities in the bot’s definition,
while the additional Microsoft Bot Framework application code pro-
vides the waterfall dialog interaction (more on this later). In contrast,
IBM Watson offers the option to have the dialogs build directly as part
of the bot’s JSON definition, providing a comprehensive high-level
visual diagram of your bot’s waterfall dialog logic, shown in Figure 3
on the next page.
3 http://smashed.by/watson
371
IBM Watson also provides
CHAPTER
One last point: if your bot has a notifications or alerts feature, Amazon
recommends invoking “Notifications” at the top level as aggregated
Alexa notifications, not from within the Skill itself. The good news is
that users will get an accurate visual indication that they have notifica-
tions (in the form of a flashing orange ring on their Echo device).
372
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
The bad news is that you have to conform to Alexa’s notification frame-
work. If deeply customized interactive notifications are essential to
your experience, you may be better off with a standalone bot.
The Google Assistant bot framework does not yet provide app-level
notifications (currently in developer preview), though they should be
available soon. Google Assistant notifications appear to offer a middle
ground of a hybrid model, promising to provide more comprehensive
customization options at the cost of some additional complexity com-
pared to Amazon Alexa.
4 http://smashed.by/chatbotcomparison
373
Setting Up Intents
CHAPTER
When you initiate a conversation with a bot, the first thing it attempts
to do is figure out what you are asking – this is called intent. For exam-
8
374
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Let’s go ahead and train our bot with some basic phrases so it can start rec-
ognizing our intents. The result should look like the set shown in Figure 5.
375
Note: Don’t forget to hit the Deploy button after applying the
CHAPTER
Below is the complete initial set of four intents and the corresponding
training phrases for our bot:
• Music
• I want to hear something good
• Play me something good
• Play decent music
• Play me a song
• I want to hear some music
• Play music
Intent: Time
• Time
• Say the hours
• Use military time
• Is it morning already?
• Is it late or early?
• How early is it?
• Time of day
• How late is it?
• What time is it?
376
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Intent: Weather
• Weather
• What will be the high temperature?
• What will be the low temperature?
• What will the temperature be like?
• Should I pack an umbrella?
• What will the weather be today?
• Will it be sunny?
• Will it rain?
• Will it rain today?
Intent: None
377
When a bot receives an incoming query, it tries to match it against each
CHAPTER
378
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
The usual practice is to train the “None” intent with some nonsense
queries, such as “I like ice cream.” Occasionally, however, the “None”
intent can be quite useful for short-circuiting queries that would other-
wise confuse your bot, and result in erratic behaviors, unsatisfying
UX, and a loss of trust by the conversation partner (user). Practice and
exposure to real-world queries are key to identifying those queries and
then training your bot to handle “None” intents gracefully.
No matter how well you think you know your domain, don’t expect
your bot to handle every query flawlessly right from the start. Every
time one of my students or co-workers proudly presents his or her first
bot, the first few queries from the peanut gallery quickly reveal its lim-
itations, resulting in unexpected behavior. Typically, the bot’s creator
rarely anticipates how various conversation partners would ask their
questions, which makes real-world field usability studies and subse-
quent bot retraining essential for success.
379
normally a proprietary black box, so there is no way to predict when a
CHAPTER
Occasionally, the predicted score in the training set gives you a clue
8
380
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
• AMAZON.SearchAction<object@WeatherForecast>
• AMAZON.HelpIntent
• AMAZON.StopIntent
• AMAZON.YesIntent
381
ASIDE: WHAT’S IN A NAME — GUPPI, SAMANTHA, ALEXA,
ASIDE — CHAPTER
Why did we name our bot GUPPI? The name is, of course, a homage to
the excellent Bobiverse series.6
8
In this brilliant trilogy Dennis E. Taylor writes about Bob, a Silicon Val-
ley CEO, whose brain is scanned into a computer and becomes the AI
in an interstellar spaceship. GUPPI (General Unit Primary Peripheral
Interface) is virtual Bob’s virtual assistant. GUPPI has limited intel-
ligence, on a par with Siri or Alexa of our own post-industrial age, so
communicating with GUPPI is alien and awkward. The name “GUPPI”
helps remind Bob of the limitations of his assistant, which in turn
helps him converse at a level GUPPI can understand and process.
6 http://smashed.by/bobiverse
Aside — What’s in a Name by Greg Nudelman
While the AI names like GUPPI or HAL 9000 (from 2001: A Space Odyssey)
imply an alien digital intelligence, the familiar name Samantha implies
high similarity with human beings, self-awareness, and even con-
sciousness. Names like Alexa and Siri strike a balance of being some-
where between the two, sounding human, yet also somewhat rare,
exotic and alien. Our current AIs are much closer to GUPPI than they
are to Samantha – a typical conversational AI assistant understands
about as much as a dog does, minus any emotional intelligence.
Thus, it makes sense to invest some time in naming and visually rep-
resenting your bot in a way that helps frame from the start what your
conversation partners should expect from communicating with your
bot. Framing expectations is key. You want people to be impressed when
your dog-level AI understands simple commands like “GUPPI, bring
me my slippers.” You do not want your customers to be heartbroken
when your bot fails to understand the following: “Samantha, even if
you come home late and I’m already asleep, just whisper in my ear one
little thought you had today. Because I love the way you look at the
world. And I’m so happy I get to be next to you and look at the world
through your eyes.”7
7 http://smashed.by/guppiquotes
Extracting Entities
CHAPTER
Using the list entity type, the bot will check the incoming parameter
against a fixed array of possible values, as in the "specialty" list entity
below:
The list entity matching is highly accurate, but it suffers from a severe
defect: if the entity value is not in the list (limited to 20,000 at the
moment), the entire intent will not be recognized and default to “None”:
384
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
385
Provided this makes sense for your specific intent, you can make your
CHAPTER
386
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Figure 8: Determining meaning of the word “Chicago” in context using various intents.
Overall, the more context you can teach your bot, the smarter and
more human-like your bot will appear. And, of course, we can’t even
begin to talk about context until we teach our bot to ask follow up
questions. A complex pattern like this is called a dialog in Alexa, and a
waterfall in LUIS.
387
Plunging into a Waterfall: a Bot Conversation
CHAPTER
Note that the “Set Alarm” intent is a bit different from the previous
intents. Although the entity was optional in the previous intents
(default to favorite music or weather at a set location or one deter-
mined via GPS) this just does not make sense in the case of setting the
alarm. We need to know when to sound the alarm, so the DateTime
entity is required. We know that to set an alarm, the conversation part-
ner has to tell the bot two things:
388
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
If the conversation partner only specifies the intent and not the time,
by saying something like “GUPPI, set the alarm,” the bot can to come
back with a question, such as “What time would you like the alarm
set for?” by using a bit of simple Node.js code. This results in a mini
conversation, where the bot waits for an appropriate response while
maintaining the original intent much like a human would. Voilà! A
simple conversation is born.
Fortunately, our task here is made much simpler by using the prebuilt
entity, datetimeV2, which automatically parses complex expressions
like “Today at 2.15pm” or “23 minutes from now,” and converts them
into a date-time variable our bot can use. The only thing we have to
worry about is capturing the Set Alarm intent where the DateTime
intent is null.
389
//'Set Alarm' MUST HAVE a non-null dateTime to proceed continue
CHAPTER
// step 2 – BOTH next() and user’s dateTime response will end up here
function setAlarmStep1(session, results, next) {
if (results.response) {
//yes, user provided input. Try parsing to see if we can get dateTime
session.dialogData.setAlarm.time = builder.EntityRecognizer.
recognizeTime(results.response);
}
// if we got time, set the alarm
if (session.dialogData.setAlarm.time) {
//some alarm setting in the DB…
} else {
//the user failed to provide time even though we’ve asked nicely –
//it’s a cancel
session.dialogData.setAlarm = null;
session.send('Sorry, I could not understand what date and time you
wanted the alarm for… Set Alarm cancelled.');
}
}
]);
390
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
As you can see, the code is fairly straightforward, though it has a few
quirks specific to the Microsoft Bot Framework. For example, the whole
next() construct seems a bit old-school, like the old BASIC goto()
function. At least until you realize that next() essentially allows the bot
to execute the whole sequence of functions in a specific order, allowing
you to collect multiple parameters one or more at a time, over several
steps (provided you insert enough if/else statements to account for
each parameter to be entered in every possible order).
Step 1
• Try to collect RECIPIENT and MESSAGE.
• Does a valid MESSAGE exist?
• If not, ask “What do you want the message to say?”
• next();
Step 2
• Does a valid MESSAGE exist?
• If not, collect MESSAGE.
• Does a valid RECIPIENT exist?
• If not, ask “Who is the recipient?”
• next();
391
Step 3
CHAPTER
• next();
Step 4
• Do BOTH valid RECIPIENT and MESSAGE exist?
• YES: send the message.
• NO: respond “Sorry, can’t send message” and exit.
Thus, the conversation partner gets just one shot to answer the ques-
tion correctly. Rather than getting stuck in an infinite loop waiting
for a valid message recipient, both Siri and Google Assistant bots exit
after a single failed response to a follow-up question. While this can
be a bit inconvenient, and can occasionally lead to the conversation
partner having to repeat the entire query two or more times, especially
in loud environments, the upshot of this fast fail behavior is consis-
tency: projecting the feeling of control that the system provides for the
conversation partner to feel comfortable using and relying on the bot.
(See Mark Wyner’s “Control! Control! You Must Have Control!” sidebar
in the next section for more on the importance of feeling in control.)
392
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
393
buildSpeechletResponseWithDirectiveNoIntent());
CHAPTER
} else {
console.log("in completed");
console.log("current request: "+JSON.stringify(request));
console.log("returning: "+ JSON.stringify(request.intent));
8
Simply put, the Alexa dialog construct contains all of the required
next() code steps in a single configurable function. You just need to
specify the required entities the Alexa Skill bot needs to collect, and the
dialog code takes care of the rest. One important consideration is that
the Microsoft Bot Framework’s next() construct can be used to spec-
ify the exact order in which the required entities are collected, whereas
Alexa’s dialog simply collects the required entities in any order the user
chooses to provide them.
For most applications, the order does not matter, so the dialog provides
a convenient shortcut. Having less to code to worry out is nice, as it
leaves time for you to focus on providing alternative conversation
394
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
prompts the bot can pick from at random. This provides variety in the
conversational interaction and helps maintain the illusion of having a
real conversation with the bot.
For example, in the Send Message intent, if the message body was not
provided, the bot can say “What?” (as in “Was that a message? I didn’t
quite understand what you wanted to say”) and “Which Mike? Smith
or Baker?” (as in “Do you want to send this message to Mike Smith or
Mike Baker?”) More sophisticated context-sensitive responses will
help our personal assistant AIs appear more human by retaining the
context of the conversation. Naturally, this kind of smart response
requires you to write some custom response code using the waterfall
construct, and can’t be implemented using a simple dialog, which just
picks responses at random.
The exact opposite holds true for deep phone system trees, where the
conversational partner spends a fair bit of time entering the context,
and so will get infuriated if your bot exits without trying to get the
account number at least a couple of times.
395
In the next section, we’ll review phone trees and some ways companies
CHAPTER
are bringing this traditionally dark side of bots into the light. We are
far from done with the conversational context topic – it is pivotal to
the conversational UI experience, and is covered in more detail in the
8
“Many people do not take into account the emotional state of the
customer. When you call someone for customer service, you’ve got
a problem and you’re probably in a bad mood. You hear someone
telling you your call is so important that we won’t let you talk to a
human. Then they slap people with too many options, and eventu-
ally, you’re in a fight with the system. When you do get a customer
representative, you’re loaded for bear.”
396
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Today, most customers use a phone call as the last resort. That usually
means self-service via website has already failed, and the customer is
well and truly stuck. For a bot, this presents a unique problem, because
bots are generally good at determining specific well-defined intents,
preferably delivered as simple requests in a single sentence.
10 Quoted in “Far From Always Being Right, the Customer Is on Hold” by Alina
Tugend, New York Times, 2008 (http://smashed.by/24shortcuts).
11 Available at Project Gutenberg (http://smashed.by/gutenberg).
397
3. Don’t Overwhelm Customers with Options
CHAPTER
Providing a lot of options will not help a customer select the right one. It’s
just too much information! Picking the right option from the list requires
8
Just to be clear: the bot UX itself is not necessarily an issue, as long as the
customer feels in control; that is, they can escape the automation and just
speak to a human.
398
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
One of the most hated phrases for a person on hold to hear is “Your call
is very important to us.” It’s especially irritating when the voice comes
overloaded with emotion, as in “Your call is so, so very important to
us.” (So important in fact, that we can’t possibly spare a human being
who can pick up the phone…) The same goes for any kind of wordi-
ness or circumlocution, or overall cuteness. This is not the time to get
inventive! Stay as conversational as possible, but try to be brief and to
the point. As mentioned earlier, don’t pretend that your bot is more
capable than a trained dog would be. You want your customer to be
pleasantly surprised, not bitterly disappointed.
399
Control! Control! You Must Learn Control!
ASIDE — CHAPTER
And when we don’t have control, things get scary and uncomfortable. So
control is a key element of trust when humans interact with a machine.
“Many BrainPal users find it useful to give their BrainPal a name oth-
er than BrainPal. Would you like to name your BrainPal at this time?”
“Yes,” I said.
“Please speak the name you would like to give your BrainPal.”
“Asshole,” I said.
“You have selected ‘Asshole,’” the BrainPal wrote, and to its credit it
spelled the word correctly. “Be aware that many recruits have select-
ed this name for their BrainPal. Would you like to chose a different
name?”
Aside: Control! Control! You Must Learn Control! by Mark Wyner
“No,” I said, and was proud that so many of my fellow recruits also felt
this way about their BrainPal.
“Your BrainPal is now Asshole,” the BrainPal wrote. “Now you must
choose an access phrase to activate Asshole. Please say your activation
phrase now.”
“Hey, Asshole,” I said.
“You have chosen ‘Hey, Asshole.’ Please say it again to confirm.” I did. Then
it asked me to choose a deactivation phrase. I chose (of course) “Go away,
Asshole.”
“Would you like Asshole to refer to itself in the first person?”
“Absolutely,” I said.
“I am Asshole.”
“Of course you are.”
12 See Mark’s complete talk: “The Voice of UX: How Speech Technologies Will
Change the UX Landscape” (http://smashed.by/voiceofux)
The New Hope
CHAPTER
experience has much improved over the years. I’m a long-time United
customer and I’d recently booked a ticket online to New Zealand, but
without using my mileage account. I called United to see if I could
exchange the ticket and book it using miles instead of dollars. This is a
rather complex use case, one that the airline does not often see. What I
wanted to do was get to a human agent as quickly as possible, explain
the situation and see if a live person could help me out. My dialog with
the United bot went something like this:
Bot: Thank you for calling United Airlines. We looked up the mobile
number you are calling from. Is this first name spelled G-R-E-G?
Answer yes or no.
Me: Yes.
Bot: You can say Check-in, Book a flight, MileagePlus, Other
Me: Agent.
Bot: I think you are asking to be transferred to an agent. [Breathy,
emotional voice] If you answer just a few questions, I can get you to
the right representative. What area are you looking for help with?
You can say Check-in…
Me: MileagePlus.
Bot: Here’s a summary of your MileagePlus Account.
Me: Representative
Bot: All right, I am going to connect you with a representative.
One last question: do you need help with your reservation or
MileagePlus account?
402
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Me: Reservation.
Bot: Thank you. Please hold for a representative.
13 Quoted in “This Chatbot Is Trying Hard To Look And Feel Like Us” by Sean
Captain, Fast Company, November 15, 2017 (http://smashed.by/chatbotlook).
403
You can, of course, give your bot a sparkling personality, but this is not
CHAPTER
necessary (and most of the time just plain detrimental to the overall user
experience). It’s OK to be straightforward, businesslike, and to the point.
Most bot tasks will be mundane and not particularly exciting. And you
8
Do: Thank you for calling ACME Widgets. How can I help you?
Don’t: We are so glad you called! Say: “Let me know about your
awesome widgets” to learn about the amazing widgets we make.
Say: “I need help with my widget” to get help with your widget.
But please don’t say “I want to speak to a human,” because you
will just upset me. And if I cry, my joints will get rusty.
I’m not saying don’t provide instructions – just don’t volunteer them.
Wait for the customer to request help with the bot or specific task, then
unleash all of the appropriate “You can say” constructs in all their glory:
14 http://smashed.by/marvin
404
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
If you need answers other than yes or no, take special care how you
ask your questions. If it’s even remotely possible to answer your ques-
tion with a yes, chances are someone, somewhere will do so. Just try to
make that impossible. Or at least as impossible as possible.
Do: I can tell you about coffee or beer. Which would you like?
Don’t: Would you like coffee or beer?
Don’t: Which would you like? Coffee or beer?
405
Here are three steps to replacing a widget. [half-second pause]
CHAPTER
• First, turn off power. This is very important – do not skip this
step. [half-second pause]
8
• Now that the power is off, remove the cover and unscrew the
widget counter-clockwise from its housing. Discard the old
widget. [half-second pause]
Most people are busy and distracted. It’s best not to lay too many of your
options on their already troubled mind. The best practice is to list no
more than three options at a time, and then ask if the customer is ready
to hear more of your bot’s brilliance. If your list still sounds like a run-on
sentence, try inserting longer pauses between choices. Amazon recom-
mends 350ms for simple choices, and 400ms for choices requiring more
consideration, such as deciding what kind of cheese you want with that
glass of Château Pétrus.15
When listing items, you can use Speech Synthesis Markup Language
(SSML) to insert inflection and emotion, strategically lengthen the
pauses, and add many other minor but useful variations into your bot’s
voice. SSML is also super fun to play with on your Alexa, as you can try
out different SSML tags and listen to how they sound right on the Alexa
Skill dashboard.
406
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
In Figure 10 you can see Alexa cast in the role of Agent Smith in that
famous movie quote from The Matrix:16
16 http://smashed.by/matrixscene
17 http://smashed.by/speechmarkup
407
4. REMEMBER TO DESIGN A BOT, NOT A PARROT.
CHAPTER
dred times already, chances are that this time, things will surely be
different. To maintain the illusion of life in your bot, to at least the
politician-level, use a variety of prompts. This is easy to do in Alexa,
but takes a bit more effort in custom stand-alone bots. Either way, it’s
quality time, well spent.
Actually that last one is a “Don’t”: just a tad too much personality. Just
checking if paying attention, you are!
18 See my article “Starting from Zero: Winning Strategies for No Search Results Pages”
for my first thoughts on this idea back in 2009: http://smashed.by/noresultspages
408
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
First, be sure to handle the pauses. If your bot doesn’t get what it needs
to move forward, or the customer pauses longer than 30 seconds, the
best practice is to ask again (also called reprompting) using a slightly
different or lengthier explanation:
Bot: Thank you for calling ACME Widgets. How can I help you?
Customer: [Silence]
Bot: [After 30 seconds] I can find the widget you are looking for,
provide installation instructions, summarize your Widget Plus
account and much more. What can I help you find?
Bot: Thank you for calling ACME Widgets. How can I help you?
Victim: I want a beer.
Bot: [checking there is no “beer” widget type in the list] I didn't
quite understand that. What widget were you interested in?
409
While it’s OK to pretend to be an obtuse aunt, never, ever pretend to be
CHAPTER
hard of hearing – people will just yell the same answer, ONLY LOUDER!
If you detect what appear to be nonsense words like these, try suggest-
ing a different way to search:
410
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
19 http://smashed.by/alexagolf
411
Alex continued:
CHAPTER
“FULL STOP. Think about that sentence. ‘When is golf?’ […] Human
context trumps all. [A bot] Would need to understand whether it’s a
8
412
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
For example, if Friend’s Dad was talking to Timmy about when his golf
tournament takes place, or if he mentioned being excited about seeing
Jordan Spieth play, we could expect that Alexa would be able to under-
stand this and extract the appropriate context. Of course, this solution
raises serious privacy concerns (not to mention the problem of trying
to correctly parse the name Spieth!)
413
Bot: I can’t find “Her” in your contacts.
CHAPTER
To change WTF to FTW, you need to retain more of the context from
one query to the next. A typical human would do so naturally, while
our bot may need a bit more work. Doing so is not that much more
complex than the interaction we’ve reviewed previously: simply have
GUPPI treat the entire multistep interaction as one continual dialog
construct. From the first query, the bot can pull out the intent and set
the person attribute:
Under the new and improved scenario, the person attribute should
remain set until some kind of time-out interval (say, 10 minutes or an
hour, as appropriate), or until it is reset by the customer. In this way,
the new FTW scenario could go like this:
414
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
These are fairly simple hacks, achievable with just a bit more work
using the current level of technology. When designing the interaction,
consider the larger context of the customer experience. Ask yourself:
would remembering any aspect of the current task help the person in
the next step(s)? If the answer is yes, and you have the budget for it, go
ahead and code the interaction to take full advantage of everything the
bot already knows, starting with the current (or immediately preced-
ing) intents and entities.
415
Often, the biggest user experience challenge comes not from the bot
CHAPTER
itself, but in the form of integration the bot has with the platform on
which it is deployed. As the next two examples will demonstrate, the
level of platform integration (and the resulting convenience or lack
8
thereof) will likely determine the level of adoption and overall success
of your bot.
Siri can be activated by pressing and holding the Home button on the
iPhone. The typical Siri experience involves the following six steps:
416
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
As you can see, instead of six Siri steps we now have to deal with ten,
almost double the number! Now imagine trying each of these flows
while driving. Siri makes it possible to perform this sequence at least
somewhat safely, without requiring you to look at the screen even once.
However, Google Assistant forces you to look at and tap the screen
multiple times (including the completely unnecessary and irritating
“Which number?” interaction, which occurs whenever the user has
multiple numbers for the same contact) creating instead a critical
fail. A golden opportunity to create a winning bot interaction, utterly
destroyed by lack of UX fit and finish: the integration between the bot
and the platform that takes into account your context.
417
Just Say Hello: Wearable Interactions Iron
CHAPTER
Man-Style
8
Our next integration example comes from the wearable world, again
in a driving context. As shown in Figure 11, in order to answer the
phone, Apple Watch forces the wearer to hit one of two tiny round
buttons (Answer and Hang up) with a precisely executed tap from the
finger of the opposite hand. This action is both challenging and dan-
gerous to do while driving, because it’s necessary to look down at the
tiny screen to both see who is calling, and to aim the finger precisely at
a tiny round button.
20 See “Fitts's Law: The Importance of Size and Distance in UI Design” from
Interaction Design Foundation, 2016 (http://smashed.by/fittslaw)
21 Presentation by Josh Clark, “Buttons are a Hack: The New Rules of Designing for
Touch” given at BD Conference, September 2011 (http://smashed.by/buttonshack).
418
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Figure 11: Answering the phone in the car: Apple Watch current use case vs. proposed “Iron Man”
experience.
419
Using this new UI, our “Iron Man” version of the experience would
CHAPTER
Siri in the watch: [vibrate] Incoming phone call from Pepper Potts.
8
That’s it!
If Tony does not answer right away, Siri can gently remind him of the
possible range of actions:
Siri in the watch: [after 3–5 second pause] Pepper Potts is calling. To
answer just say “Hello.” You can also say “Hang up,” “Text,” or “Text ETA.”
Tony: Tell her I’ll call her back. Can’t you see I’m busy defying the
laws of physics flying this thing!
22 http://smashed.by/1usdwatch
23 http://smashed.by/1usdprototype
420
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Although most of our bears (ahem, bots) are far from dancing at the
level of Mikhail Baryshnikov, conversational UIs offer genuinely
exciting possibilities and many unique and immediately useful
applications. Using the material in this chapter, you should be able to
build your own standalone bot using Microsoft Bot Framework and
LUIS, or as an Amazon Alexa Skill, while avoiding many of the exist-
ing challenges and pitfalls of conversational UIs. I’d like to invite you
to take the next step in creating the bot equivalent not of a dancing
bear perhaps, but at least that of a dancing dog who can also fetch the
owner’s slippers.
24 The Inmates Are Running the Asylum: Why High Tech Products Drive Us Crazy and How To
Restore the Sanity by Alan Cooper (1999).
421
Note: I tried to choose just a few ideas that showcase the wealth
CHAPTER
HANDS-FREE CONTROL
One of the most exciting and powerful ways to use conversational UI
is to offer hands-free control of common and tedious actions. While it
only loosely qualifies as a conversational UI, Yondu Udonta’s self-pro-
pelled yaka arrow25 in the 2014 Marvel Studios film Guardians of the
Galaxy is a great (if deadly) demonstration of this idea.
It’s too bad we are fresh out of yaka. (And besides, this author’s talent
for whistling is far eclipsed by his many other talents, including his
truly impressive underwater basket-weaving skills.) The good news
is that you don’t need to be a native Centaurian like Yondu to take full
advantage of similar, though far less lethal technologies. One idea that
is gaining traction is to use voice recognition to launch macros and
25 http://smashed.by/yakaarrow
26 http://smashed.by/whistlearrow
422
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
MY VOICE IS MY PASSPORT
Security is a top concern for many conversational interfaces. Speaking
a password aloud every time you need to check your bank account bal-
ance is rarely a great idea: the sound can be intercepted and copied. An
interesting work-around is to use a personal assistant to do two things
at the same time: perform the desired action, and verify security and
identity. This can be done via a special pre-recorded pass-phrase, or
even through certain aspects of the voice that are difficult to duplicate,
such as accent, cadence, timbre, and word choice. It’s even possible to
continuously verify a speaker’s identity when interacting with a bot.
423
Thus, the voice fingerprint provides an additional level of security,
CHAPTER
AI TURK
Many bot applications have not yet found widespread adoption
beyond simple (but very useful) digital assistants doing basic tasks
such as texting. In an effort to accelerate the addition of human
smarts to the bot experience, many companies began to create bot-hu-
man hybrids, so-called Mechanical Turk models. This is a bot model
described in Neal Stephenson’s brilliant book The Diamond Age, where
a remote operator helps a young woman come into her own by acting
through and with the help of the bot housed in the “Illustrated Primer,”
a device not unlike an iPad. Back in 2010, when iPads first hit the mar-
ket, I spoke of the tantalizing possibilities of human-augmented “style
assistants,” who would use the iPad’s on-board camera to help select
clothes and furniture.27
27 See “Design Caffeine for Search and Browse UI”, Information Architecture Sum-
mit, 2010 (http://smashed.by/searchui).
424
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
The huge promise of this technology is that we can use the entity
guessing threshold to spot difficult or ambiguous tasks and kick them
off to a human, while handling the more obvious queries via digital
assistant. In turn, the human Mechanical Turk answers become inputs
for the bot’s logic model, which will keep getting ever smarter, continu-
ally reducing – and eventually removing entirely – the need for human
intervention. This is a process not unlike what happens now with many
customer service bots, as we’ll discuss in the next section.
28 “This Chatbot Is Trying Hard To Look And Feel Like Us” by Sean Captain, Fast
Company, November 15, 2017 (http://smashed.by/chatbotlook)
425
CHAPTER
8
AVA is a pure AI, so she has the advantage of never getting angry or
upset, no matter how rude and uncomplimentary the customers are
of Autodesk’s software. Instead, AVA is supposed to offer a very human
level of empathy, helped along by her very expressive face. Whether
this investment will pay off, AVA is certainly an impressive effort. This
is also likely the area where we should expect tremendous advances,
fueled by widespread use of special effects technologies used in mov-
ies and video games.
CITIZEN BOT
If AVA skirts the edge of the uncanny valley29 just south of our comfort
zone, our next subject, Sophia, trashes the uncanny valley divide com-
pletely. Meet the newest robot citizen of Saudi Arabia, shown in Figure
13 on the next page.
29 http://smashed.by/uncannyvalley
426
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
Whether you love her or feel genuinely creeped out, it’s hard to ignore
the fact that Sophia is incredibly lifelike, and in fact can be virtually
indistinguishable from a human, at least in a dark room during a
casual conversation. Although Sophia is currently one of a kind, it’s
hard to believe that it will be long before lifelike robots will be used for
all kinds of applications traditionally reserved for humans.
30 http://smashed.by/alexacookbook
427
Summary
CHAPTER
In this chapter, we’ve reviewed the key features of major bot frameworks:
Microsoft Bot Framework with LUIS and Cortana, Amazon Alexa, Google
8
Assistant, and IBM Watson. We dug deep into building a demo bot with
LUIS, setting up intents, extracting entities and coding waterfall dialogs.
We discussed how to name your bot, and why it is important to choose a
name that elicits the right expectations of functionality and interactivity,
so that the user would be impressed when the bot performs simple tasks,
and not expect complex logic, awareness, or emotional intimacy.
428
UX Design Of Chatbots And Virtual Assistants by Greg Nudelman
I hope you were able to follow along with the examples, and now have
your very own fully-functional conversation bot, which incorporates
some of the UX best practices we discussed. I also hope that you will
take the requisite next steps to deploy your bot on your channel of
choice and try it out with your customers. Please let me know your
thoughts by posting in the comments or by contacting me at
http://designcaffeine.com — thanks for coming on this journey!
429
chapter 9
Work is being done to integrate these technologies into the web plat-
form, but we can build these technologies into the web today.
The goals of this chapter are to explore how it works and help show
some patterns designers and developers can explore to start using
them today.
VR experiences are
usually accomplished
through a combination
of some sort of headset
for viewing the virtual
world, and a controller
for interacting with it.
Samsung Gear VR virtual reality headset.
432
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
There are also web browsers designed to be used in VR, such as Sam-
sung Internet for Gear VR, which allow you to browse the web while
wearing a Gear VR.
Some browsers offer the full browsing experience of the 2D web. Oth-
ers only allow you to access web content specifically designed to work
with VR headsets.
433
We can use the <video> element to play immersive videos, optimized
CHAPTER
This video initially looks like a normal element on the page. When the
9
user presses the fullscreen button, they are placed immersively into
the video. This is great for 3D immersive movies. The element supports
many popular formats for immersive videos:
We can also use JavaScript to set the background of the browsing envi-
ronment. As seen in the screenshot above, the default background is a
beach scene at night.
window.SamsungChangeSky({
sphere: 'http://site.com/blue-sky.jpg'
});
There is another API which has been widely adopted to enable all brows-
ers to display any content in XR hardware: the WebXR Device API. These
APIs allow you to build general-purpose VR experiences on the web.
434
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
One of the really good things about VR on the web is that it has a great
community developing tools dedicated to reducing the barriers devel-
opers face when building VR for the first time.
As a result, the learning curve for VR on the web is one where it is rela-
tively quick to get started, but challenging to release a polished product
that works well on all platforms.
Fortunately there are many other benefits that come from working on
the web which more than make up for these challenges.
The most powerful feature by far is that users have instant access to
your content. If they are using a phone, they can look around the scene
on their screen by turning it around.
This can be used to sell users on your content and encourage them to
try it out when it is inconvenient for them to use a headset.
Instant accessibility means people can more easily try your scene.
They can see what is going on even if they can’t use it to its full extent
with a headset or controller.
Once users have enjoyed your scene, they can copy the URL and share
it. The web helps content go viral more easily. URLs are really powerful
because they are easily shared, whether they’re posted on Twitter or
printed in a book.
435
For scenes where users may not have the patience to download a whole
CHAPTER
dow gives users a great taster and encourages them to try it out further
with a headset.
It used to be known as the WebVR API, but was renamed because its
scope has grown to encompass augmented reality and mixed reality
headsets.
2. If they are on a phone, the user can look around this scene by ro-
tating their phone – like a magic window into a different reality.
436
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
3. The user sees a button which indicates that this page is XR-capable.
They press this button using whatever the headset provides for
pointing: mouse, keyboard, touch, VR controller, finger gesture.
4. The scene is now ready to be viewed:
5. In desktop-connected headsets, like the HTC Vive, the scene is
shown in 3D on the connected VR headset. The headset is ready
to put on.
The API tries to handle a lot for you, such as the distortion required to
make a scene look normal (that is, undistorted) when viewed through
the strong lenses in the VR headset. It even tries to compensate for
missed frames to give the user a more comfortable experience.
There is a big downside, however: it requires WebGL!
437
The consequence is that it will be very difficult and expensive to use
CHAPTER
HTML and CSS to build parts of your VR scene. In turn, this makes it
difficult to build nice user interfaces. We instead have to think about
telling stories with 3D graphics, sounds, and lighting rather than text
9
Although the standards for the WebXR Device API are not yet finalized,
there are working implementations in many popular browsers. There
is also an excellent polyfill for the cases when the browser does not yet
support the API.
1 http://smashed.by/webxr
438
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
WebGL is a very verbose language to work with. It also has a very lim-
ited concept of 3D. The primary purpose of WebGL is to draw triangles
very fast – everything beyond that, you have to implement yourself.
Fortunately, there are many popular libraries and tools that work with
WebGL. Here are a few of the open-source ones:
439
The power of the web is that it works across a wide range of
CHAPTER
On the web your content is not restricted to any single platform and
therefore should work on each platform to a certain extent.
440
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
Once you have worked out what you want people to do in your scene, it
is time to plot out where stuff should go. There are a few ways to do this.
Start thinking about what you want to build and how VR enhances it.
Think of how you can make the most of VR and take advantage of hav-
ing a full sphere of space instead of being limited to a small window.
441
Also think about what you want the user to do. What do you want the
CHAPTER
various points of interest for the user without making the scene so
cluttered that it is hard to navigate. New users may get frustrated as
they try to navigate a VR environment.
It is also important to
balance the placement of
objects. If objects are placed
too far around the user’s
peripheral vision, they may
go unnoticed and the user
may be unable to access them if they have reduced motion. Requiring
too much head movement can cause neck aches and be uncomfortable.
Conversely, placing all the objects in the center of the user’s vision may
be convenient for them to use, but not take advantage of the VR experi-
ence. They might as well just be using a normal laptop screen.
442
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
CUES FOR 3D
Many of the features which make your scene feel and look good are
related to cues the brain uses for depth.
Even worse are conflicting visual cues, which will make the user
uncomfortable or give them headache.
443
2. Occlusion: when one object
CHAPTER
3. Atmospheric perspective:
as objects recede towards
the horizon they increa-
singly take on the color of
the atmosphere. Often this
means turning on a fog
effect so that objects further
away lose some of their
saturation.
4. Shading: approximation of
shadows and highlights cau-
sed by light falling on an ob-
ject. This allows us to see the
volume of an object rather
than just its silhouette.
444
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
445
If you build with these things in mind, it will give the user a clear idea of
CHAPTER
what is happening, and the size and distance of the objects in your scene.
9
The result of lower resolution is that scenes with a lot of detail and
high-contrast textures look very visually noisy, making it hard to deter-
mine what is important. Combine this with the inability to use lens
blur in VR (everything is in focus even when not being directly looked
at), and the background becomes visually distracting.
2 http://smashed.by/googleblocks
446
One place where picking a high-quality texture can work wonders is
the skybox. It can very quickly and inexpensively add huge amounts of
feeling to any scene.
It tells the user whether it is day or night, what the weather is like, and
even whether they are on Earth or some alien planet. The best skybox I
have ever seen is on the Oasis map in Overwatch.
Here, the colors inspire the lighting for the rest of the scene. The
clouds are cartoonishly beautiful and the stars at sunset feel magical.
The skybox tells such a story with just a single high quality texture.
If it is used as a basis to inspire the scene’s lighting, it will integrate
beautifully with the rest of the scene.
You can buy equirectangular sky images online from various stores.
They can be painted by hand, but this can become difficult: if you use
an equirectangular image it will need to have distortion applied to it.
If you want to try out a very simple skybox, a vertical gradient will
work well for setting the mood, and it can be made very quickly in an
image editor. Here I made a 512×512 gradient from one of the gradient
tool presets in GIMP. The horizon is halfway down the image.
A simple skybox made with the gradient tool.
The same principle applies to the floor texture. A floor with a simple
repeating pattern will give the user a clear idea of the topology of
the ground around them and give them a good sense of the scale of
distant objects.
A simple, subtle tile works wonders for this, but be careful: an overly
detailed floor will pull the user’s eye away from the content and
towards the floor. It is good to find a balance that is useful and attrac-
tive without becoming distracting.
When you have a very large floor that you can see for long distances,
a single repeating tile may end up looking a little strange. A way to
get around this is to vary your floor a little bit. For example, instead
of using a single grass texture repeating many times in all directions,
placing grass decals or clumps of grass
here and there over a single color of
floor will look much less weird and
distracting.
449
Certain problems come up when using absolute black (#000) or white
CHAPTER
This is rarely encountered in real life. Typically, lights are a little bit
warmer (more yellow/orange) or a little bit cooler (more purple/blue).
This causes our eyes to see the shadows as the opposite color. So a
warm light casts a cool shadow.
450
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
If you pick the light-source color based on colors in your skybox, it will
integrate beautifully into your scene and feel very atmospheric.
The other atmospheric effect you can use is “fog.” This will change the
color of the objects to match the fog color as they recede further from
the user’s perspective. The skybox is usually made to be unaffected by
the fog. A fog color should be chosen based on the skybox’s horizon to
simulate atmospheric perspective for distant objects.
You can also create a cool silhouette effect against a sunset skybox by
using a color which is darker or bluer.
Of course, the colors don’t have to reflect reality. Using unusual colors
for the sky or sun is a great way to give an otherworldly feeling.
Interactivity
Once you have built your scene, you will want the user to be able to
engage with your content.
To receive input from the user, you can get the position and rotation
information from the attached controllers. This is a key time to think
about progressive enhancement: not all users will have the same con-
troller configuration.
451
There are three controller configurations a user typically may have:
CHAPTER
The most basic form of interaction which works in all cases is to allow
the user to “click” on elements by pointing at them. To do this you “ray-
cast” to your interactive objects, where the ray is fired either from the
controller(s) or from the center point of the user’s camera.
It is then good to display a cursor at the point where the ray hits the
scene so the user knows what is currently “under the pointer.” Objects
which are interactive should visually (and audibly) respond to this
cursor, so that when it is under the cursor the user knows that they can
perform some kind of interaction with it.
When the user clicks it, there should be some kind of response, such
as changing size or making a noise. All these animations should be
tweened in an aesthetically pleasing way, according to the principles of
animation. They should wind up, follow through and squash or stretch
to feel engaging. It is more important to give the user information
about how items react than to make something physically realistic.
452
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
Two main types of interaction I see used are scripted interactions and
physics-based interactions.
Interactions like these are very useful for exploring a world, and the
state is much easier to share with other users. This is great for explor-
ing shared worlds together. VR has enormous potential for storytelling,
especially when experienced with someone else.
453
Walking Around
CHAPTER
At the same time, the scenes we build are often beautiful expansive areas,
much larger than the real world we are standing in with our headsets.
How do you explore a large virtual world when you can’t walk?
There are hardware solutions to this issue, but they are usually expen-
sive and unwieldy. The most common solutions use the existing
controllers and headsets to give the user a way to move around. There
are a few different ways to move in VR that tend to be reused, so it is
an area ripe for innovation.
454
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
Flying lets the user drift around the scene. It is a tricky thing to do well
because the user isn’t moving in real life. Their visual impression of
accelerating or decelerating isn’t matched by a physical sensation, and
as a consequence can make them feel ill.
455
Occulus has done some research on comfortable locomotion,3 which
CHAPTER
Another thing to keep in mind is that some users are restricted in how
they can turn: they may be sat down or lying in a bed. If you provide a
method like using a controller button to rotate the user, it will be more
accessible. Google has done some research 4 which found that a smooth
rotatation left or right will make some people motion sick. Instead,
jumps of 10–20 degrees will feel more comfortable.
Social VR
At first thought, VR seems like a lonely experience – but it can bring
people together too. If you can share the state of a virtual world
between two or more users, then VR starts to become social.
3 http://smashed.by/locomotion
4 http://smashed.by/locomotionvr
456
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
Audio
While audio is not my area of expertise, one thing I would like to make
clear is that audio is an integral part of the experience. It can provide
as many cues to the user for interactivity and setting the mood as the
visuals do.
Adding audio will have a huge impact on your scene, but I often
see it forgotten about.
There are libraries to allow you to produce 3D audio on the web and to
place sounds around a user. This also gives them a cue that they should
look around to where the sound is coming from.
I once saw a hackathon entry in which you had to find a hidden object
by 3D sound alone. It was very engaging and a great demonstration of
the power of 3D audio.
457
Optimizing for Performance
CHAPTER
Mobile first is still great advice. Here is how I test as I build for WebVR:
Users have high standards for the web when it comes to getting
started. If they have to wait more than a few seconds they will leave
the page. This often means having to show your scene before every
item in it is fully loaded, populating the scene as items load.
458
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
With browser caching and tools like service workers, assets can be
reused between pages, helping to reduce download sizes for scenes
with shared assets.
If you come from the web world, 70 Mb may seem very large. The
average web page is 2.5 Mb. But if you are coming from the 3D graph-
ics world, where installations can run up to hundreds of megabytes
on mobile games, this is quite restrictive. It is important to save space
wherever we can.
5 http://smashed.by/aframeperf
459
TIPS FOR INCREASING FRAME RATE
CHAPTER
tracking the head. If you skip too many frames, you can make the user
feel ill.
When drawing the scene, the 3D software will draw each object sep-
arately in what is known as a draw call. Lots of draw calls will be very
difficult render, even if each draw call is simple.
First, measure how difficult it is for the computer to draw each frame.
This functionality is built into many 3D graphics libraries to tell you
the difficulty of drawing the previous frame.
460
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
Adding the stats attribute to the <a-scene> HTML element lets you see how much the com-
puter has to work to draw the frames.
The main goal is to limit the number of draw calls. Notice that the
scene in the image has almost 900,000 faces because of the rain.
However, because it is rendered in very few draw calls (31), it is quite
efficient and maintains a good frame rate on my low-powered netbook.
The simplest trick to reduce draw calls is to hide any objects not visible
to the user so they don’t get drawn needlessly.
6 http://smashed.by/perfopt
461
Another way to reduce the number of draw calls is to merge geome-
CHAPTER
try wherever you can. This means that meshes which share the same
material have their geometry combined. They can no longer be moved
independently of each other since they are effectively one mesh. In
9
462
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
We also have to do a new bake if we change the scene in any way. This
is the downside of light and shadow baking. The compromise is to
bake all the shadows of objects and lights in the scene which do not
move, and do real-time shadows for only the few objects which move.
They usually blend together well and look really good.
463
We can simplify this by limiting the number of objects in the scene and
CHAPTER
removing objects from the physics calculations as soon as they are not in
view or not being used.
9
We can also calculate physics only between objects likely to collide. Two
boxes in different rooms don’t need such calculations because they won’t
intersect.
Physics gets even more expensive for complex shapes. Shapes like spheres
and cubes can have their collision calculations reduced to simpler maths.
But if you have an arbitrary 1,000 polygon shape, the maths for whether it
intersects another object gets very computationally expensive.
We have some tricks for solving these problems, though. For complex poly-
gons we can use simpler shapes for simulating the physics.
464
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
The high-poly models have no physics done to them, they are just
placed to be in the same as the location as the visually hidden com-
pound objects.
7 http://smashed.by/navmesh
465
What I Wish I Had Known When I Began
CHAPTER
Here is a quick list of tips I wish I had known when I started making
demos:
466
Crafting Experiences for AR/VR/XR by Ada Rose Cannon
Have fun and show off what you build. I always love to see people’s
first VR experiences.
467
chapter 10
Bringing Personality
Back to the Web
Vitaly Friedman
Bringing Personality Back
CHAPTER
to the Web
10
by Vitaly Friedman
It’s not that somebody imposed these rules or limitations on our creative
output; usually they originate from good motives and the best inten-
tions. After all, one of the main tenets of web design has always been
creating a subtle, almost invisible and functional interface – an interface
that doesn’t make users think, where less is more, and form follows
function, where simplicity prevails – an interface where everything
feels just right. Yet when everything is structured in a predictable way,
nothing really stands out. Given how remarkably similar names, logos,
icons, typography, layouts, and even shades of gradients on call-to-action
470
Bringing Personality Back to the Web by Vitaly Friedman
buttons often are, it’s not surprising our users find it difficult to distin-
guish between brands, products, and services these days.
Very few people miss the golden times of the infamous Flash, with
its strikingly experimental layouts and obscure mystery-meat navi-
gation. Admittedly, the focus has shifted from creating an experience
to merely providing content in a structured form. Yet unlike in those
good ol’ days when we talked about how wonderful or horrible web-
sites were, today most experiences are almost invisible, making it
exceptionally difficult to connect emotionally with them.
471
Customers are used to and expect decent experiences. They aren’t
CHAPTER
decisions to extremes, choosing the best candidates, but it’s not nec-
essarily true. According to well-known Herbert A. Simon’s satisficing
theory, we tend to prefer the first option that meets an acceptability
threshold, just because we don’t know if we can find a better option or
how much effort it would take. We rarely study the entire spectrum of
options in detail (and sometimes it’s nearly impossible), and as a result,
we satisfice with a candidate that meets our needs or seems to address
most needs.
It’s not just about price. It’s not just about features. It’s not just about
choosing the right placement of buttons, or the right shades of colors
in endless A/B tests. And it’s not about choosing a cute mascot illustra-
tion that shows up in email campaigns. In the end, it’s about creating
an experience that people can fall in love with, or connect deeply
with – an experience that, of course, drives the purpose of the site,
but also shows the human side of it, like the personality of the people
building it, their values and principles, their choices and priorities.
That means designing voice and tone, interface copy, and embracing
storytelling, authenticity, inclusivity, and respect; and all of that while
establishing a unique visual language supported by original layout
compositions and interaction patterns. Together with clear and honest
messaging, these create a unique signature, which, used consistently,
472
Bringing Personality Back to the Web by Vitaly Friedman
makes the product stand out from the rest. This task might sound as
daunting as months of marketing research, but it doesn’t necessarily
require an enormous amount of effort or resources.
In this chapter, we’ll look into a few practical techniques and strate-
gies that might help you find, form, and surface your personality effi-
ciently. By doing so, we’ll explore how doing so consistently could fit
into existing design workflows, along with plenty of examples to give
you a good start. But before we get there, we need to figure out how
omnipresent design patterns and best practices fit into the equation.
Those dead ends happen when we realize we aren’t really getting any-
where with the result exposed on our digital canvas. We’ve been there
many times, so we know how to explore uncharted territories and how
to maneuver the flanks, and so as we keep sculpting our ideas, we keep
making progress, slowly but steadily moving towards a tangible result.
Two steps forward, one step back, revisiting what we’ve done so far
and refining those precious pixels – based on… frankly, based on intu-
ition and random experiments. Eventually the back-and-forth brings
us to a calm, peaceful, and beautiful place – just where we think we’ve
found a solution – the solution.
473
We know, of course, that it’s unlikely it’s going to be the one, though,
CHAPTER
don’t we?
To avoid losing time, we rely on things that worked well in our pre-
vious projects – the off-canvas navigation, the accordion pattern,
rounded profile images, and the holy 12-column grid layout. It’s not for
lack of knowledge, skill, or enthusiasm that we fall back to all those
established practices – it’s just infinitely more difficult and time-con-
suming to come up with something different every single time. And
because we lack time, we use all those wonderful, tried-and-tested
design patterns – all of them tangible, viable solutions for a particular
kind of problem. Obviously, this process might be slightly different for
different people, but broken down into its essence, that’s what’s hap-
pening behind the scenes as we make progress in our designs.
474
Bringing Personality Back to the Web by Vitaly Friedman
while they were working well as standalone solutions, they just didn’t
work together as a whole. The building blocks of the system weren’t
sufficient to maintain and support the system. We had to redesign
what we’d built so far, and we had to introduce overarching connec-
tions between those components that would be defined through the
personality and voice and tone of the new identity.
It’s not that design patterns and best practices are necessarily evil,
though. They are merely a double-edged sword helping and troubling
the visual output. When applying them, we need to do so carefully
and thoughtfully. Whenever you consider resolving a problem with a
design pattern, it’s a good idea to ask yourself a few questions:
475
4. How does this component help us reach the overarching goal of
CHAPTER
the system?
5. How do we connect that component to the rest of the system –
10
Then, for every pattern, we explore its usability issues and problems,
resolve them, and then style and design the module in a way that
feels connected to everything else. That last step could be something
as simple as a consistently used transition, or a geometric pattern, or a
non-conventional position in the layout. Finally, once everything is in
place, we repackage the design pattern and add it to the library, ready to
be served for the rest of the system.
476
Bringing Personality Back to the Web by Vitaly Friedman
Humans can genuinely connect to brands they trust, but brands need
to earn that trust first. Obviously, it comes from reliable recommen-
dations and positive experiences. But as designers communicating on
477
behalf of companies, how do we efficiently elicit trust in people who
CHAPTER
aren’t yet aware of the brand? As it turns out, trust can also come from
the appearance of the brand, which can be influenced by its values,
beliefs, principles, and activities. It isn’t easy to fall in love with a com-
10
Ready? Chances are high that you’ve come up with common and pre-
dictable answers. Perhaps words such as “simple,” “clean,” “strong,”
“dynamic,” “flexible,” or “well-structured” have come to mind. Or maybe
“attentive to details,” “focused,” “user-centric,” and “quality-driven.”
Can you see a problem with these answers? These words describe our
intention rather than our personality. While the former is usually very
specific and stable, the latter is usually very fuzzy and ever-changing.
The qualities outlined above don’t provide a good answer to the ques-
tion, as they describe how we want to be perceived, but not necessarily
how we actually are. In fact, usually we don’t really know who we are
or how we are perceived outside of the comfortable company bubble
we find ourselves in.
478
Bringing Personality Back to the Web by Vitaly Friedman
Maybe it’s the fact that the company is actively contributing to pro
bono projects for non-profit organizations that make a real difference
in the world. Maybe because it supports schools and newcomers to
the industry by providing an annual scholarship. Or because it ties in
the profits with a fair salary bonus for all employees. Or just because it
allows you to play with the latest fancy technologies and crazy exper-
iments, and contribute to open-source in five percent of your working
time. The company doesn’t need huge ambitions, idealist goals, or a
fancy working environment to stand out.
479
Now, can you spot a similar thread among all of the statements above?
CHAPTER
Because they come from personal experiences, they seem much more
human and relatable than more general and abstract terms you might
come up with initially.
10
MailChimp’s interaction design just before and just after an email campaign is sent out.
480
Bringing Personality Back to the Web by Vitaly Friedman
Cats have become one of the key figures in Smashing Magazine’s branding. The
Smashing Cat reflects the character and attitude of people working behind the scenes.
481
Now, we never wanted our legacy to be cats, but it wasn’t really up to
CHAPTER
it. We didn’t even properly discuss it, as we didn’t know if we’d host
more conferences in the future anyway. This small decision put
something in motion that we couldn’t dismiss years later. Because
conferences turned out to become one of our central products, we’ve
been promoting them heavily in our mailings, announcements, release
posts, and social media messages.
482
Bringing Personality Back to the Web by Vitaly Friedman
What does it all mean for you? Ask questions to surface humane
qualities that lie in the very heart of the company first. This will give
you a foundation to build a visual language on – a language that would
translate your qualities to the interface design. Every company has a
unique signature in some way, and often it’s reflected through the peo-
ple working there. Ultimately, it’s just about finding the time and cour-
age to explore it – and to embrace the fact that our flaws and quirks are
a part of it as much as our big ambitions and good intentions are.
483
Espen Brunborg, a creative lead for a graphic design agency in Nor-
CHAPTER
In one of our conversations years back, Elliot Jay Stocks, who was
involved in the 2012 redesign of Smashing Magazine, briefly men-
tioned one fine detail of his design process that stayed with me for
quite some time. He said that a good design possesses one of two
qualities: it’s either absolutely perfect in every way, with perfect align-
ment, sizing, and hierarchy (which is usually quite hard to achieve), or
it’s deliberately imperfect in a few consistent ways (which is much easier
to achieve). According to Elliot, in a good design there shouldn’t be any-
thing in between. In other words, buttons should either be perfectly
aligned to the grid, or not aligned at all – offset by 20–30 pixels and
more. Being off just by a few pixels would feel wrong, while being off
by 20–30px looks deliberate, and hence less broken.
484
Bringing Personality Back to the Web by Vitaly Friedman
So what if, instead of chasing the perfect solution for every single com-
ponent, we ran and tested various expressions of our personalities? In
interface design, it would mean entirely different creative directions.
Perhaps a multicolumn layout with bold typography, against a geo-
metric layout with a single accent color? What if, rather than seeking
the perfect roundness of a button, you deliberately introduced slight
inconsistencies? A custom animation on one of the call-to-action but-
tons, or a dynamic placement of an image outside of the box in which
it’s usually supposed to be placed? Or perhaps rotating a subheading
by 90 degrees? The personality can be expressed in many entirely
different ways, so the task is to discover variations that are promising
enough for testing.
This approach won’t always win, but complemented with A/B tests,
it might bring you to new heights you wouldn’t be able to achieve
otherwise. Ultimately, we want customers to fall in love with their
experience and consequently the brand, to form a lasting bond. A
deliberately imperfect yet humane interface can help us get there. It
requires you to find just one distinguishable quality that nobody else
has, and boost it up.
485
Choose One Thing and Boost It Up
CHAPTER
When working with Dan Mall on the Smashing redesign, one interest-
ing detail Dan brought up at the very start of the project was the role of
the signature in the final outcome. According to Dan, choosing a few
distinct, competing expressions of the personality is often too much:
it’s enough to choose just one little detail and boost it up all the way. In
more practical terms, that means picking one pattern and using it con-
sistently, everywhere: on every page, and in every user interaction. How
do you find that sacred detail? You go back to the roots of the company.
486
Bringing Personality Back to the Web by Vitaly Friedman
Early design explorations with Andy Clarke used the tilting con-
sistently for every single visual element on the site. This signature
carried over to the final design as well. Today, all icons, author images,
conference flags, job board logos,
illustrations on product panels, and
book covers on product pages are all
consistently tilted. That tiny detail
doesn’t break the experience, yet it
lends a unique visual treatment to the
design that’s clearly distinguishable
from everything else as a result.
Early design explorations used the tilting element consistently for every visual element
on the site. This signature carried over to the final design as well.
487
At this point you might be slightly worried that you don’t really have
CHAPTER
the next sections, we’ll explore some examples and ideas you could use
for you own particular situation.
Probably the easiest way to come up with your own design signature is
by using custom illustrations designed specifically for the brand. Every
artist has their own unique style, and unlike stock images or stock pho-
tos that often almost enforce generic appearance into layouts, custom
illustrations give a brand a unique voice and tone. You don’t need to go
overboard and create dozens of illustrations; just a few would probably
do. Think about replacing all the stock photos you’ve purchased with
custom illustrations – this should give you a good enough baseline to
start with.
488
Bringing Personality Back to the Web by Vitaly Friedman
Atlassian uses a friendly illustrative style with color consistently used for buttons and
headings at every touchpoint of the experience.
Why are custom illustrations not enough to stand out? Because just like
many other attributes on the web, illustrative style also follows trends.
Compare Atlassian’s style to Slack’s visual language. Yes, the fine details
are different, but the pastel color combinations are similar. The illus-
trations from these different projects could happily coexist within one
single website, and many customers wouldn’t notice a difference.
The style of the illustrations on both the Atlassian and the Slack sites look fairly similar.
Both illustrations could happily coexist within one single website.
489
CHAPTER
10
3 https://bond.backerkit.com/
490
Bringing Personality Back to the Web by Vitaly Friedman
4 https://www.hioscar.com/
491
CHAPTER
10
5 https://marumarumarumori.jp/
492
Bringing Personality Back to the Web by Vitaly Friedman
Storytrail is a city guide with interactive maps and videos. Every city
has its own signature which is a wavy horizontal line outlining a city’s
most important landmark. The design uses this signature line for vari-
ous animations, transitions, and arrangement of items in the layout.
493
CHAPTER
10
6 http://smashed.by/haufeds
7 http://rc3.me/
494
Bringing Personality Back to the Web by Vitaly Friedman
Lynn Fisher8 also adds some randomness to her portfolio. The layout
changes completely between different breakpoints, creating a totally
different experience on mobile and desktop devices. Even the favicon
changes dynamically as well.
When considering the visual direction of a site, it’s a good idea to con-
sider custom illustration style, backgrounds, typography, and shapes.
Establish strong connections between all of these attributes by reusing
design decisions, such as the choice of colors and spacing. While doing
so, of course, it wouldn’t hurt avoiding predictable options used widely
everywhere else. One of the effective ways to achieve this is by keeping
tabs on ongoing design trends, then pick the most prevalent one and…
smash it to pieces.
8 https://lynnandtonic.com/
495
Pick a Trend and Smash It to Pieces
CHAPTER
It’s almost ironic that it has become trendy to dislike trends these
days, and for good reason: usually their primary purpose is visual
embellishment, rather than driving a designer’s intent, and often
they don’t add much to the experience beyond friction, confusion,
and fancy whistles and bells. No wonder then that designers have
started to fight back with “brutalist designs”9 – websites that aim to
exhibit the essence of a website in its unstructured form, exposing
the website’s functions to extremes.10
9 http://brutalistwebsites.com/
10 Brutalism in architecture is characterized by unconcerned but not intentionally
broken aesthetics. When applied to web design, it often goes along with deliber-
ately broken design conventions and guiding principles.
496
Bringing Personality Back to the Web by Vitaly Friedman
497
CHAPTER
10
Dropbox rebranding wasn’t received too well. The design is very bold, and very notice-
able. As such, Dropbox achieved its goal of being talked about in their redesign.
11 http://loflorecords.com/
498
Bringing Personality Back to the Web by Vitaly Friedman
12 https://waaark.com
499
the background and a story preview in the foreground. That’s enough
CHAPTER
Imagine for a second that you have to redesign your ongoing project,
10
but can’t use any basic shapes such as circles, rectangles, or triangles.
What would you choose? We all know there is an infinite amount of
options, but why is it then that so often we are constrained by highly
predictable and heavily used choices? What is neither a circle nor a
rectangle nor a triangle? Well, slanted or tilted elements aren’t. Neither
are letters and large typography. Nor are custom responsive illustra-
tions or iconography. Nor whitespace, audio, and video. Nor transi-
tions and animations. Nor pretty much any other shape created via a
polygon, with content embedded via SVG masks. TPS,13 a Russian real
estate agency, uses the shape of its properties for thumbnails, rather
than generic squares or circles. Every single property has its own
shape. The idea is used consistently for every single property.
13 http://www.tpsre.ru/
500
Bringing Personality Back to the Web by Vitaly Friedman
It’s not that all basic shapes should be ignored and dismissed from now
on, of course. Avoiding basic shapes deliberately is one of the very first
exercises we do when we try to come up with a slightly more original
art direction. Once you’ve come up with a decent idea without basic
shapes, you can start bringing them back sparingly when necessary.
Chances are high, however, that you might be able to get away without
them altogether.
14 http://smashed.by/districtox
501
Do Make People Think
CHAPTER
Why is it that when we are puzzling our way around a foreign city and
a souvenir shop owner is desperately trying to catch our attention on
10
the street and push a sale, we pass by in haste; yet we slowly walk into
a beautifully designed souvenir store that is silent and humble just
around the corner? Perhaps it’s because we seek authentic, honest, and
respectful experiences, and tend to ignore everything that doesn’t fit
the bill. In his fantastic book Blink, Malcolm Gladwell outlined an inter-
esting phenomenon related to how humans value their experiences.
502
Bringing Personality Back to the Web by Vitaly Friedman
Everything is a little off on BAO London15 – the spacing, the color com-
binations, the form layout, the hierarchy, the buttons, the cursor, the
lightboxes, the illustrations. This consistent breaking of predictable
patterns makes the website appear interesting and appealing. Breaking
things slowly and deliberately, one component at a time. That’s not a
regular restaurant website.
Everything is way off on the Hans Brinker Budget Hostel website16 and
it’s done deliberately as well. The hostel was struggling to sell rooms as
15 https://baolondon.com/
16 http://hansbrinker.eu/
503
CHAPTER
10
If you can’t make it better, make it worse. Even if you don’t have a wonderful product to
sell, it’s always possible to wrap a story around it and make it more interesting.
504
Bringing Personality Back to the Web by Vitaly Friedman
In his How I Built This interview on NPR,17 Mike Krieger, the co-founder
and creative mind behind Instagram, mentioned that rather than
spending a significant amount of time trying to understand why peo-
ple abandon the service, one of the fundamental principles that drives
growth is focusing on customers who deeply love your product and stay
around for years. By prioritizing existing customers and what they truly
love about your product, you might not only attach them to your prod-
uct, but also boost the word-of-mouth marketing that’s infinitely more
effective than traditional landing pages.
17 http://smashed.by/npr
505
Offboarding Matters
CHAPTER
Just like human relationships sometimes end abruptly and badly, leav-
ing a lasting negative aftermath, so can our relationships with digital
10
506
Bringing Personality Back to the Web by Vitaly Friedman
For example, out of all the wonderful form elements on a given page,
what could be less exciting than a “Title” input? Sometimes appear-
ing alongside radio button counterparts or a dropdown, rigorously
asking customers for very personal information about their marital
status, without any obvious reason whatsoever. And that’s exactly the
moment when we can make it shine beautifully. A great way of cre-
ating memorable experiences is adding a bit of surprise at the point
where it’s most unexpected. Pick the most boring, unnoticeable part
of the experience and try to make it interesting. Now, is there a way to
make this interaction more interesting?
507
CHAPTER
10
Austin Beerworks (see the example on the next page) is just one of many
local breweries in the US. When customers enter the site, as always
they are prompted with an age check that’s supposed to ensure they
are over a certain age limit. Most people – honestly or dishonestly –
would click on “Yes” and move on, but if the customer chooses to click
on “No,” they embark on a “choose-your-own-adventure” trip to be
guided to a video that best describes their personality.
18 http://bodenusa.com/
508
Bringing Personality Back to the Web by Vitaly Friedman
19 https://volkshotel.nl
509
CHAPTER
10
What’s a common interaction on the web? Forms, in all their different flavors
and appearances. In fact, chances are high that you have some sort of a login
and password input on your site, and, of course, that’s a pretty boring inter-
action. Adding a character responding to a user’s input might spice things up
a little. As a result, people might spend more time interacting with the form
before signing in. That’s better engagement at hand. Darin Senneff’s Yeti
character21 (see image on the next page) does just that.
The strategy is simple: choose one predictable, boring pattern, study user
expectations and… break them mercilessly by adding something unex-
pected and uplifting to it. Please note that it doesn’t mean breaking usabil-
ity just for the sake of breaking it; rather, it’s about making a handful of
boring elements more interesting by adding some unconventional treat-
ments to their design.
20 http://smashed.by/tympart
21 http://smashed.by/yetiform
510
Bringing Personality Back to the Web by Vitaly Friedman
511
CHAPTER
Surprisingly, most of the time these pain points aren’t particular features;
it’s the perceived complexity of the interaction and the lack of transpar-
ency. Too many form fields; too much time investment; too slow an inter-
10
SBB Mobile is a Swiss trip planner app that allows customers to check the
schedule of trains and purchase train tickets. On its own, it’s a trip plan-
ner like every single similar app out there, except for one thing. The app
provides a “touch timetable.” Customers can define their common destina-
tions and arrange them on a grid. To purchase a ticket from Zurich to Laus-
anne, for example, it’s enough to draw a line on the grid connecting Zurich
and Lausanne and then confirm the selection. Booking tickets has never
been that fast and easy. That’s a great example of making a conventionally
complex interaction straightforward, especially for people who commute
frequently. Also, it’s a unique design signature that nobody else has (yet).
512
Bringing Personality Back to the Web by Vitaly Friedman
Not only does YouTube provide fully accessible controls for naviga-
tion, it also contains a keyframes preview with thumbnails appearing
on hover, and navigation via keyboard – and it stores the current state
of the video, allowing customers to save a particular time stamp with
a unique URL to continue watching later. YouTube also contains many
513
lengthy videos, like documentaries or tutorials, so users can slide up
CHAPTER
the thumb vertically to adjust the scale of the track and hence jump to
the point of interest more precisely. Unfortunately, only a few users
know of the feature, and the interaction isn’t particularly self-explan-
10
atory, but those who do know of it, use it frequently. One pain point,
solved well.
514
Bringing Personality Back to the Web by Vitaly Friedman
The Harvard Law Review solves this problem in a different way. Refer-
ences are always located right next to the point where they are men-
tioned. Every side note and footnote either appears on the sides on
larger screens, or displayed inline via an accordion. Once a user has
tapped on the footnote, it expands in its entirety, while the footnote
turns into a “close” button. A simple problem solved well.
Imagine you want to book a wonderful vacation with your family, but
you haven’t picked your dates yet. You have an idea of when you’d like
to go, and you have some flexibility regarding the dates for your next
vacation. DoHop allows its users to choose a flexible date for traveling;
for example, particular months of the year, or a particular season,
(winter or fall, perhaps). It then suggests dates and a time span with
the best price. And if you have a public holiday weekend coming up in
a few weeks, and you’d love to make a plan, RoutePerfect (see the screen-
shot on the next page) suggests a route based on your preferences.
That’s a real problem case solved well. Most traveling websites ask for
specific dates for inbound and outbound flights.
515
CHAPTER
10
Good solutions require time and focus. They require us to really under-
stand what pain points users experience first. Users might not be
very good at articulating those paint points, so we developed a simple
technique that helps us get to the root of the problem.
516
Bringing Personality Back to the Web by Vitaly Friedman
517
Exceeding Expectations by Default
CHAPTER
Here’s another question for you: of all the hotel experiences you
ever had, which ones are the most memorable? Think about it for a
10
moment. Think about what made them so special and why they stayed
with you all the time. It might have been an extraordinary natural
backdrop, or remarkably attentive personnel, or a lavish breakfast
buffet. Or something entirely different. In fact, for many of us it could
have been a pretty average dormitory as much as an exquisite 5-star
chalet in the Swiss alps. The environment matters, but it’s not the envi-
ronment alone that matters.
The reason why these experiences are memorable is because they aren’t
average.22 In fact, they are the very opposite of average in some way, as
something was exceptional about them. It’s not necessarily the hotel
itself – it’s the timing and the people we happen to spend these expe-
riences with. A good hotel provides a setting that enables wonderful
experiences, and so does a good website interface. A memorable hotel
adds a fine detail to the experience that exceeds our expectations, and it
does so without telling us up front. And so do memorable websites.
518
Bringing Personality Back to the Web by Vitaly Friedman
details can make all the difference in the world. So we could sprinkle a lit-
tle bit of human kindness here and there, adding extra value silently, with-
out even mentioning it. That fine detail could be as simple as a custom-de-
signed profile illustration, based on the photo the customer has uploaded.
It could be a handwritten thank-you note, signed by the entire team and
sent via good ol’ snail mail. It could also be an unexpectedly straightfor-
ward resolution of a problem after a mistake has been made.
A mock-up we’re currently exploring in Smashing Magazine’s checkout to allow inline edit-
ing of data on the review step and editing of the order within 5 minutes after the purchase.
519
In the same way, an interface could suggest to a signed-in customer to
CHAPTER
use a use a coupon code saved in their dashboard as it’s about to expire
soon, or inform them about a similar – and hence potentially duplicate
– booking made a while back. The personality of the brand shines best
10
520
Bringing Personality Back to the Web by Vitaly Friedman
Error messages deserve slightly more credit than they are usually given.
They appear at the point where the customer’s progress is blocked, and
when they have to slow down and pay full attention to resolve a problem.
We can use the situation to our advantage to infuse a bit of personality
into the experience. Every single time an interface fails to meet expecta-
tions, it’s an opportunity to create a memorable impact in the process of
speedy recovery. If we manage to turn an annoyance into the feeling of
being valued or understood, we might be heading off on the right track.
One of the very first things I started working on when we embarked on the
redesign was filling elaborate spreadsheets with alternate wordings for our
checkout error messages. It wasn’t done with the intention to A/B test the
“best performing” error message; it was done primarily to discover better
expressions of the personality through the interface. On their own, error
messages don’t really make sense, but they fit well within the story being
told throughout the site. Once an error has occurred, we try to use both
adaptive and playful copywriting to address the issue while also raise the
occasional smile.
Voice and tone are the main pillars of a personality. MailChimp has built a dedicated voice
and tone style guide (http://smashed.by/voicetone) to align designers, customer support, and
everybody else in the way they communicate to customers.
521
Seek critical pain points that customers often experience on the site
CHAPTER
by looking into customer support logs and user interviews, and design
these experiences with extra care and attention. It goes without saying
that a quirky personality won’t help much if the customer isn’t able to
10
solve a problem, so take care of the basics first. Ultimately, it doesn’t take
that much effort to turn negative experiences into positive ones – it’s
just a matter of having it on your to-do list when designing an interface.
23 http://smashed.by/designanimations
522
Bringing Personality Back to the Web by Vitaly Friedman
very sparingly, as they should be reserved for very special occasions, while
subtle micro-animations could accompany the user all along the way. Val
suggests using animation only for key compositions of your story, like
sending a marketing campaign, or favoriting an item, or seeing a suc-
cessful purchasing page, while everything else should remain calm and
normal. With this idea in mind we could think of designing our interfaces
with two kinds of interactions: the prominent “showroom” ones, used
rarely; and subtle “workhorse” ones, used frequently.
24 http://smashed.by/progreduct
523
As Allan Grinshtein pointed out,25 a user’s proficiency in a given prod-
CHAPTER
uct decays over time without usage (also known as experience decay), so
if a user’s frequency of use and usage volume have gone down, then
their interface should regress a level or two, depending on how far
10
The more often customers visit the site, the less likely they want to
be confronted with anything that would slow them down. Therefore,
it might be a good idea to slowly fade out showroom signatures with
growing frequency of use, perhaps removing parallax-effects or speed-
ing up transitions for returning users. In the end, it’s all about the chore-
ography: don’t be that person at a dinner party filling the room with an
extensive story of their life.
25 http://smashed.by/designanimations
26 http://smashed.by/pattystalk
524
Bringing Personality Back to the Web by Vitaly Friedman
Patty Toland, Filament Group, “Design Consistency for the Responsive Web.”
(http://smashed.by/pattystalk)
525
CHAPTER
10
526
Bringing Personality Back to the Web by Vitaly Friedman
With low-fidelity prototypes in place, the next step for the design
is to gain style, with logo, brand colors, custom fonts, transitions,
and animations added to the mix. Sometimes this hierarchy will be
perfectly mapped in the order we write React components and CSS
properties with Sass. Even BEM naming for classes will happen in
that order as well. The prototypes will gain abstract utility classes
first, and more elaborate relationships will be reflected through more
specific class names throughout the process. The process establishes
a clear separation of responsibilities for modules.
This process seems plausible but it raises a very critical question: what
pages to design and prototype first? When we start designing, we design
the heart of the experience first: the most critical and impactful part of
the experience. More specifically, we try to capture the very essence of
the experience by exploring key interactions, then break it down into
reusable components, and then radiate outwards from that essence. For
an online magazine, it would be reading experience and typography
first. For a landing page, it would be the pricing plans and a feature
comparison first.
527
CHAPTER
10
Only then, eventually, we reach the category pages and the FAQ pages
living on the far outer edges of the experience spectrum. Somewhere
in between we explore the front page, but usually we design it late
rather than early in the process – at the point when we’ve established
a strong identity already, so we use the front page to amplify and
explore it prominently, potentially with a bold design that would
exhibit the main qualities of the personality.
528
Bringing Personality Back to the Web by Vitaly Friedman
That’s why we start thinking about the signature of the design when
we start designing the heart of the experience, and sometimes even
before that. Spreadsheets exploring error messages, visual experi-
ments around shapes and colors and type, as well as user interviews
help us get there. Eventually, decisions made for the first prototype can
be reused for other pages, yet sometimes we need to run the process
from the start again – as some pages clearly will be one-offs, such as
the landing page or a front page. They will still exhibit relationships to
everything else because they are designed and built using the person-
ality traits that have been solidified by this point.
It’s these relationships that would then lay the main foundation of a
design system, along with components and examples of the interface
in use. Too often style guides show a component in isolation, along
with Sass class names and a code snippet, without including how that
component should appear and behave in relation to other modules on
the page. Contextual examples matter both for designers and devel-
opers, and they give a good reason to both visit and keep the design
system up to date in the long-term.
529
CHAPTER
10
A storyboard with components. Each component also has a speed and level of dynamics
attached to them. Image courtesy: Andrew Clarke.
You could even print them out and put them as magnets on a story-
board, just like Sipgate did in the earlier example, so designers can
freely move them around and thus explore ways of combining predict-
able components in unpredictable ways. That’s what Andrew Clarke
does when embedding art direction and storytelling in his designs –
very much like comic strip designers arrange the frames according to
narrative dynamics and impact when laying out a comic story.
530
Bringing Personality Back to the Web by Vitaly Friedman
The design signature lies at the very heart of the design. It’s a strand
that connects the components in the interface, and it’s what helps
designers stay on track when maintaining or evolving a design lan-
guage. Deciding on the personality traits first helps drive the direction
of the design, and can be just a good enough constraint to dissolve
initial intentions and goals into tangible, distinguishable attributes
that could eventually become the heart of the experience.
Wrapping Up
As much as we could get seduced by the charm of a website, in the
end, the main purpose of it shouldn’t be self-indulgence. Expressions
of the personality of the site enable emotional connections with cus-
tomers and visitors, and because they are human by their nature, they
outline a path to authentic, honest, and respectful interfaces. It’s up to
us to figure out how to shape that path and the outcome ahead of us.
Now, it might not be for everybody, and perhaps not every site needs
a personality in the first place, or perhaps it should be subtle and
express itself in little nuances here and there. I hope that in either of
these cases, once flipping over the last page of this book, you’ll have
a good enough tool belt of ideas and techniques to create unique and
humane experiences – experiences people could fall in love with.
531
I’d like to express my gratitude to Jen Simmons, Rachel Andrew, Andrew Clarke,
CHAPTER
Dan Mall, Espen Brunborg, and Elliot Jay Stocks for inspiring work, contribu-
tions, and help in discussing the idea of art direction on the web, and making
the web more diverse and experimental. I’d also like to thank Marko Dugonjic,
10
532
Index CSS Grid fallback 117
INDEX
534
IntersectionObserver 281 responsive images 295
latency 306, 316 Sass 133, 149
lazy loading 263, 280, 312 semantic CSS 167
length units 85 separation of concerns 220
media queries 112 server-side processing 253
minmax() 98 service workers 184, 313
motion 176, 339, 522 single-page app (SPA) 48
network latency 306 sizes 296
network strategies 212 Smashing Book 7 2020
non-critical CSS 263 srcset 295
notification 68 Storyboards 530
offboarding 506 style guides 24, 41, 78,
offline 204, 310 474, 487, 521
offload 308 subsetting 298
OKR 29 SVG 163
pattern library 12 tabindex 54
percentage sizing 87 tables 121
performance optimization 184, 232, 458 testing 75, 484
personality 350, 470 the fold 156
physics 463 theming 158
plotting 441 third-party content 304, 311
preconnect 248 unused content 288
predictability 332, 476 user testing 516
preload 232, 266, 286 variable-width images 296
priority hints 261, 279 versioning 215
progressive disclosure 476 vh unit 102
progressive enhancement 270 viewport width 124, 171
progressive loading 267, 439 VR 432
progressive reduction 523 vw unit 296
Promises 186 wearables 415
prototyping 362, 420, web fonts 284, 298,
525 web packaging 292
Push Events 225, 229 WebP 297
push notification 225 WebXR Device API 436
QUIC 246 wireframe 525
refactoring 168 workflow 12
responsive design 112, 155 XR 432
535
More From Smashing Magazine
• Design Systems
by Alla Kholmatova
• Digital Adaptation
by Paul Boag
We’ve been there: it’s tough to advocate for respect and transparency and
privacy, but perhaps we could lead conversations in our companies and
make a business case on why they matter. And as such, this book features a
bunch of shiny new techniques and strategies — shining some light on the
wonderful new frontiers of the web we all are shaping together. We hope
that once we get there, we’ll see all those concerns of the past in an entirely
new light.