npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

alpine-turnout

v1.2.4

Published

AlpineJS Turnout Switch

Downloads

181

Readme

License: MIT Gzipped Size Version Alpine.js

Alpine Turnout

Alpine Turnout Logo

A lightweight SPA Switch for Alpine.js built for speed and state persistence.

Unlike traditional routers that destroy and recreate DOM elements on every navigation, Alpine Turnout focuses on DOM preservation.

It treats your routes like railroad tracks: every section stays "alive" in the DOM. This preserves the internal state—meaning input fields, scroll positions, and component variables remain exactly as the user left them—while the "Turnout" logic reactively switches the view and URL to the correct destination.

Persistent Switch

Why Turnout?

  • 🛤️ Zero-Config: Set up your HTML layout normally and use the x-route directive to declare your "tracks." No complex routing manifests required.

  • 💾 State Persistence: Forms, scroll positions, and component data are preserved. When you navigate away and back, everything is exactly where you left it.

  • Instant Switching: No re-mounting or re-fetching logic on every click. It’s the fastest way to navigate an Alpine app.

  • 🍦 Alpine-Native: Built specifically for the Alpine ecosystem. It uses a global store and works with a single declarative directive.

  • 🎬 Seamless Transitions: Works out-of-the-box with Alpine's built-in x-transition for smooth UI entries and exits.

  • 🪶 Ultra-Lightweight: The entire library is only 3 kB. High performance with zero bloat.

  • 🔍 SEO Ready: Since your content lives in the DOM, it remains fully indexable by search engines like Google and DuckDuckGo.

Example Code Snippet

<div x-data>
    <nav>
        <a href="/">Home</a>
        <a href="/user/123">User Profile</a>
    </nav>

    <section x-route="/">
        <h1>Welcome Home</h1>
        <input type="text" placeholder="I stay alive!">
    </section>

    <section x-route="/user/:id">
        <h1 x-text="'User Profile: ' + id"></h1>
    </section>
</div>

No config, just your Alpine.js HTML as you are used to. Add a x-route = /user/:id attribute and have your id directly available!


Kickstart

Turnout Playground Example 1

Turnout Playground Example 1

Turnout Playground Example 2

Turnout Playground Example 2

Turnout Playground Example 3

Turnout Playground Example 3


Installation

Via CDN (recommended)

Include the script before Alpine.js:

<script src="https://unpkg.com/alpine-turnout" defer></script>
<script src="https://unpkg.com/alpinejs" defer></script>

Via NPM module

Install Alpine Turnout:

npm install alpine-turnout

Initialize Alpine.js and Alpine Turnout as modules with the following code:

import Alpine from 'alpinejs';
import AlpineTurnout from 'alpine-turnout';

Alpine.plugin(AlpineTurnout);
Alpine.start();

Then launch your dev environment with vite:

npm run dev

Usage

1. Define your tracks(/routes)

Create a nice layout in html. Then use the x-route and x-title directives:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Alpine Turnout</title>
    <script src="//unpkg.com/alpine-turnout" defer></script>
    <script src="//unpkg.com/alpinejs" defer></script>
    <link rel="stylesheet" href="//unpkg.com/@picocss/pico">
</head>
<body class="container" x-data="{}">

    <h1 x-data x-text="$store.turnout.title"></h1>

    <nav>
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/user/john">Profile</a></li>
            <li><a href="/search">Search</a></li>
        </ul>
    </nav>

    <article>
        <div x-route="/" x-title="Welcome Home" x-transition>
            <p>This is the homepage.</p>
        </div>

        <div x-route="/user/:name" x-title="User Profile" x-transition>
            <p>Hello, <strong x-text="name"></strong>!</p>
        </div>

        <div x-route="/search" x-title="Search" x-transition>
            <div x-data="{ query: '' }">
                <input type="text" x-model="query" placeholder="Type here...">
                <p>Your input is preserved even if you switch tabs!</p>
            </div>
        </div>
    </article>

</body>
</html>
  • Go Here for a more extensive live example!

  • Go Here for a more functional note app live example!

  • Go Here for a demonstration studio app live example!

  • Go Here and check out our /examples/* directory for more.

2. Navigation

Turnout automatically intercepts any internal <a href="/user/john">Visit John</a> links. You can also navigate programmatically:

<button @click="$store.turnout.go('/user/john')">Visit John</button>

How it Works

When you define an x-route, Alpine Turnout does three things:

  1. Registers the path: Adds the pattern to a global registry.

  2. Injects Scope: Makes route parameters (like :name) available directly to the HTML inside that div.

  3. Manages Visibility: Uses x-show logic under the hood. When the URL matches, the "track" becomes visible; otherwise, it is hidden with display: none.


API Reference

Global Store: $store.turnout

Property | Type | Description --- | --- | --- path | String | The current URL pathname. title | String | The value of x-title for the active route. notFound | Boolean | True if the current path matches no registered routes. go(path) | Function | Programmatically navigate to a new route.

Directives

x-route="[path]"

Used on a div or section to define a "track". Elements with this directive are automatically toggled based on the URL.

  • Static Routes: x-route="/about"

  • Dynamic Routes: x-route="/post/:id" (makes id available in local scope).

  • Wildcard (Custom 404): x-route="*"

x-arrive="[expression]"

The "Arrival" hook. Fires every time the route becomes active.

  • Use this to fetch fresh data or reset a form when the user navigates to the page.

  • Example: x-arrive="getWeather()"

x-leave="[expression]"

The "Departure" hook. Fires when the user navigates away from this route.

  • Use this to stop timers, cancel requests, or save draft data.

  • Example: x-leave="stopAutoRefresh()"


Features & Behavior

📜 Scroll Memory

Alpine Turnout automatically remembers the scroll position of every route.

  • When navigating via links, it returns you to your previous scroll depth on that specific "track".

  • When clicking a new unique path for the first time, it defaults to the top (0,0) with a smooth behavior.

⚓ Anchor Support (#hash)

The router detects fragment identifiers. If a URL contains a #, the router will:

  • Resolve the correct x-route.

  • Wait for the DOM to render.

  • Smoothly scroll the element with the matching id into view.

🛰️ Link Interception

<a> tags are intercepted automatically if they point to an internal path (starting with /).

  • Internal links: Trigger an Alpine Turnout "switch" without a page reload.

  • External links: Ignored by the router, allowing standard browser behavior.

Default 404 Behavior

If no x-route="*" is found and the user hits an unregistered path, Turnout automatically injects a "Dead End" 404 section into your main element to prevent a blank screen.

Transitions

Because Turnout uses Alpine's visibility toggling, you can use standard transitions. Note that we recommend setting a leave.duration.0ms if you want the "old" page to disappear instantly while the new one fades in.

<div x-route="/fast" 
     x-transition.duration.500ms 
     x-transition:leave.duration.0ms>
    ...
</div>

Comparison with alpine-router(s)

Subject | alpine-router(s) | alpine-turnout --- | --- | --- DOM Logic | Destroys/Creates | Hides/Shows (Persistent) State | Reset on nav | Preserved (Forms/Input) Performance | Lower Memory | Faster Switching Best For | Massive apps | One-pagers & Dashboards


SEO Proof

Most modern routers (React Router, Vue Router) are "empty" until JavaScript runs. Bots often see a blank page on the first pass.

Alpine Turnout’s Edge: Since all your "tracks" (the divs with x-route) are physically present in your HTML file, a crawler like Googlebot sees all your content immediately when it reads the source code.

The Result: Your internal pages are indexed much more easily than with a standard SPA.


🚀 Deployment

Since this is a Single Page Application (SPA) using the History API, your web server should be configured to serve index.html for all requests that don't match a static file.

Example for Nginx:

location / {
    try_files $uri $uri/ /index.html;
}

Example for Netlify:

Simply include a file named netlify.toml in the publish directory of your repository:

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

🧪 Testing

This project is tested using Vitest and JSDOM. Because Alpine.js initializes asynchronously, the test suite ensures that routes are correctly registered and cleared.

To run the tests:

npm install
npm test

Test Cases

Our suite covers the following test cases:

  • initializes and shows the home route by default

  • navigates to a parameterized "track" and updates the view

  • updates parameters reactively without re-mounting the element

  • renders a 404 terminal when a "track" is not found

  • persists state (like attributes or input) when switching "tracks"

  • intercepts internal links and prevents default behavior

  • ignores external links and allows standard navigation

  • triggers x-arrive when a route becomes active

  • triggers x-leave when moving away from a route


⚖️ License

MIT © Github NPM