Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 1.5Beginner ~24 min · shot 5 of 6

Perceived Speed and Response Time

In one line

People judge speed by what they feel, not by a stopwatch. Feedback within about 100 ms feels instant, a spinner that flashes for 100 ms feels like a glitch, and a screen that gives no sign of life for a second feels broken. Motion decides which of these a user gets.

0/6 · 0%

Think of it like this

You press the call button for a lift. If the button lights up at once, you relax and wait, even for a minute. If it stays dark, you press it again and again, and start to wonder if it is broken. The lift came at the same time in both cases. The light only told you that you were heard.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

perceived speed
How fast something feels to the user, which is not always how fast it is.
feedback
A visible sign that the page received the user's action.
spinner
A small repeating animation that says work is in progress.
skeleton screen
Grey placeholder shapes in the layout of the content that is still loading.
determinate progress
A progress bar that shows how much of a known amount of work is done.
role="status"
An HTML attribute that makes a screen reader read out changes in an element politely, when it is free.

Step by step

01Try the four server speeds

In "A spinner that waits" pick a server speed and press Save ride. The button changes to "Saving…" and is disabled in the same frame, whatever the speed. Pick fast: 80 ms and no spinner ever appears: the page just says "Ride saved".

02Read the spinner's life on the timeline

Choose medium: 400 ms, press Save and watch the track. The spinner's animation appears at about 200 ms. The answer arrives at 400 ms but the spinner stays until 600 ms (200 ms shown, plus the rule that it stays 400 ms in all), so it is read as a spinner and does not flicker. Measured in real time: shown at about 200 ms, hidden at about 600 ms. If you scrub the clock in big jumps, timers the page starts meanwhile start late and the spinner can seem to stay longer. Use Step, or let it play, for exact times.

03Try the two failure cases

Choose slow: 1.5 s: the spinner shows from about 200 ms until the end, and then "Ride saved" appears. Choose fails after 1.5 s: the spinner ends and the message says "Couldn't save. Check your connection and try again." The button is usable again. A spinner that never ends would be a bug.

04Switch the list between its four states

In "Four states of a list" press Loading, Loaded, Empty and Error. Loading shows three grey rows in the same shape as the real rows, so nothing moves when the real ones arrive. Empty tells the user what will be here. Error says what happened and offers Try again, which returns to loading.

05Feel the difference

The two pairs below both have the same server time. Play both sides of each and choose before you read why.

Try it yourself

  1. 1

    Remove the minimum display time

    In "A spinner that waits" set SHOW_AT_LEAST to 0 and choose medium: 400 ms. The spinner now vanishes as soon as the answer arrives at 400 ms. It was visible for 200 ms. That is the flash the minimum time prevents. Put it back to 400 and the spinner stays 200 ms longer, until about 600 ms.

  2. 2

    Change the delay

    Set SHOW_AFTER to 600 and choose medium: the answer comes at 400 ms, so no spinner is shown at all. For slow, it now shows at 600 ms. Between 100 ms and a second, the only sign of life is the "Saving…" button. Ask yourself whether that is enough for your page.

  3. 3

    Look at reduced motion

    Switch on the timeline's Reduced motion and replay, choose slow, press Save. The spinner is still in the page, but it is a static ring with no @keyframes track: the animation is inside prefers-reduced-motion: no-preference. The "Saving…" label carries the message on its own.

Code & diagrams

A spinner that waits

Acknowledge at once, show a spinner only after 200 ms, keep it for at least 400 ms, always end it with a result or an error. The spinner turns only when the user has not asked for reduced motion.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.
Four states of a list

Loading (a skeleton), loaded, empty and error. The skeleton rows have the same size as the real rows so the page does not jump.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.
Feel itPress feedback

Saving takes 1.5 seconds in both. Which Save button feels faster?

A A

Press Run to see the page.

B B

Press Run to see the page.
Play both, then choose:
Feel itA spinner for a fast answer

The server answers in 120 ms in both. Which feels smoother?

A A

Press Run to see the page.

B B

Press Run to see the page.
Play both, then choose:

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget the error path

In "A spinner that waits" replace the line starting catch { with finally {}, choose fails after 1.5 s and press Save.

terminal
$ Press Save with the server set to fail, then look at the page 1 second after the failure
── what you'll see ──
The spinner is still on screen and the button still says Saving… and cannot be pressed. The console shows `Uncaught (in promise) Error: offline`.
Measured with the clock frozen at 2.5 s: the spinner's display was still block and its animation was still running.

Myth vs fact

Myth

A spinner makes any wait feel shorter.

Fact

A spinner shown for a fast answer feels like a glitch, and one that never ends feels like a failure. It helps only when the wait is long enough to need it, and when it always ends.

Myth

Skeleton screens are always better than spinners.

Fact

They suit content with a known layout. For a button press or a short action, a skeleton is more to draw than the wait is worth. Use a skeleton for a page region, a label change for a button.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The delay and minimum times (200 ms and 400 ms here) are choices, not standards. Measure your real response times: if most answers arrive between 150 and 250 ms, a 200 ms delay still makes flashes, so raise it.

  • ▸

    Screen readers hear a loading state only if you announce it. Change the text of a role="status" container that is already in the page. Text that arrives together with a new element is often missed.

Remember this

  1. 1

    The problem this solves: a page that does real work (saving, loading, searching) takes real time you cannot always remove. What you control is what the user sees during that time. Good motion tells them "I heard you" at once, "I am working" if it takes a while, and "here is the result" or "it failed" at the end.

  2. 2

    Three response-time limits have been used in interface design for decades (written up by Jakob Nielsen at NN/g). About 0.1 second: the result feels like a direct effect of your action. About 1 second: you notice the delay, but your train of thought continues. About 10 seconds: the limit of keeping your attention on the task. They are guides, not laws, but they tell you what to plan for at each time.

  3. 3

    Acknowledge within one frame. The pressed look, a changed label ("Saving…") or a disabled button should appear on the very next frame after the click, even when the result takes seconds. This separates "I heard you" from "here is the result". It only works if the main thread is free: if a click handler starts with heavy work, the acknowledgement waits behind it (Topic 1.4). Make the cheap visual change first.

  4. 4

    Show a spinner only when the answer is late. If a response comes back in 80 ms, a spinner that appears and vanishes in 80 ms is a flash: it looks like a glitch and slows the page down. Wait about 200 ms before showing one (a common choice: tune it for your page). And once a spinner is shown, keep it for a minimum time (about 400 ms) so it does not flicker on and off when an answer arrives just after it appeared.

  5. 5

    Skeleton screens are grey placeholder shapes in the layout of the real content. They suit content with a known shape (a list, a card, a profile) that takes more than a moment to load: the page does not jump when the content arrives, and the user sees where things will be. Keep any shimmer slow and calm, and make it static for people who ask for reduced motion. Motion cannot make a slow page fast. It can make a wait look like progress or like a fault: a frozen screen reads as broken, a calm indicator reads as working, and a jittery one reads as trouble.

  6. 6

    Honest progress. A bar that moves steadily and finishes when the work finishes is a promise kept. A bar that races to 99% and sits there is worse than no bar. If you do not know how long work takes, move slowly toward a limit (such as 85%) and finish when the work really does. Never invent progress to hide a slow page.

  7. 7

    Every state needs feedback: loading, loaded, empty ("No rides yet") and error. A spinner that never ends when the server fails is the worst case: the user waits for ever. Always end a loading state, with a result or an error message and a way to try again, and announce it for screen readers with role="status" or role="alert".

Explain it without notes

01

Why wait before showing a spinner, and why keep it for a minimum time?

02

What are the three response-time limits, and what should you plan for at each?

Practice

01

Write a function withSpinner(promise, show, hide) that calls show() only if the promise takes longer than 200 ms, and keeps it visible for at least 400 ms once shown.

02

A list loads in 100 ms on a fast network and 4 s on a slow one. Which loading pattern would you use for each, and why?

Trade-offs

  • ↔

    Showing feedback at once costs a little work in the click handler. The reward is large: the user knows the press registered, and does not press twice.

Done when you can

  • You can say what 0.1, 1 and 10 seconds mean for a user

  • You can acknowledge an action within one frame

  • You can show a spinner only when the answer is late, and always end it

  • You can build loading, empty and error states, not just the happy path