Part VIII · Craft 22 / 25

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.

All day · a switch Flipped all day. It barely moves: the new state is the message.
Often · a menu Quick and small. Open before you think about it.
Now and then · a dialog Enough time to show where it came from.
Once · a first run Seen once or twice, so it can take the stage.
Fig. 22.1 — Four moments, from seen all day to seen once

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.

Feel

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 oftenFor exampleAt mostDistance
Constantlytyping, hovering, pressing90msnone
Frequentlymenus, tabs, popovers180ms8px
Occasionallypanels, dialogs280ms24px
Rarelyonboarding, a first run, a celebration480ms24px

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 --ease-out · 280ms. Arrives quickly, then takes its time to settle.
Playful spring · response 0.45, bounce 0.4. Pops past full size and back.
Precise --ease-inout · 180ms. Short, symmetrical, and stops exactly.
Fig. 22.2 — One badge, three personalities
  • 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.

TokenValueMeant for
--dur-instant90mspress states, toggles
--dur-quick180mshovers, small reveals
--dur-base280mspanels, cards
--dur-scene480mspage and chapter transitions
--ease-outcubic-bezier(.2, .8, .2, 1)enters
--ease-incubic-bezier(.4, 0, 1, 1)exits, at 0.7× the enter’s time
--ease-inoutcubic-bezier(.65, 0, .35, 1)on-screen moves
--ease-revealcubic-bezier(.16, 1, .3, 1)lines of text rising out of a mask
spring.snappyresponse 0.3, bounce 0direct manipulation
spring.softresponse 0.5, bounce 0.15playful 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 1px over --dur-instant. Just enough to feel the press.
  • Changing chapters is a cross-fade, not a slide. The table reserves --dur-scene for page changes, but the one that shipped is shorter, which is what the frequency rule asks for: you turn pages often. (196ms is the exit rule from earlier chapters: 0.7 of the 280ms enter.) The old page fades out over 196ms (--dur-base-exit); halfway through, the new one fades in over 280ms (--dur-base) while rising 4px, 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 over 480ms, then its ghosts appear, then the red key lands on spring.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.

  1. Name the feeling in one word, before you touch a number: floaty.
  2. Find its cause: what in the motion makes it feel that way? Too long for its distance.
  3. Name the parameter that holds the cause: duration.
  4. 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:

FeelingUsual causeParameterChange
sluggishtoo long for how often it’s seendurationshorter, into its frequency band
floatytoo long for its distancedurationshorter
abrupttoo few frames to show a pathdurationlonger
hits a wallstill speeding up when it arriveseasingease-out
mechanicalconstant speedeasingany ease
nervousa bounce with nothing to cause iteasingdamp it to bounce 0
shoutingtravels too fardistancea shorter trip
slow to arrivethe stagger adds upstagger20–40ms between items
lingeringthe exit takes too longexit durationabout 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.

Floaty 1100ms to rise a short way.
Duration → 300ms Same curve, same distance.
Mechanical Linear: the same speed all the way.
Easing → --ease-inout Same 520ms. It starts, travels, and lands.
Shouting A routine card travelling 160px.
Distance → 16px Same time, same curve, a short rise.
Fig. 22.3 — Three feelings, each fixed by one change

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.

L2 Curve bench
Open in Lab
Presets

Constant speed. Mechanical.

Handles · (x1, y1) start, (x2, y2) end
0.00
0.00
1.00
1.00
1000ms
Compare with B
01time →position
01×time →velocitypeak 1.0×
linear
00/60 fr 0ms

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.

Your motion vocabulary

Each word, and what you meant by it in numbers: typical duration, curve, and ζ (damping) for springs. Click a word to see its specimens.

Specimens

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.

22 · Taste: a personality as tokens Open in Lab
/* 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; }
}
22 · Taste: duration by frequency Open in Lab
// 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.

Eye trainer · choose Fix the feeling

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.

Full rounds and your calibration in the Eye trainer