# Taxi.js and what a drop-in Highway.js replacement had to change

> Taxi.js is a small vanilla JavaScript library for AJAX navigation and page transitions, written as the successor to the unmaintained Highway.js. It kept the idea and changed most of the API, so the interesting question for an adopter is what breaks and what improves when you move across.

**craftedbygc/taxi** — Taxi is a 🤏 small and 🐊 snappy js library for adding slick PJAX navigation and beautiful transitions to your website.

- Repository: https://github.com/craftedbygc/taxi
- Website: https://taxi.js.org
- Stars: 643 · Forks: 14
- Language: JavaScript
- License: BSD-3-Clause
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/craftedbygc-taxi

## A spiritual successor, not a fork

The README states the position plainly: Taxi.js is the spiritual successor to Highway.js. Taxi itself was designed as a drop-in replacement for Highway.js, which is, in the project's words, sadly no longer maintained.

So the reference point matters. Anyone who used Highway.js already knows the shape of this kind of tool: intercept a link click, fetch the new page, swap the content, animate the transition. Taxi's own description is a js library for adding AJAX navigation and beautiful transitions to your website, and the repository description calls it a small and snappy js library for adding slick PJAX navigation and beautiful transitions.

The technical identity is in the package manifest. The package is @unseenco/taxi, it is a module type package, and it ships both a CommonJS and an ES module build with separate type definitions for each:

```json
  "main": "./dist/taxi.cjs",
  "module": "./dist/taxi.js",
  "types": "./types/taxi.d.ts",
```

The exported conditions map an import to the ES build and a require to the CommonJS one, each with its own types entry. That is a modern package shape, and it is a change from the 1.x line that the README flags with an upgrade guide and a CHANGELOG link for anyone upgrading from 1.x.

The runtime dependencies are almost nothing. The only dependency is @unseenco/e at version 3 or newer:

```json
  "dependencies": {
    "@unseenco/e": "^3.0.0"
  },
```

The package is also marked sideEffects: false, which is what tells a bundler that importing it has no side effects and can be tree shaken. For a library that does DOM work when initialised, that declaration is a deliberate contract with the bundler, and it is worth knowing about if you are the one writing the initialisation code.

## What Highway did not have, and what that costs

The README lists the enhancements over Highway, and reading them as a group tells you what the author thought was missing.

URL-based routing is first, which means the transition is tied to a URL rather than to an internal state machine. Better cache management comes next, then the ability to preload URLs, which is the feature that makes the technique feel fast rather than merely smooth. Blocks navigation during an active transition, and that one can be opted out of. Auto runs javascript on the new page. Previous page's content is automatically removed, and that too can be opted out of. And click events on links can be intercepted via stopPropagation without hacks.

The last two are the most interesting, and they cut in opposite directions. The stopPropagation point is a real design difference: in a document-level delegated listener you can decide whether a click is yours to handle, and the library says you can do that without the workarounds that were needed in older implementations. The automatic removal of the old content is the opposite kind of change, because it removes a decision from you. The README is explicit in the differences section that old content is automatically removed during a transition, so there is no need to manually call from.remove() in your transitions.

That default is a genuine trade. Automatic cleanup means a transition that forgets to remove the old node does not leak it, and the common case is shorter. It also means a transition that wants to keep the old content on screen, which is what a cross-fade needs, has to opt out of the library's behaviour rather than get it for free.

The differences section also makes clear that this is not a drop-in in the literal sense, despite the intent. The public API is different, there are new methods and functionality, and the parameters passed to renderers, transitions and events are a little different.

## The data attributes all changed, so a migration is a rename

The single most concrete difference from Highway is the data attribute names, and it is the part of the upgrade that will show up as a silent breakage if you are not careful.

The README states that data-taxi, data-taxi-view and data-taxi-ignore are to be used instead of data-router-wrapper, data-router-view and data-router-disabled respectively.

So the wrapper element that holds the transition target, the view that identifies which part of the page to replace, and the opt-out for elements that should not be intercepted all carry new names. Because these are attributes rather than API calls, a missed rename does not throw. The markup keeps working as plain HTML, the library simply does not find the elements it expects, and the site quietly stops transitioning. That is the worst kind of upgrade failure, and it is the reason to read the upgrade guide rather than to diff the library.

The method changes are easier to spot. attach and detach are no longer methods, because link clicks are listened to via delegation so these are no longer needed. redirect is now navigateTo, and the README gives the reason: as a method name, redirect felt weird.

Renderers now have an initialLoad method, which is the piece that handles the first page load so that the same code path serves both the initial render and later navigations.

So the migration from Highway is: rename three attributes, drop attach and detach calls, rename redirect to navigateTo, add an initialLoad method to your renderers, and expect the arguments passed to renderers, transitions and events to have shifted. The last one is the one the documentation cannot fully enumerate for you, and it is where you will look at the reference in the docs directory.

## The repository shows a full modern build pipeline

The top level of the repository describes a library that takes its tooling seriously, and it says something about where the project has moved.

The documentation site is built with Astro, there is an astro.config.mjs and a docs directory, and the full documentation lives at taxi.js.org. The bundling is Vite, with a vite.config.mjs, the tests run on Vitest with a separate vitest.config.js, and TypeScript is configured through tsconfig.json with hand written type declarations shipped in a types directory.

The build script shows the order of operations:

```json
    "ts": "tsc && node scripts/build-cjs-types.js",
    "test": "vitest run",
    "test:coverage": "vitest run --coverage",
    "dev": "astro dev",
    "build": "npm-run-all build:astro ts bundle",
```

The ts step is not just tsc. It also runs a script called build-cjs-types.js, which is how the separate CommonJS type declarations in the export map get produced, since tsc alone would emit one set of declarations for one module format.

The test environment is jsdom, so the tests run in a simulated DOM rather than a real browser, and coverage is available through the v8 provider. The dev dependencies also include GSAP, which tells you that the documentation site uses a real animation library to demonstrate the transition capability, and Astro plus Tailwind CSS version 4 to build the docs.

The published files are only dist and types, so the repository's tests, docs and build configuration never reach a consumer's node_modules. There is a .nojekyll file, which is a GitHub Pages setting, and a scripts directory that holds the build-cjs-types.js script.

## A version number worth checking before you install

There is a discrepancy in the visible project metadata that anyone installing from npm should resolve first.

The recent releases listed for the repository are 1.8.0 on 2025-02-08, 1.7.0 on 2024-09-17 and 1.6.0 on 2024-05-15, the last of which is noted as adding inline style reloading support. But the package manifest in the repository declares version 2.0.0, and the type module, the export map and the separate CommonJS types all belong to that 2.x shape.

The most likely reading is that 2.0.0 is the version under development on the main branch, with 1.8.0 being the last published release. The README's note about an upgrade guide for people upgrading from 1.x fits that reading exactly, because the 2.x line is where the API differences live.

That distinction matters more than it usually would, because this library's breaking changes are mostly in attribute names and method names rather than in behaviour. Installing 1.8.0 gives you a different API surface from 2.0.0, and a tutorial or an answer on a forum written against one of them will mislead you about the other.

The maintenance picture is otherwise healthy. The repository is not archived and the last push was on 2026-09-27, so the 2.x work is active. The licence is BSD-3-Clause, with the LICENSE.md file at the repository root, which is a permissive licence and unremarkable for a library of this kind.

One naming detail to keep in mind when you look for the package. The npm package is @unseenco/taxi, the author is Jake Whiteley of Unseen Studio Ltd, and the repository URL in the manifest points at github.com/craftedbygc/taxi. The organisation and the package scope differ, which is a small thing that trips up people searching for the source.

## Where Taxi.js is the wrong tool

The first wrong case is a site whose pages need a server round trip to be correct. Taxi swaps a full document load for an AJAX fetch and a content swap, and everything that came with the full load has to be re-established by the library or by you. The README says it auto runs javascript on the new page, and the 1.6.0 release note mentions inline style reloading support, which is exactly the category of problem this creates. A site with a global cart, a session header, or a server rendered permission check needs to re-run or re-fetch that state on every navigation, and the library cannot know what your site does.

The second wrong case is a modern application that already has a router. If your site is a React or Vue application with client side routing, adding a document swapping router on top creates two routers. The same applies to a site using the browser's own view transition API, where the transitions you would write here compete with a mechanism the platform now provides.

The third wrong case is expecting the library to give you transitions. It provides the mechanism, and the animations are yours. The documentation site uses GSAP to demonstrate what that looks like, and that dependency is a dev dependency of the docs, not of the library. A team expecting a set of ready-made page transitions will find a routing and swapping tool instead.

The alternative worth naming is a full framework router, or the platform's own navigation with CSS view transitions, or simply not intercepting links. The difference in approach is that Taxi is a transport swap with a lifecycle, and it leaves the visual and the application state to you. A framework router owns the state, mounts components, and gives you a transition hook as part of that. The trade is the same one this library's predecessors faced: a small, framework free, drop-in approach that you assemble, against a larger dependency that owns more of the problem. For a marketing site, a documentation site or a server rendered application that can be made to work with a content swap, Taxi is the lighter option. For an application with real client side state, it is the wrong shape.

## Conclusion

Taxi.js fits a static or server-rendered site that wants SPA-like navigation and transitions without adopting a framework, and that is willing to give up full page loads in exchange for it. It is the wrong choice if your pages depend on a server round trip for correctness, or if you need view transitions, since the transitions are yours to write, and it is the wrong choice if you are still on Highway.js and intend to stay, because the API differences are large enough to be a migration rather than a swap. Before you adopt it, read the upgrade guide linked from the README, check that the 2.0.0 version in the package manifest matches the release you plan to install, and confirm your links are real anchors rather than a framework's click handlers.

## FAQ

### How do I install Taxi.js?

The README gives npm i @unseenco/taxi or yarn add @unseenco/taxi as the two commands, and the package is published on npm under the @unseenco scope. The full documentation is at taxi.js.org.

### What is the difference between Taxi.js and Highway.js?

The data attributes changed, so data-taxi, data-taxi-view and data-taxi-ignore replace data-router-wrapper, data-router-view and data-router-disabled. attach and detach are no longer methods since link clicks use delegation, redirect is now navigateTo, renderers have an initialLoad method, and the parameters passed to renderers, transitions and events differ.

### What does Taxi.js add over Highway.js?

The README lists URL-based routing, better cache management, the ability to preload URLs, blocking of navigation during an active transition with an opt out, automatic running of javascript on the new page, automatic removal of the previous page's content with an opt out, and intercepting of link click events via stopPropagation without hacks.

### Does Taxi.js have any runtime dependencies?

One. The package manifest lists @unseenco/e at ^3.0.0 as its only dependency, and the package is marked sideEffects: false. Everything else in the manifest, including GSAP, Astro and Tailwind, is a dev dependency used to build the documentation.

### What licence is Taxi.js released under?

BSD-3-Clause, with the licence text in the LICENSE.md file at the repository root. The package manifest records the same licence identifier.

## Sources

- [craftedbygc/taxi on GitHub](https://github.com/craftedbygc/taxi)
- [License: BSD-3-Clause](https://github.com/craftedbygc/taxi/blob/main/LICENSE)
- [Project website](https://taxi.js.org)
- [README](https://github.com/craftedbygc/taxi/blob/main/README.md)
- [Releases](https://github.com/craftedbygc/taxi/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/craftedbygc-taxi
