Yugen UI

PageProgress

A thin brand-colored progress bar along the top edge: signals that the app is working during route changes or background loads without covering what you're reading.

Two modes: determinate (value 0–1) when the real progress is known, and trickle (active), which creeps toward ~94%, then runs to 100% and fades out when done. The imperative controller pageProgress.start() / .done() / .set(n) can be called from anywhere (a router, a fetch wrapper) without context. start/done calls are counted, so overlapping tasks still finish at the right moment. With prefers-reduced-motion, the width doesn't animate and the bar doesn't fade.

Overview

The old page stays in place while the new one loads.

Import

Install from npm, copy the source into your project with @yugen-ui/cli or the shadcn CLI, or import directly when working in this repo.

TSX
// npm install yugen-uiimport { PageProgress, PageProgressFallback, pageProgress, usePageProgress } from "yugen-ui";

Variants

Each tile is a standalone example: hover to copy, or press Code to see the source.

No file yet

Determinate (value)

When the real progress is known (a file upload), pass value; add active so the bar only shows during the upload.

The bar runs along the top edge of the window. Click several times: it only finishes once every sync is done.

Imperative controller

In an app, mount one <PageProgress /> at the root and call pageProgress.start() / .done(). The example uses its own createPageProgress() so it doesn't touch the docs site's bar.

Suspense & lazy routes

PageProgressFallback as the Suspense fallback: the bar runs while the chunk loads, with no spinner in the middle of the page.

Usage

Do
  • Mount one PageProgress at the app root and drive it with pageProgress or PageProgressFallback.
  • Use value when there is real progress; use trickle when all you know is that work is running.
  • Keep the old page content visible while the new page loads.
Don't
  • Don't use PageProgress for a local action inside one region. Use LoadingOverlay or Button loading.
  • Don't forget to call done() in finally, or the bar stays at ~94% forever.
  • Don't make the bar thick or give it a status color. It is a background signal, not a notification.

Props

Prop · TypeDefaultDescription
valuenumberDefault: —0–1, determinate mode; ≥ 1 → completes, then fades out.
activebooleanDefault: —true → shows the bar (trickle if there is no value); false → runs to 100%, then fades out.
controllerPageProgressControllerDefault: pageProgressController used when value / active aren't passed.
position"fixed" | "absolute"Default: "fixed"fixed: top edge of the viewport (portal, z-50) · absolute: top edge of the relative parent.
thickness"medium" | "large"Default: "medium"2px / 4px.
labelstringDefault: "Loading page"aria-label of the progressbar.
pageProgress.start() / .done() / .set(n) / .reset()PageProgressControllerDefault: —Imperative control; start/done calls are counted. createPageProgress() creates a separate controller.
PageProgressFallback / usePageProgress(active?)component / hookDefault: —Runs the bar while mounted (as a Suspense fallback) or while active.

Messages

Text built into the component: labels, aria-labels, screen reader announcements.

English by default. Change it with the messages prop of YugenProvider, by the namespace below; messagesVi has the Vietnamese set. See Interface language.

messages.pageProgress

Key · TypeDefault (English)messagesVi
labelstring

aria-label của progressbar.

Default (English)"Loading page"messagesVi"Đang tải trang"