Feedback
RouteProgress
The thin bar across the top of the screen while the app loads the next page or submits a form, so a click that waits on the server never feels frozen: one global affordance instead of a pending state on every action. Once active has held for 150ms the bar fades in, and screen readers hear "Loading" once; a faster navigation shows and says nothing. While it shows, it creeps toward 90% of the width, more slowly the nearer it gets; when active turns false it fills to the end and fades out. With reduced motion it is the whole width from the start and only fades.
In Spoonjoy
Spoonjoy, a recipe app, is built with Ouro HX (how).
See it live, no sign-in needed: Home · A recipe · Recipes
In its source: app/root.tsx
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/route-progress#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/route-progressIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/route-progress#v1.2.13 ourostack/ouro-ui/route-progress-tests#v1.2.13import { RouteProgress } from "@/components/ouro/route-progress/route-progress";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
// React Router, in the root route:
const navigation = useNavigation();
<RouteProgress active={navigation.state !== "idle"} />Usage
Render it once, in the root layout, with your router's navigation state.
Use instead when
- Use
Progressinstead for a task whose amount is known,Spinnerinside the control that is busy, andButton'sloadingfor the one action a person just took.
Props
Type: RouteProgressProps.
active(required)- Type:
boolean - Whether a navigation or a form submission is under way: in React Router,
useNavigation().state !== "idle"; in Next.js, the pending state ofuseTransitionaroundrouter.push. className- Type:
string - Layout only: rarely needed, since the bar places itself at the top of the screen. Appearance classes warn in development.
label- Type:
string - Default:
"Loading" - What screen readers hear when a navigation starts.
ref- Type:
Ref<HTMLDivElement> - Reaches the root, the status region.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
isLoading: Not a prop. Ouro UI usesactive:<RouteProgress active={navigation.state !== "idle"} />.loading: Not a prop. Ouro UI usesactive:<RouteProgress active={navigation.state !== "idle"} />.value: Not a prop. RouteProgress shows that something is loading, not how much; useProgressfor a known amount.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: RouteProgress merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.
Claim sources
- layout-classes: test:registry/ouro/components/ouro/route-progress/route-progress.test.tsx::RouteProgress > forwards a ref and lets app layout classes win
verify:testExists(registry/ouro/components/ouro/route-progress/route-progress.test.tsx::RouteProgress > forwards a ref and lets app layout classes win)