Managing Complex Waiting Experiences on Web UIs

Waiting is not a concept we like. We are usually stressed at every point where “waiting” touches our lives. And on the internet, “waiting” can be much more complicated than we thought it would be.

But apparently there is a long time we will have to “wait” until the tech guys solve the “speed” problem. ¯\_(ツ)_/¯

“You cannot manage what you cannot define.”

Part 1: Let’s define “waiting”!

On the Web, we frequently face the expression “loading”. I experienced that managing the “loading” experience is much more complicated than I thought, and I really had to manage it.

So, let’s define “waiting”:

In interfaces, “waiting” splits into 4 concepts which you can combine:

  1. Scope: Where the “waiting” happens.
  2. Specificity: If the “waiting” time is known.
  3. Interaction: If the “waiting” blocks the user or not.
  4. Reality: If the “waiting” is real or fake.

1. Scope

Defines where the “waiting” action happens. It may be two areas:

  • 📦 Application [A]
    Whole application will be loaded or some loading will affect whole application. E.g. Booting an operating system, opening a big software.
  • ✂️ Component [C]
    A part of an application will be loaded. E.g. Loading timeline tweets while scrolling Twitter’s feed.

2. Specificity

Defines if we know when the “waiting” will end.

  • ⏭ Determinate [D]
    The waiting action has a specified ending. E.g. Downloading a file, watching a movie, listening to a music, etc.
  • ⏩ Indeterminate [I]
    The waiting action does not have an ending. E.g. Streaming a live TV channel, listening to a radio, waiting for someone to write a reply to your comment, sending a request to a server, etc.

3. Interaction

Defines if the “waiting” action will block the interaction origin (user).

  • 🔒 Blocking [B]
    The waiting action is critical and the user shouldn’t touch specified area while something is happening. E.g. blocking user not to click “create” button again while creating something on back-end.
  • 🔓 Non-blocking [N]
    The waiting action is not really critical and user can touch anywhere while something is happening. E.g. Allowing user to step into a specified time while YouTube movie is loading.

4. Reality

Defines if the “waiting” action is real or not real.

  • 📝 Real [R]
    The waiting action really waits for something to end in the background. E.g. User uploads their avatar to the server.
  • 💭 Fake [F]
    The waiting action is not really waiting but it “feels” like doing something. It’s generally for UX purposes. E.g. Wait for server to create seed data.

“Waiting” Examples

Every combination of these 4 concepts has a value on UIs.

I don’t think to add Reality concept to the combination since it doesn’t make any difference on UI aspect.

Application, Determinate, Blocking 📦 ⏭ 🔒

Use cases: Splash screens.

Think of an application scope with blocking user and has a predictable ending. It’s basically a splash screen.

Splash screens are something which puts users to on hold and it doesn’t allow you to use anything while things completely set up.

Splash screen of NetBeans IDE.

In the example above, there is a progress bar that indicates the status of the waiting progress.

Info: If the “waiting” work has a predictable or specified ending (determinate), generally “progress bars” are being used.
A progress bar goes from 0 to 100.

Application, Determinate, Non-blocking 📦 ⏭ 🔓

Use cases: Top loader, global activity indicator.

Blocking user is not friendly for your application and when the waiting is on application scope, top loaders or global activity indicators are your friend.

YouTube’s global activity indicator.
Note: Predictable waitings should also be considered as “determinate” even they are “indeterminate”. A page loads in maximum 1–2 seconds. Thus it’s a good UX to use a progress bar.

Application, Indeterminate, Blocking 📦 ⏩ 🔒

Use cases: Splash screens (for mobile)
Opening screen of Facebook Application
Waiting for an “indeterminate” action is generally shown as “activity indicators” which are circle loaders.
An activity indicator (circle loader) that goes nowhere.

Application, Indeterminate, Non-blocking 📦 ⏩ 🔓

Use cases: Activity indicator, hourglass.

When you do not want to block user while something is happening on application, an activity indicator shown on screen which is generally minimal.

An indeterminate network activity happening on iOS which is not blocking user.

Component, Determinate, Non-blocking ✂️ ⏭ 🔓

Use cases: Background loaders, percentage texts.

While you actually wait for something to load, the UI will allow you to interact with it. YouTube’s “gray loader” actually covers this case.

Gray loader of YouTube

Component, Indeterminate, Blocking ✂️ ⏩ 🔒

Use cases: Ghost loaders

Ghost loaders are most popular loaders nowadays. Building user interface with faking its content makes the layout more stable in UX aspect.

Slack’s ghost loaders.

Combinations without examples, not very common:

  • ✂️ ⏭ 🔒 Component, Determinate, Blocking: Download progress bars of browsers may be examples for that.
  • ✂️ ⏩ 🔓 Component, Indeterminate, Non-blocking: Optimistic rendering with an activity indicator may be example for that.

Let’s summarize the combinations with a table:

Scope, Specificity, Interaction. Reality concept doubles this table.

Part 2: Let’s manage them!

After defining the concepts, managing them become easier.

Let’s assume we have an interface as below:

An application scope top loader, and many component loaders.

There are few simple steps to easily manage them:

1. Name every “wait”:

Naming waits is the first step you need to make. It allows you to trace and debug in complex states.

Write a clear message what it’s waiting for.

2. Collect them into a list:

The “waiting” names should be collected into an array:

Loaders should be in a global singleton loaders array. Also the names must be unique.

let waitingFor = [
"fetching sidebar",
"fetching user",
"fetching content",
"sending content",

3. Add and remove them:

A simple push to the waitingFor before the actual work starts:

// Start a new loader

waitingFor.push("logging in");
await fetch("/login");

And to remove, filter the waitingFor and replace it with the new state.

// End the loader

await fetch("/login");
waitingFor = waitingFor.filter(l => l != "logging in");

4. Get the state of loaders:

// Are there any loaders on application scope?

return waitingFor.length > 0;
// Are there any specified loader?

return waitingFor.includes("logging in");

5. Collect progresses in a key-value store:

Keep progress information in another global singleton object.

let progresses = {};

6. Starting a progress:

Also the waitingFor should be appended.

// Set the progress of "uploading avatar"
waitingFor.push("uploading avatar");
progresses["uploading avatar"] = { current: 249, total: 1828 };

7. Ending a progress:

waitingFor = waitingFor.filter(l => l != "uploading avatar");
const { ["uploading avatar"]: omit, ...progresses } = progresses;
// or
delete progresses["uploading avatar"];

A Vue.js Implementation: vue-wait

According the ideas above, I created a tool called “vue-wait” that manages your loaders with a declarative API and useful Vue component.

vue-wait helps to manage multiple loading states on the page without any conflict. It’s based on a very simple idea that manages an array (or Vuex store optionally) with multiple loading states. The built-in loader component listens its registered loader and immediately become loading state.
vue-wait logo contains the V of Vue, X of Vuex and a circle indicator.

Vue-wait is an implementation for Vue to manage loaders easily. It has a very simple API.

vue-wait provides a very simple API to start.

<v-wait for='fetching data'>
<template slot='waiting'>
This will be shown when "fetching data" loader starts.

This will be shown when "fetching data" loader ends.

And you can just start and end loaders.

$wait.start("fetcing data"); // waiting slot will be shown
$wait.end("fetching data"); // default slot will be shown

For detailed documentation, please visit


When we examine “waiting” in detail, it is far from reality to implement the flow of the application with one “loading” type. Knowing the possibilities will increase the power you have. Also, as you can see in the code above, “waiting” is both application and component level state management.

Thanks Fatih Acet and Eser Ozvataf for review.