Agent Wranglers

Data views Recommended

Show loading without flicker

Show a loading indicator only when loading takes longer than 300 ms. Once shown, keep it for at least 500 ms. Reserve the final size, so nothing jumps.

How to do it

Start a timer when the request starts, and show the indicator when it fires. Use a skeleton with the same height as the loaded view, which keeps layout shift at zero. Set aria-busy="true" on the region while it loads. Keep the old data on screen while a refresh loads, and mark it as updating.

Example

const SHOW_AFTER_MS = 300;
const MIN_VISIBLE_MS = 500;

export async function withLoadingIndicator<T>(region: HTMLElement, work: Promise<T>): Promise<T> {
  let shownAt = 0;
  region.setAttribute('aria-busy', 'true');
  const timer = setTimeout(() => { shownAt = performance.now(); region.dataset.loading = 'true'; }, SHOW_AFTER_MS);
  try {
    return await work;
  } finally {
    clearTimeout(timer);
    if (shownAt) await new Promise(done => setTimeout(done, Math.max(0, MIN_VISIBLE_MS - (performance.now() - shownAt))));
    delete region.dataset.loading;
    region.removeAttribute('aria-busy');
  }
}
import time
from playwright.sync_api import expect

WATCH_FOR_INDICATOR = """
new MutationObserver(() => {
  if (document.querySelector('[data-loading]')) window.sawIndicator = true;
}).observe(document, { subtree: true, childList: true, attributes: true });
"""

def delay_100ms(route):
    time.sleep(0.1)
    route.continue_()

def test_fast_load_never_shows_the_indicator(page):
    page.add_init_script(WATCH_FOR_INDICATOR)
    page.route("**/api/orders*", delay_100ms)
    page.goto("https://staging.example.com/orders")
    expect(page.get_by_role("table", name="Orders")).to_be_visible()
    assert page.evaluate("window.sawIndicator === true") is False

How to check

A browser test delays the data by 100 ms. A mutation observer records that no indicator appeared at any moment. With a 2 second delay, the indicator appears, and layout shift for the region stays under 0.01. With a 400 ms delay, the indicator stays for at least 500 ms.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/data-views/loading-without-flicker.