A route progress bar for SolidJS and SolidStart: the thin loading bar along the top of the page while the next route loads. The loading drift is one CSS transition, so you theme and retime it from CSS.
Documentation · Live demo · Example on StackBlitz
- With
@solidjs/routerit takes one component.<RouteProgress />followsuseIsRouting(), so links,navigate(), back/forward, action redirects and every<Suspense>the next route waits on show the bar. - The drift toward completion is a single CSS transition, and no JavaScript timer steps it. Color, height and timing are custom properties.
- A navigation shorter than
delay(200 ms) draws nothing, so quick loads never flash. - Routes,
track(fetch(...))and your ownstart()each hold the bar, and it completes when the last one lets go. - Navigations that leave the page (external links, form posts, reloads) show it too, through the Navigation API.
solid-route-progress/navigationworks without a router. - Styles sit in a cascade layer, so Tailwind v4 utilities win without
!important, anddata-stateworks as a variant. The bar renders on the server and is a labeledrole="progressbar"that followsdir="rtl"and forced colors. <RouteProgress />adds about 2.4 kB min+gzip to your bundle, and there are no dependencies. The bar only animatesopacityandtranslate, so it stays smooth on the compositor thread while the next route keeps the main thread busy.
npm i solid-route-progress
# or
pnpm add solid-route-progress
# or
yarn add solid-route-progress
# or
bun add solid-route-progressIt needs solid-js 1.9 or a later 1.x, and @solidjs/router 0.15 or 1.x for the router integration; Solid 2 is not supported yet. The bar draws in Chrome and Edge 111, Firefox 113 and Safari 15.4 or later; see browser support.
Import the stylesheet once:
/* src/app.css */
@import 'tailwindcss'; /* optional */
@import 'solid-route-progress/style.css';Render the bar once under <Router>. With SolidStart:
// src/app.tsx
import { Router } from '@solidjs/router'
import { FileRoutes } from '@solidjs/start/router'
import { Suspense } from 'solid-js'
import { RouteProgress } from 'solid-route-progress/router'
import './app.css'
export default function App() {
return (
<Router
root={(props) => (
<>
<RouteProgress />
<Suspense>{props.children}</Suspense>
</>
)}
>
<FileRoutes />
</Router>
)
}Theme it with custom properties, on :root or anywhere else:
:root {
--sp-color: oklch(0.62 0.19 264); /* or a Tailwind token: var(--color-indigo-500) */
--sp-height: 2px;
}Hold it for work that is not a navigation. Wrap the app in <ProgressProvider>, and useProgress() reaches the same bar from anywhere:
const progress = useProgress()
progress.track(fetch('/api/items')) // shows the bar until the request settlesWithout @solidjs/router, render <NavigationProgress /> from solid-route-progress/navigation instead. For server rendering, use a bundler that resolves the solid export condition, as SolidStart and vite-plugin-solid do.
| Page | Covers |
|---|---|
| Quick start | @solidjs/router, SolidStart, no router, and driving the bar yourself |
| Styling | custom properties, Tailwind utilities, state hooks, custom templates, recipes |
| Controller | createProgress() options, start(), done(), set(), track(), and the types |
| Components | <Progress>, <ProgressProvider>, useProgress(), <Bar> |
| Router integration | ignored links, navigations that leave the page, actions without a redirect |
| Navigation API | <NavigationProgress>, for apps without a router |
| Examples | live demos of each API |
The package ships these docs as Markdown for the installed version, in node_modules/solid-route-progress/dist/docs/ (start at README.md). To have your agent read them first, add a line to your AGENTS.md or CLAUDE.md:
Before using solid-route-progress, read node_modules/solid-route-progress/dist/docs/README.md.The site serves the same docs as llms.txt, llms-full.txt, and a .md copy of every page. There is also an agent skill for Claude Code, Codex, Cursor and other agents that read skills:
npx skills add kecan0406/solid-route-progress| There | Here |
|---|---|
minimum |
--sp-start |
trickleSpeed, easing, speed |
--sp-trickle-duration, --sp-trickle-easing, speed / --sp-speed |
trickle: false |
trickleTo equal to --sp-start: the bar reveals and waits for set() / done() |
color, height, template |
--sp-color, --sp-height, children |
showSpinner, spinnerPosition |
the spinner recipe, placed with your own top / inset-inline-end |
parent |
render the bar inside the container, class="absolute" |
direction: 'rtl' |
automatic under dir="rtl" |
startPosition, delay, stopDelay |
set(n), delay (default 200 ms), stopDelay |
shallowRouting, targetPreprocessor |
shallow, filter(to, from) |
disableSameURL |
built in: navigations the router drops never show a bar |
data-disable-progress, data-prevent-progress |
data-sp-ignore |
promise() |
track(promise); start() returns its own release |
inc(), dec(), pause(), resume() |
none: the drift is a CSS transition, so JS holds no current position to step or freeze. Stepwise loads: set(k / n). |
indeterminate |
none: trickling already reads as indeterminate to assistive tech; style data-state="trickle" as you like |
nonce, style, disableStyle, memo |
not needed: styles are a stylesheet you import, components are plain Solid |
Issues and pull requests are welcome. Read CONTRIBUTING.md first, and report security issues as described in SECURITY.md.
MIT
