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.1.1

Published

AlpineJS Turnout Switch

Readme

Alpine Turnout

Alpine Turnout Logo

A lightweight, persistent tab-style switch for Alpine.js

Unlike traditional routers that destroy and recreate DOM elements, Alpine Turnout treats your routes like railroad tracks. Every section stays in the DOM, preserving its internal state, while the "Turnout" guides the view and URL to the correct destination.

Why Turnout?

  • Zero-Config: just setup your html layout like normally and use the x-route attribute to declare the "tracks".

  • Persistence: Forms, scroll positions, and component data are preserved when navigating away and back.

  • Instant Switching: No re-mounting or re-fetching logic on every click.

  • Alpine-Native: Uses a global store and works with a single directive.

  • Transitions: Works seamlessly with Alpine's x-transition.

  • Super Small The alpine-turnout code is only 2.00 kB (gzip: 0.94 kB)

  • SEO Proof: All your content gets indexed by the popular search engines like Google, DuckDuckGo etc.


Installation

Via CDN

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 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.

Directive: x-route

Used on a div or section to define a "track".

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

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

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


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


⚖️ License

MIT © Github NPM