# New SPA navigation package for Browser.element

**URL:** <https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565>\
**Category:** Show and Tell\
**Created:** [February 24, 2026, 6:36pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565 "2026-02-24T18:36:53Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)\
**Post date:** [February 24, 2026, 6:36pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565/1 "2026-02-24T18:36:53Z")

</div>

I’ve published a small package that gives you SPA-style URL navigation using `Browser.element` and ports, without needing `Browser.application`.  
I’ve been doing this thing for many apps, so it was time I just made a helper package for myself and whoever might find it useful.

**Elm:** [mpizenberg/elm-url-navigation-port](https://package.elm-lang.org/packages/mpizenberg/elm-url-navigation-port/latest/)  
**JS companion (npm):** [elm-url-navigation-port](https://www.npmjs.com/package/elm-url-navigation-port)  
**Source + example:** [GitHub](https://github.com/mpizenberg/elm-url-navigation-port)

[![Demo app using this package](https://github.com/user-attachments/assets/174c105f-a600-4cb7-a873-d71f381d4c10)](https://github.com/user-attachments/assets/420fd233-3313-4258-a792-5da3fd8e3c51)

## Why?

I often recommend staying on `Browser.element` for its simplicity and better compatibility with external JS libraries and browser extensions. But it doesn’t come with URL navigation built in. This package fills that gap with two ports and a tiny JS companion (~50 lines).

As a bonus, it exposes `history.pushState` with state objects — something `Browser.application` doesn’t offer. This enables patterns like multi-step wizards that support the back button without changing the URL.

## What it provides

Four navigation patterns:

1. **`pushUrl`** — standard SPA page navigation
2. **`pushState`** — push a state object without changing the URL (great for wizard steps)
3. **`back` / `forward`** — traverse history by N steps
4. **`replaceUrl`** — cosmetic URL updates without notifying Elm

All URLs go through [lydell/elm-app-url](https://package.elm-lang.org/packages/lydell/elm-app-url/latest/), so they’re always same-origin by construction.

The [example/](https://github.com/mpizenberg/elm-url-navigation-port/tree/main/example) directory has a full working demo with all four patterns. That’s the video demo at the top of this post.

Enjoy!

---

<div class="post-metadata">

**Author:** ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)\
**Post date:** [February 24, 2026, 7:57pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565/2 "2026-02-24T19:57:00Z")

</div>

Karma. @lydell

New version 1.1.0 that adds the following function:

```elm
{-| Navigate to a URL with a state object, creating a new history entry.

The JS companion calls `history.pushState(state, "", url)` and
notifies Elm with both the new URL and the state. Use this when
you need to attach metadata to a history entry alongside a URL
change (e.g. scroll position, referrer context).

    Nav.pushUrlWithState navCmd
        (AppUrl.fromPath ["product", "42"])
        (Encode.object [( "scrollY", Encode.int 250 )])

-}
pushUrlWithState : CommandPort msg -> AppUrl -> Encode.Value -> Cmd msg

```

> **[Navigation - elm-url-navigation-port 1.1.0](https://package.elm-lang.org/packages/mpizenberg/elm-url-navigation-port/1.1.0/Navigation#pushUrlWithState)**

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [February 24, 2026, 9:24pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565/3 "2026-02-24T21:24:44Z")

</div>

> [@mattpiz](#):
>
> I’ve published a small package that gives you SPA-style URL navigation using `Browser.element` and ports, without needing `Browser.application`.

I do this too, for 2 reasons. One is that Browser.Navigation.Key is opaque which is a problem for testing. The other is that as an element you can decide where the Elm application sits within the overall DOM, and pushing it down a div or 2 helps with virtual-dom/browser plugin conflicts.

---

<div class="post-metadata">

**Author:** ![dirkbj](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dirkbj/32/3162_2.png) [@dirkbj](https://discourse.elm-lang.org/u/dirkbj)\
**Post date:** [February 26, 2026, 8:11pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565/4 "2026-02-26T20:11:52Z")

</div>

This is great! This will fit in nicely with some of the apps we write. Thanks for sharing this. 🙂

---

<div class="post-metadata">

**Author:** ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)\
**Post date:** [February 26, 2026, 9:47pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565/5 "2026-02-26T21:47:48Z")

</div>

Awesome, happy to see it be of use to others!

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [March 8, 2026, 9:48pm UTC](https://discourse.elm-lang.org/t/new-spa-navigation-package-for-browser-element/10565/6 "2026-03-08T21:48:00Z")

</div>

Closed on March 8, 2026, 9:48pm UTC

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
