# Navigation progress
A **navigation progress bar** is the thin line pinned to the very top of the page — the one
that trickles forward while a route loads and snaps to full when it arrives. Unlike most
components there's nothing static to drop into your prose: the bar is mounted once, then *driven
from JavaScript*. You `start()` it, `complete()` it, or `set()` it to any value between 0 and 100,
and it animates the rest.
The demo below ships as a project snippet (`snippets/NProgressDemo.jsx`) so you have something live
to click. It mounts the bar and wires three buttons to the API: **Run** starts the trickle and
finishes it after a beat (the way a page navigation looks), while **Start** and **Finish** expose
the two ends on their own. Click any of them and watch the line at the very top of this page.
Because the snippet is a project component, it's available as a tag with no setup:
```aardvark
{% NProgressDemo %}
```
## How it works
The bar is a single `` element — mount it once (the snippet does this for the
page), and from then on you move it with the imperative `nprogress` helpers rather than props:
- `nprogress.start()` — begin trickling toward the top edge (it eases forward but never reaches 100% on its own).
- `nprogress.complete()` — fill to 100% and fade out.
- `nprogress.set(value)` — jump to an exact percentage (0–100), e.g. tie it to upload progress.
- `nprogress.increment()` / `nprogress.decrement()` — nudge it by a step.
- `nprogress.reset()` — clear it back to the start with no animation.
That split — one mounted bar, an imperative API to drive it — is what makes it a snippet rather
than a one-shot tag: a static `{% tag %}` can't expose `onClick` handlers that call
`nprogress.start()`. The snippet owns both halves: it renders `` and the
controls that drive it. Customize it (different triggers, tie it to a real fetch, restyle the
buttons) by editing `snippets/NProgressDemo.jsx`.
## CSS Selectors
Every snippet instance mounts inside a wrapper carrying its island name, so you can target the demo
without touching the rest of the page:
```css
/* The mounted snippet wrapper (the controls live inside it). */
[data-aardvark-island="NProgressDemo"] {
margin-block: 1rem;
}
```
The bar itself is a Mantine `Progress`, so it carries that component's stable Styles API parts —
target the track with `.mantine-Progress-root` and the moving fill with `.mantine-Progress-section`:
```css
/* Recolor the moving fill of the top-of-page bar. */
.mantine-Progress-section {
background-color: var(--mantine-color-pink-6);
}
```
The bar's *own* layout (its fixed position at the top, the trailing glow) comes from
`@mantine/nprogress`'s stylesheet, which is hashed at build time (e.g. `.m_8f2832ae` for the bar
root). Those hashes change between Mantine releases, so prefer the stable `.mantine-Progress-*`
parts above when you restyle it.
## Injecting Attributes
`attr={...}` forwards raw HTML attributes straight onto the snippet's trigger — here the **Run**
button, which is where the snippet forwards its ref. Use it to attach an inline handler, a
`data-*` hook, or any attribute the component doesn't expose as a prop:
The live result — click **Run** to fire the injected `onclick` (it alerts the button's label)
*and* run the progress bar:
Source: Markdown
```aardvark
{% NProgressDemo attr={'onclick': '''
const value = this.innerText;
console.log('attr demo value:', value);
alert(value);
'''} %}
```
Source: Python
```python
component('NProgressDemo', attr={'onclick': '''
const value = this.innerText;
console.log('attr demo value:', value);
alert(value);
'''})
```
Attributes go on through a separate channel from React props, so they're written verbatim to the
DOM node and never collide with what Mantine manages. Avoid `class`/`style` there (the framework
owns those) — reach for the component's own props instead.