Skip to content
WunderUI
HooksPro

useLoadSequence

Opens every dashboard page the same way: the cards build up top to bottom, then numbers roll, charts draw in and bars fill — with skeletons first only when the data takes longer.

Attach the hook to the root of your app shell, spread on it and on the page area, and pass the page's path as so every page opens with the sequence. The shell stays visible; nothing flies in. In , mark each card with and give it (when it appears) and (when its content starts); and turn card positions into rows, columns and delays (strictly top to bottom: a row 40 ms after the last card above it, +25 ms per card, the page built within about half a second). A card appears in place — opacity only, . Provide the phase through : the charts of hold their enter sequence until and start at , and rolls its digits while it is . Bars marked grow from their start edge. Pass while the data loads: if it is still not there after (400 ms) the phase turns — show skeletons in the cards' places — and stays there at least (500 ms) so it never flashes; data that arrives within the delay shows no skeleton.

See alsouseAnimatedNumberuseInViewAppearMetric Card

Import

tsx
import { useLoadSequence } from "@wunderui/react/charts"

Comes with WunderUI Core and Pro, in the entry of the package from your plan. See the plans.

Signature

ts
useLoadSequence(ref: React.RefObject<HTMLElement | null>, options?: LoadSequenceOptions): LoadPhase

Parameters

ParameterType
refReact.RefObject<HTMLElement | null>
options.enabled?Off: the phase is "done" from the start (nothing hidden, nothing moves).boolean | undefined
options.replayKey?Change it to play the sequence again: the page's path (every page opens with it) or a replay button.string | number | undefined
options.ready?Whether the page's data is there. While it is not, the sequence waits after LOAD_SKELETON_DELAY the phase turns "loading" (show skeletons).boolean | undefined
options.prepare?Called once the shell is on screen and the data is ready, right before the phase turns "play": mark the cards, set their delays, return how long the sequence runs. Return null while the content is not laid out yet (it is asked again on the…((root: HTMLElement, reduced: boolean) => LoadSequencePlan | null) | undefined

Returns

Examples

A dashboard shell with a load sequence

tsx
import * as React from "react"
import { LoadSequenceContext, loadDelays, readingOrder, useLoadSequence } from "@wunderui/react/charts"

export function DashboardLayout({ children, path, ready }: { children: React.ReactNode; path: string; ready: boolean }) {
  const ref = React.useRef<HTMLDivElement>(null)
  const phase = useLoadSequence(ref, {
    replayKey: path, // every page opens with the sequence
    ready, // false while the data loads: skeletons after 400 ms
    prepare: (root, reduced) => {
      const cards = Array.from(root.querySelectorAll<HTMLElement>("[data-card]"))
      if (!cards.length) return null // not rendered yet: asked again next frame
      const delays = loadDelays(readingOrder(cards.map((c) => c.getBoundingClientRect()).map((r) => ({ top: r.top, left: r.left }))))
      let last = 0
      cards.forEach((card, i) => {
        const delay = reduced ? 0 : delays[i]
        card.setAttribute("data-load-item", "")
        card.style.setProperty("--load-delay", `${delay}ms`)
        card.style.setProperty("--load-content-delay", `${delay + 150}ms`)
        last = Math.max(last, delay)
      })
      return { duration: last + 2000, finish: () => cards.forEach((c) => c.removeAttribute("data-load-item")) }
    },
  })
  return (
    <div ref={ref} data-load={phase}>
      <main data-load-main="">
        <LoadSequenceContext.Provider value={phase}>{children}</LoadSequenceContext.Provider>
      </main>
    </div>
  )
}

Skeletons only for longer loads

tsx
const { data, isLoading } = useQuery(...)
const phase = useLoadSequence(ref, { replayKey: path, ready: !isLoading, prepare })

// "loading" only when the data takes longer than LOAD_SKELETON_DELAY (400 ms), then at least LOAD_SKELETON_MIN (500 ms)
{phase === "loading" ? <DashboardSkeleton /> : <Dashboard data={data} />}

A KPI value that rolls in

tsx
import { NumberRoll } from "@wunderui/react/charts"

// rolls while the surrounding load sequence plays; plain text otherwise
<NumberRoll value="$48,200" className="text-3xl font-bold tabular-nums" />

Replay button

tsx
const [replay, setReplay] = React.useState(0)
const phase = useLoadSequence(ref, { replayKey: replay, prepare })

<Button onClick={() => setReplay((n) => n + 1)}>Replay</Button>

Notes

  • Every page opens with it: pass the route's path as . Re-renders on the same page never replay it.
  • Under reduced motion nothing animates: the page is shown as it is (nothing is held back), numbers and charts show their final state, and a skeleton for a longer load has no shimmer.
  • renders the formatted value as plain text on the server, before and after the roll, so it hydrates without a mismatch and always ends exact. Use tabular figures so the digit columns keep their width.
  • While and the page area's children are held back by the stylesheet (the shell is not). Add so pages without JavaScript still show their content.