22 Taste 22 Taste 22 Taste 22 Taste
Good motion is sized to how often it is seen, and sometimes the right size is none.
Chapters 1 to 20 taught you to measure and build motion, and chapter 21 to make it safe. This one is about choosing it. Two animations can both be well made, with an honest curve and a sensible duration, and one of them can still be wrong: wrong for the product, wrong for the moment, or wrong the fiftieth time it plays.
Taste sounds like a gift. Most of it is a few questions you can learn to ask, and the chapter is organised around them. How often will this be seen? What should the product feel like? Does this need to move at all? And when something feels off: what exactly is wrong, and which one thing would fix it? Each section ends in a check you can run on your own work.
Seen all day, seen once
Four moments from one product. From left to right, each is seen less often than the one before. Hover or tap each one to replay it, and notice how long each takes before you could act on the result.
Each one moves more than the one before it. The switch is over almost before it starts. The menu is quick and small. The dialog takes a moment to rise out of the page. The first-run panel grows out of its tile and fills the frame, because it gets that stage once. The rarer the moment, the more motion it can afford.
Which one on the fortieth time?
Imagine using this every day.
You switch between these tabs forty times a day. Which one do you want?
The frequency rule
The more often something is seen, the less it should move. Motion costs a little attention and a little time, and the cost is paid every time it plays. Seen once, a lovely animation is a gift. Seen two hundred times a day, it is a tax.
So before choosing a curve, ask how often the thing will be seen, and let the answer set a budget. To place something in the table, imagine one user’s day: things they trigger many times a minute are constantly, several times an hour frequently, a few times a day occasionally, and once or twice in the life of the account rarely. The boundaries are a rule of thumb; when unsure, pick the more frequent row, because too little motion costs less than too much.
| How often | For example | At most | Distance |
|---|---|---|---|
| Constantly | typing, hovering, pressing | 90ms | none |
| Frequently | menus, tabs, popovers | 180ms | 8px |
| Occasionally | panels, dialogs | 280ms | 24px |
| Rarely | onboarding, a first run, a celebration | 480ms | 24px |
The numbers are this site’s own tokens: --dur-instant up to --dur-scene for time, --dist-nudge and --dist-travel for distance. They are a starting scale, not physics: 200ms for a menu would be fine too. What to keep is the shape (rarer means longer and farther), and the top of the range: a routine interaction that takes more than about half a second starts to feel like waiting. The durations are ceilings, not targets. And distance matters as much as time: something seen constantly shouldn’t travel at all. A hover can change a colour; it shouldn’t move the page.
This site keeps the table in its own code. frequencyBudget in its motion policy maps constant, frequent, occasional and rare to the most duration and distance each may use. Figure 22.1 follows it: the switch runs at 90ms, the menu at 180ms, the dialog at 280ms, and only the first run gets 480ms.
A spring counts by how long it moves, not only by when it arrives. The spring in A above reaches the new tab in about 200ms, close to when B has finished, then overshoots by about 10% and needs 800ms in all to settle. That tail is what you would see on every switch.
The rare end of the table is where delight belongs. A first run, a finished setup, a milestone: seen once or twice, they can afford a longer move, a bigger distance, even a bounce. A celebration on every completed task would stop being one. Keeping it rare is part of the design.
Personality
Every product moves with a character, whether anyone chose it or not. Here is one moment, a count landing on a bell, built three ways.
- Calm says nothing here is urgent. It covers most of the way at once, settles slowly, and never overshoots. For enters, it rises a small distance, around
8px. - Playful says this is meant to be enjoyed. The spring grows about 10% past full size and settles back, over
800ms. It spends energy, and energy is the message. - Precise says you are in control; this is a tool. Short, symmetrical, no overshoot. It stops exactly where it was told to.
Which belongs in a banking app? In a game? In a code editor? There’s no right answer in the abstract, only for a particular product. A bounce in a game says fun. The same bounce on the screen that confirms a payment says the money is bouncing. Three questions narrow it down: what does a mistake cost the user here (more cost, more precise), how long do they stay in one sitting (longer, calmer), and what does the brand already sound like in its words and colours (the motion should agree with it).
So pick one personality per product and use it everywhere. Write it down as a few tokens (a duration, a curve, a distance) and let every component take its motion from them. When one screen is calm and the next is playful, the product feels like it was built by several people who never met.
Personality and frequency work together. Personality sets the character; frequency sets the budget. A playful product still keeps its tabs quick, as you felt above. It spends its bounce on the badge, the like, the finished task: confirmations, where chapter 5 found a bounce honest.
When not to animate
A cut is a decision too, and often the calm one. Leave it still:
- When it would make someone wait. If the next action has to wait for a motion to finish, the motion is in the way. Keep controls working while things move, or don’t move them (chapter 21).
- When there is nothing to follow. Motion says keep your eye on this. When content is simply replaced, like the next page of search results, there is nothing to keep your eye on. Show it.
- When their own action already shows it. A typed letter appears where you type it. A dragged card is already under your finger. Motion on top of a direct action repeats what the hand just did, and lags behind it.
- When it happens constantly. Scrolling, typing, hovering. This is the frequency rule taken to its end.
- When it would be the tenth thing moving. Motion points. When everything points, nothing does. This site’s motion policy sets a budget of six UI animations at once; figures are exempt, because they are the content.
A quick test for any animation you are unsure about: remove it and ask what the user loses. If you can name something (where a panel came from, which item changed, that a save worked), keep it. If the honest answer is “nothing, it just looked nice”, it belongs in the rare row of the table or nowhere.
Case study: this site
This site chose calm. It is meant to read like a quiet book whose figures happen to be alive: numbered plates, wide margins, and a page that holds still where you read, so that whatever moves there means something. Its motion language is small: four durations, four curves, two springs.
| Token | Value | Meant for |
|---|---|---|
--dur-instant | 90ms | press states, toggles |
--dur-quick | 180ms | hovers, small reveals |
--dur-base | 280ms | panels, cards |
--dur-scene | 480ms | page and chapter transitions |
--ease-out | cubic-bezier(.2, .8, .2, 1) | enters |
--ease-in | cubic-bezier(.4, 0, 1, 1) | exits, at 0.7× the enter’s time |
--ease-inout | cubic-bezier(.65, 0, .35, 1) | on-screen moves |
--ease-reveal | cubic-bezier(.16, 1, .3, 1) | lines of text rising out of a mask |
spring.snappy | response 0.3, bounce 0 | direct manipulation |
spring.soft | response 0.5, bounce 0.15 | playful confirmations |
Add three distances (4px, 8px, 24px) and one stagger step (30ms), and that is all. Now look at how they are spent, from the most frequent moment to the rarest:
- Hovering a link strengthens its underline to full ink over
--dur-quick. Nothing moves. You hover constantly, so hovering gets colour, not motion. - Pressing a button drops it
1pxover--dur-instant. Just enough to feel the press. - Changing chapters is a cross-fade, not a slide. The table reserves
--dur-scenefor page changes, but the one that shipped is shorter, which is what the frequency rule asks for: you turn pages often. (196msis the exit rule from earlier chapters: 0.7 of the280msenter.) The old page fades out over196ms(--dur-base-exit); halfway through, the new one fades in over280ms(--dur-base) while rising4px, the smallest distance the site has. - Arriving is where the chrome spends
--dur-scene, because each arrival happens once. A chapter’s title is written on in blue pencil and then inked, and its lede rises out of a mask, line by line. The first time a figure scrolls into view, its axes draw over480ms, then its ghosts appear, then the red key lands onspring.soft. Each plays once, when you first reach it.
And the bounce? Of the two springs, only spring.soft bounces, at 0.15, and in the chrome it is spent on that one moment: the red key landing. Nothing else in the chrome bounces. That restraint is the personality.
The critique protocol
When a motion feels wrong, you usually know it in a word before you know why. That word is where the fix starts.
- Name the feeling in one word, before you touch a number: floaty.
- Find its cause: what in the motion makes it feel that way? Too long for its distance.
- Name the parameter that holds the cause: duration.
- Change one thing, then look again, at the same speed as before.
One thing, because if you change the curve and the duration together and it gets better, you don’t know which one fixed it, and you’ve learned nothing for next time. If it still feels wrong, name the new feeling and go round again.
Most feeling-words point at the same few causes:
| Feeling | Usual cause | Parameter | Change |
|---|---|---|---|
| sluggish | too long for how often it’s seen | duration | shorter, into its frequency band |
| floaty | too long for its distance | duration | shorter |
| abrupt | too few frames to show a path | duration | longer |
| hits a wall | still speeding up when it arrives | easing | ease-out |
| mechanical | constant speed | easing | any ease |
| nervous | a bounce with nothing to cause it | easing | damp it to bounce 0 |
| shouting | travels too far | distance | a shorter trip |
| slow to arrive | the stagger adds up | stagger | 20–40ms between items |
| lingering | the exit takes too long | exit duration | about 0.7× the enter |
Three of them, each fixed by changing one thing. The left column is broken; the right changes one parameter and nothing else.
Notice that the words are about the whole motion, but each fix is one number. Floaty was never the curve. Shouting was never the speed. The protocol turns a vague “something’s off” into a parameter you can change.
Name it, change one thing, save it
This bench is set to a move with two faults, one in the curve and one in the time. Play it and name the louder one in a word (use the table above). Change only the parameter behind it, then play again and see whether that word still fits. Play it again, name what’s left, and change one more thing. When it feels right, press Save to Journal and give it the word you would now use for it.
The journal below keeps every specimen you save, from any instrument. Each word gathers numbers: how many times you used it, the median duration of those specimens, the kind of curve, and for springs the damping ratio. After a dozen saves you will know what you mean by snappy, in milliseconds. That is a vocabulary you can hand to someone else.
The notation
A personality is a set of tokens that every component reads. Components use the roles (--move-dur, --move-ease, --move-dist), never raw values, so switching the personality changes the whole product at once. Set data-personality on the root element and every rule below follows. @starting-style (chapter 9) gives the new element the values to animate from. The playful curve is the spring from figure 22.2, sampled into linear() and run for its settle time, as in chapter 5.
/* One personality per product. Components read the roles, never raw values. */
:root { /* calm */
--move-dur: 280ms;
--move-ease: cubic-bezier(.2, .8, .2, 1);
--move-dist: 8px;
}
:root[data-personality="playful"] { /* spring: response .45, bounce .4 */
--move-dur: 800ms; /* its settle time */
--move-ease: linear(0, 0.006 1%, 0.031 2.33%, 0.071 3.67%, 0.123 5%,
0.253 7.67%, 0.649 15%, 0.758 17.33%, 0.852 19.67%, 0.93 22%,
0.99 24.33%, 1.035 26.67%, 1.069 29.33%, 1.092 33%, 1.092 37.33%,
1.016 54.33%, 0.994 64.33%, 1);
--move-dist: 24px;
}
:root[data-personality="precise"] {
--move-dur: 180ms;
--move-ease: cubic-bezier(.65, 0, .35, 1);
--move-dist: 8px;
}
.badge {
transition: scale var(--move-dur) var(--move-ease);
@starting-style { scale: 0; }
}
.toast {
transition: translate var(--move-dur) var(--move-ease),
opacity 180ms linear;
@starting-style { translate: 0 var(--move-dist); opacity: 0; }
} // The more often it's seen, the less it may move. Times are in ms, distances in px.
const DURATION = { instant: 90, quick: 180, base: 280, scene: 480 };
const DISTANCE = { none: 0, nudge: 8, travel: 24 };
const BUDGET = {
constant: { duration: 'instant', distance: 'none' }, // hovering, pressing
frequent: { duration: 'quick', distance: 'nudge' }, // menus, tabs
occasional: { duration: 'base', distance: 'travel' }, // panels, dialogs
rare: { duration: 'scene', distance: 'travel' }, // onboarding, first run
};
function enter(el, seen) {
const b = BUDGET[seen];
// Reduced motion: keep the fade, drop the move.
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const y = reduce ? 0 : DISTANCE[b.distance]; // px to rise from
return el.animate(
[
{ opacity: 0, transform: `translateY(${y}px)` },
{ opacity: 1, transform: 'none' },
],
{
duration: DURATION[b.duration],
easing: 'cubic-bezier(.2, .8, .2, 1)',
fill: 'both',
},
);
}
enter(menu, 'frequent'); // 180ms, 8px
enter(dialog, 'occasional'); // 280ms, 24px On Monday, the change is small: give each component a frequency, not a duration. The helper asks one question before anything moves: how often is this seen? The answer picks the duration and the distance. The personality picks the curve. Keep the two separate and the product stays consistent as it grows. Chapter 23 puts motion to a stricter job: explaining data, where every move has to be true.
Fix the feeling
A motion plays with a feeling-word attached. Name the parameter that causes it, and fix it in one change. Say the cause to yourself before you touch anything.
It feels wrong. Name the cause, and fix it in one change.Critique: feeling → cause → parameter → change one thing.
5 trials. Judge with your eyes first; the numbers come after. Three right in a row makes trials harder, a miss eases them.