Skip to content

Navigation

Loading boundary

A loading UI that never flashes, plus a top progress bar and pending links for SvelteKit navigations.

<script>
  import { LoadingBoundary } from "@codeatlasdev/ui";
</script>

<LoadingBoundary label="Loading invoices">
  <Invoices /> <!-- contains: const rows = $derived(await load()) -->
</LoadingBoundary>

LoadingBoundary wraps <svelte:boundary> for content that suspends on await. It shows nothing for the first delay ms (200 by default), so a fast response never flickers a skeleton. Once the placeholder is visible it stays for at least minDuration ms (500), so it never flashes for one frame either. Under a second, show nothing; under ten, a skeleton (NN/g). While waiting the region is role="group" with aria-busy="true" and an accessible name. The default placeholder is rows skeleton lines; pass a pending snippet shaped like your content to keep the layout still. It needs the experimental.async Svelte compiler option in the app.

#255

Fast and slow loads

A 100 ms load shows nothing. A 1.5 s load shows the skeleton after 200 ms, then the rows.

Reference #255 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { Button, LoadingBoundary, Stack } from '@codeatlasdev/ui';
  import Report from './Report.svelte';

  let ms = $state(1500);
  let run = $state(0);
  const load = (wait: number) => () =>
    new Promise<string[]>((r) => setTimeout(() => r(['Invoice 1042', 'Invoice 1043', 'Invoice 1044']), wait));
</script>

<Stack gap="4">
  <Stack direction="row" gap="2">
    <Button variant="outlined" size="sm" onclick={() => { ms = 100; run++; }}>Fast (100 ms)</Button>
    <Button variant="outlined" size="sm" onclick={() => { ms = 1500; run++; }}>Slow (1.5 s)</Button>
  </Stack>
  {#key run}
    <LoadingBoundary label="Loading invoices">
      <Report load={load(ms)} />
    </LoadingBoundary>
  {/key}
</Stack>

Props

PropTypeDefaultDescription
childrenSnippet–The suspending content.
pendingSnippet–What to show while waiting. Defaults to skeleton lines.
delaynumber200Milliseconds to wait before showing the placeholder.
minDurationnumber500Minimum milliseconds the placeholder stays once shown.
labelstring–Accessible name of the busy region. Defaults to a localised 'Loading…'.
rowsnumber3Skeleton lines of the default placeholder.

NavigationProgress (from @codeatlasdev/ui/kit, because it reads navigating from SvelteKit’s $app/state) draws a thin bar at the top of the viewport while a client-side navigation is pending. It appears after delay ms (120), so quick navigations show nothing, and it sets data-ca-navigating on <html>. Mount it once in the root layout, next to RouteAnnouncer.

<script>
  import { NavigationProgress, RouteAnnouncer, pendingLink } from "@codeatlasdev/ui/kit";
</script>

<NavigationProgress />
<RouteAnnouncer />

<a href="/billing" {@attach pendingLink()}>Billing</a>

pendingLink() is a link attachment: the <a> being navigated to carries data-pending and aria-busy="true" until the page is ready, so you can style the tapped link (a[data-pending] { opacity: .6 }). The AppShell stays mounted through the navigation: only the page below it changes.

Props

PropTypeDefaultDescription
delaynumber120Milliseconds to wait before showing the bar.
labelstring–Accessible name. Defaults to a localised 'Loading…'.