How to Use
Get the package
Simply include the package through your favourite package manager:
npm
npm i @unseenco/taxi
yarn
yarn add @unseenco/taxi
pnpm
pnpm add @unseenco/taxi
Setting up
Next, you need to import Taxi.Core into your code and create a new instance:
import { Core } from '@unseenco/taxi'
const taxi = new Core()
// or if you prefer
import * as Taxi from '@unseenco/taxi'
const taxi = new Taxi.Core()
Then amend your HTML so that data-taxi is added to the parent of the content you want to replace during a transition, and data-taxi-view is added to the element you are replacing:
<main data-taxi>
<article data-taxi-view>
...
</article>
</main>
Please note: The data-taxi-view element has to be the only child of data-taxi.
Now when you navigate in your app, data-taxi-view will be replaced with the data-taxi-view from the target URL instead of the whole page loading 🥳
Via CDN
You can load Taxi as an ES module straight from a CDN. jsDelivr’s +esm endpoint also resolves Taxi’s dependency (@unseenco/e) for you:
<main data-taxi>
<article data-taxi-view>
...
</article>
</main>
<script type="module">
import { Core } from 'https://cdn.jsdelivr.net/npm/@unseenco/taxi@2/+esm'
const taxi = new Core()
</script>
Note: 2.0 no longer ships a UMD build, so the old
taxi.umd.js/ globaltaxiapproach is no longer available. See Upgrading to 2.0.
Which links are handled by Taxi?
Taxi will only transition links to a domain which is the same as the current URL (for obvious reasons).
By default, Taxi will not transition links which:
- have
data-taxi-ignorepresent on the link element; - are anchor links for the current page;
- have a
targetattribute present on the link element; - have a
downloadattribute present on the link element; - are clicked with a modifier key held (cmd, ctrl, shift or alt), so opening in a new tab/window or downloading works as normal;
- have had
preventDefault()called on their click event by your own code before it reaches Taxi.
Of course, you can always change this behaviour using the links option.
Options
When creating a new Taxi instance, you can pass an object of options into the constructor:
const taxi = new Core({ ... })
Let’s look at these in more detail.
renderers
Record<string, Renderer>
Please see Renderers for more information.
transitions
Object.<string, Transition>
Please see Transitions for more information.
links
string
Links is a CSS selector which Taxi uses to decide if a clicked link should be transitioned or not.
Here is the default value:
const taxi = new Core({
links: 'a[href]:not([target]):not([href^=\\#]):not([data-taxi-ignore])'
})
As you can see the default value ignored links with a target attribute, is an anchor link on the current page, or has data-taxi-ignore present.
You can use this option to extend this behaviour and fine tune which links are considered valid.
removeOldContent
boolean
Taxi will remove the previous page’s content after the Transition’s onLeave method has finished. Set this to false to disable this behaviour.
allowInterruption
boolean
Taxi blocks further navigation while a transition is in progress. Set this to true to disable this behaviour.
When a new navigation interrupts one in progress, the interrupted navigation’s request is aborted and its navigateTo() Promise rejects with an AbortError. Any preloads still downloading are left alone, so the new navigation can reuse them.
bypassCache
boolean
Default behaviour is to cache the contents of a URL after fetching it to make repeated visits faster. Set this to true to disable the cache completely.
If you want default behaviour, but wish to force certain pages to always be fetched (and never loaded from cache), you can add the data-taxi-nocache attribute to the data-taxi-view element on that page.
enablePrefetch
false | 'hover' | 'visible'
Controls the automatic prefetch strategy. Defaults to 'hover'.
| Value | Behaviour |
|---|---|
'hover' |
Preloads a link when the user hovers over or focuses it (mouseenter/focus) |
'visible' |
Uses IntersectionObserver to preload links as they scroll into the viewport |
false |
Disables automatic prefetching entirely |
// Preload links as they scroll into view
const taxi = new Core({
enablePrefetch: 'visible'
})
// Disable prefetching
const taxi = new Core({
enablePrefetch: false
})
When using 'visible':
- only links to the current domain are observed;
- nothing is prefetched if the user has enabled a data saver mode (
navigator.connection.saveData); - links are re-scanned after each navigation. If you add links to the page yourself (e.g. an infinite loader), call
updateCache()afterwards and they will be picked up too.
Note:
enablePrefetch: trueis still accepted and maps to'hover'for backwards compatibility.
enableViewTransitions
boolean
Opt in to the browser’s View Transitions API for page swaps. Defaults to false.
When enabled and the browser supports it, Taxi wraps the DOM swap (remove old page, insert new page) inside document.startViewTransition(), giving you:
- A default cross-fade animation with no extra code
- Element-to-element transitions via
view-transition-nameCSS
const taxi = new Core({
enableViewTransitions: true
})
Apply a custom CSS transition by naming elements:
.hero {
view-transition-name: hero;
}
Note: When
enableViewTransitionsis active, custom JSTransitionclasses are bypassed — the browser handles the visual animation. Renderer lifecycle hooks (onLeave,onEnter, etc.) still fire as normal, butonEnterCompleted(and theNAVIGATE_ENDevent) now wait for the browser’s animation to visually finish before firing, rather than firing as soon as the new content is in the DOM.onLeave/onLeaveCompletedare unaffected and still fire immediately, since they’re JS-side bookkeeping hooks rather than part of the visual animation. Browsers that don’t support the API fall back to the standard behaviour automatically. Users who prefer reduced motion get an instant swap with no animation.
See View Transitions for a deeper explanation and a live demo, including how to scope the animation to a single element (e.g. a specific <div>).
enableAccessibility
boolean
Opt in to accessibility handling for page changes. Defaults to false.
After a normal page load, screen readers announce the new page and keyboard focus starts at the top of the document. After an AJAX navigation neither happens: focus stays on a link that no longer exists and nothing is announced. With enableAccessibility: true, after each navigation Taxi:
- announces the new page’s
<title>through a visually hiddenaria-liveregion (added to the end of<body>with adata-taxi-announcerattribute); - moves focus to the first
<h1>in the newdata-taxi-view, or to thedata-taxi-viewelement itself if there is no<h1>. The element getstabindex="-1"so it can take focus, and focus happens withpreventScrollso your scroll handling is unaffected.
const taxi = new Core({
enableAccessibility: true
})
Browsers show a focus outline on the focused element. If you’d rather not show it for these programmatically focused elements:
[data-taxi-view] h1:focus:not(:focus-visible),
[data-taxi-view]:focus:not(:focus-visible) {
outline: none;
}
maxCacheSize
number
By default Taxi caches every page it visits indefinitely. Set maxCacheSize to a positive integer to limit how many pages are kept in the cache at once.
When the limit is reached, the least recently used page (that isn’t the current page) is evicted to make room. Visiting or preloading a cached page counts as using it. Set to 0 (default) for unlimited caching.
const taxi = new Core({
maxCacheSize: 10
})
fetchOptions
RequestInit
An object of options merged into every fetch() request Taxi makes. Use this to add custom headers, change credentials mode, etc.
const taxi = new Core({
fetchOptions: {
headers: { 'X-My-Header': 'value' },
credentials: 'include',
}
})
reloadJsFilter
bool|function(element: HTMLElement)
Please see Reloading JS for more information.
reloadCssFilter
bool|function(element: HTMLLinkElement|HTMLStyleElement)
Please see Reloading CSS for more information.