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

@shadow-js/router

v0.1.0

Published

Minimal, type-safe router for ShadowJS

Readme

ShadowJS Router

npm version TypeScript License: MIT

Declarative client-side routing for ShadowJS applications with support for nested routes, layouts, transitions, and scroll restoration.

✨ Features

  • 🎯 Declarative Routing: Define routes using JSX components
  • 🧩 Nested Routes: Support for nested layouts and route hierarchies
  • 🎭 Transitions: Built-in route transition animations
  • 📜 Scroll Restoration: Automatic scroll position management
  • 🔗 Programmatic Navigation: Navigate programmatically with full control
  • 🎨 Layout Support: Shared layouts for multiple routes
  • 📱 Hash & History Modes: Choose between hash and history-based routing
  • 🔍 Route Parameters: Dynamic route segments with type safety

📦 Installation

The ShadowJS Router requires @shadow-js/core as a peer dependency:

# Install both packages together
npm install @shadow-js/router @shadow-js/core

# Or if you already have @shadow-js/core installed
npm install @shadow-js/router

Important: This router is specifically designed for ShadowJS applications and requires @shadow-js/core to function properly. The router imports directly from @shadow-js/core and will not work without it.

Development Setup

For development and testing, you can use the development configuration:

# Copy the development package.json
cp package.json.dev package.json

# Install dependencies (includes @shadow-js/core)
npm install

Peer Dependency Architecture

This package uses a peer dependency approach for several reasons:

  1. Framework Integration: Tightly coupled with ShadowJS reactive system
  2. Bundle Size: Avoids bundling framework code that consumers already have
  3. Version Alignment: Ensures consumers use compatible versions
  4. Tree Shaking: Allows better optimization in consumer applications

🚀 Quick Start

Basic Routing

import { Router, Route, A, useLocation } from "@shadow-js/router";

function Navigation() {
  return (
    <nav>
      <A href="/">Home</A>
      <A href="/about">About</A>
      <A href="/contact">Contact</A>
    </nav>
  );
}

function Home() {
  const location = useLocation();
  return (
    <div>
      <h1>Home</h1>
      <p>Current path: {() => location.pathname}</p>
    </div>
  );
}

function About() {
  return <h1>About Us</h1>;
}

function Contact() {
  return <h1>Contact Us</h1>;
}

function NotFound() {
  return <h1>404 - Page Not Found</h1>;
}

function App() {
  return (
    <Router notFound={NotFound}>
      <Navigation />
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/contact" component={Contact} />
    </Router>
  );
}

Dynamic Routes with Parameters

import { useParams } from "@shadow-js/router";

function UserProfile() {
  const params = useParams();
  return (
    <div>
      <h1>User Profile</h1>
      <p>User ID: {() => params().id}</p>
    </div>
  );
}

function App() {
  return (
    <Router>
      <Route path="/users/:id" component={UserProfile} />
    </Router>
  );
}

🎯 Core Concepts

Route Configuration

Routes can be configured in two ways:

1. Declarative Routes (JSX)

<Router>
  <Route path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/users/:id" component={UserProfile} />
</Router>

2. Programmatic Routes (Objects)

const routes = [
  { path: "/", component: Home },
  { path: "/about", component: About },
  { path: "/users/:id", component: UserProfile },
];

<Router routes={routes} />;

Route Matching

ShadowJS Router uses a sophisticated matching algorithm:

  • Exact Matching: /users matches only /users
  • Parameter Matching: /users/:id matches /users/123
  • Wildcard Matching: /users/* matches /users/123/posts
  • Optional Parameters: /users/:id? matches /users and /users/123

Nested Routes and Layouts

function DashboardLayout({ children }) {
  return (
    <div className="dashboard">
      <nav>
        <A href="/dashboard">Overview</A>
        <A href="/dashboard/users">Users</A>
        <A href="/dashboard/settings">Settings</A>
      </nav>
      <main>{children}</main>
    </div>
  );
}

function DashboardOverview() {
  return <h2>Dashboard Overview</h2>;
}

function App() {
  return (
    <Router>
      <Route path="/dashboard" layout={DashboardLayout}>
        <Route path="/" component={DashboardOverview} />
        <Route path="/users" component={DashboardUsers} />
        <Route path="/settings" component={DashboardSettings} />
      </Route>
    </Router>
  );
}

📚 API Reference

Components

<Router>

The main router component that manages client-side routing for your application.

Props:

  • routes?: RouteConfig[] - Array of route configurations (alternative to children)
  • children?: any - Child routes when using JSX syntax
  • transition?: Transition - Default transition for all routes
  • notFound?: ComponentType - Component to render for unmatched routes
  • mode?: "history" | "hash" - URL handling mode (default: "history")
  • scroll?: ScrollBehavior - Scroll restoration behavior

Examples:

// JSX syntax
<Router notFound={NotFound} mode="history">
  <Route path="/" component={Home} />
  <Route path="/about" component={About} />
</Router>;

// Object syntax
const routes = [
  { path: "/", component: Home },
  { path: "/about", component: About },
];

<Router routes={routes} />;

<Route>

Defines a route with its path and component.

Props:

  • path: string - Route path pattern
  • component?: ComponentType - Component to render for this route
  • layout?: ComponentType - Layout component wrapping this route
  • children?: RouteConfig[] - Nested routes
  • redirect?: string | Function - Redirect path or function
  • transition?: Transition - Route-specific transition

Examples:

// Basic route
<Route path="/home" component={Home} />

// Route with layout
<Route path="/dashboard" layout={DashboardLayout}>
  <Route path="/" component={Overview} />
  <Route path="/users" component={Users} />
</Route>

// Route with redirect
<Route path="/old-path" redirect="/new-path" />

// Dynamic route
<Route path="/users/:id" component={UserProfile} />

<A>

Enhanced anchor tag for client-side navigation with automatic active states.

Props:

  • href: string - Navigation target
  • replace?: boolean - Replace current history entry
  • state?: any - State to pass to the new route
  • className?: string | Function - CSS class (can be reactive)
  • All standard <a> tag props

Examples:

<A href="/home">Home</A>
<A href="/users" className={() => (location.pathname === "/users" ? "active" : "")}>
  Users
</A>

<Redirect>

Programmatically redirects to another route.

Props:

  • to: string - Redirect target path

Examples:

<Redirect to="/login" />

Hooks

useLocation()

Returns the current location object with reactive properties.

Returns: Store<Location>

Location Properties:

  • pathname: string - Current path
  • search: string - Query string
  • hash: string - Hash fragment
  • state: any - Navigation state

Examples:

function CurrentPage() {
  const location = useLocation();

  return (
    <div>
      <p>Current path: {location.pathname}</p>
      <p>Search: {location.search}</p>
      <p>Hash: {location.hash}</p>
    </div>
  );
}

useParams<T>()

Returns route parameters extracted from the current URL.

Returns: Store<T | undefined>

Examples:

function UserProfile() {
  const params = useParams<{ id: string }>();

  return <div>User ID: {params().id}</div>;
}

useSearchParams()

Manages URL search parameters with reactive updates.

Returns: [Store<URLSearchParams>, SetterFunction]

Examples:

function SearchComponent() {
  const [searchParams, setSearchParams] = useSearchParams();

  const updateSearch = (query: string) => {
    setSearchParams({ q: query, page: "1" });
  };

  return (
    <input
      value={searchParams().get("q") || ""}
      onInput={(e) => updateSearch(e.target.value)}
    />
  );
}

Functions

navigate(to, options?)

Programmatically navigate to a new route.

Parameters:

  • to: string - Target path
  • options?: NavigateOptions - Navigation options

NavigateOptions:

  • replace?: boolean - Replace current history entry
  • state?: any - State to pass to the new route

Examples:

// Basic navigation
navigate("/dashboard");

// Replace current history entry
navigate("/login", { replace: true });

// Pass state
navigate("/checkout", { state: { fromCart: true } });

redirect(to)

Redirect to a new route (equivalent to navigate with replace: true).

Parameters:

  • to: string - Redirect target

Examples:

redirect("/login");

Types

RouteConfig<P, T>

Configuration object for defining routes programmatically.

Properties:

  • path: P - Route path pattern
  • component?: ComponentType - Route component
  • layout?: ComponentType - Layout component
  • children?: RouteConfig[] - Nested routes
  • redirect?: string | Function - Redirect configuration
  • transition?: Transition - Route transition

PathParams<P>

Type-safe route parameters extracted from path patterns.

Examples:

// For path "/users/:id/posts/:postId"
// PathParams = { id: string, postId: string }

NavigateOptions<T>

Options for programmatic navigation.

Properties:

  • replace?: boolean - Replace history entry
  • state?: T - Navigation state

Transition

Animation effect for route changes.

Types:

  • "none" - No transition
  • "fade" - Fade transition
  • CustomTransition - Custom transition function

🔧 Advanced Usage

Programmatic Navigation

import { navigate, redirect } from "@shadow-js/router";

function LoginForm() {
  const [isLoggedIn, setIsLoggedIn] = useStore(false);

  const handleLogin = () => {
    setIsLoggedIn(true);
    // Navigate after successful login
    navigate("/dashboard", { replace: true });
  };

  const handleLogout = () => {
    setIsLoggedIn(false);
    // Redirect to home
    redirect("/");
  };

  return (
    <div>
      <button onClick={handleLogin}>Login</button>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
}

Route Transitions

<Router transition="fade">
  <Route path="/" component={Home} />
  <Route path="/about" component={About} transition="slide" />
</Router>

Available transitions: "fade", "slide", "scale", "none"

Custom Scroll Behavior

<Router
  scroll={(context) => {
    if (context.action === "PUSH") {
      window.scrollTo(0, 0); // Scroll to top on navigation
    } else if (context.action === "POP") {
      // Restore scroll on browser back/forward
      // Default behavior handles this automatically
    }
  }}
>
  {/* routes */}
</Router>

Route Guards and Redirects

function ProtectedRoute({ component: Component }) {
  const [isAuthenticated, setIsAuthenticated] = useStore(false);

  return (
    <Show when={() => isAuthenticated()} fallback={<Redirect to="/login" />}>
      <Component />
    </Show>
  );
}

function App() {
  return (
    <Router>
      <Route
        path="/dashboard"
        component={() => <ProtectedRoute component={Dashboard} />}
      />
    </Router>
  );
}

Query Parameters

import { useSearchParams } from "@shadow-js/router";

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();

  const category = () => searchParams().get("category") || "all";
  const sort = () => searchParams().get("sort") || "name";

  const updateFilters = (newFilters) => {
    setSearchParams({
      ...Object.fromEntries(searchParams().entries()),
      ...newFilters,
    });
  };

  return (
    <div>
      <select
        value={() => category()}
        onChange={(e) => updateFilters({ category: e.target.value })}
      >
        <option value="all">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="books">Books</option>
      </select>
    </div>
  );
}

🏗️ Router Architecture

Route Compilation

The router compiles route configurations into an optimized matching tree:

  1. Parse route patterns into regex and parameter names
  2. Build matching tree for efficient route resolution
  3. Cache compiled routes for performance

Navigation Flow

  1. URL Change Detection: Listen to popstate and hashchange events
  2. Route Matching: Find matching route for current path
  3. Component Resolution: Resolve component and layout hierarchy
  4. Transition Handling: Apply route transitions
  5. Scroll Management: Handle scroll restoration
  6. Render: Update DOM with new route content

State Management

The router manages several reactive states:

  • Current location: pathname, search, hash
  • Current route match: matched route and parameters
  • Navigation history: for back/forward functionality
  • Scroll positions: for restoration

🔌 Integration

With ShadowJS

import { useStore } from "@shadow-js/core";
import { Router, Route, useLocation } from "@shadow-js/router";

function App() {
  const [user, setUser] = useStore(null);

  return (
    <Router>
      <Route path="/" component={Home} />
      <Route path="/profile" component={Profile} />
    </Router>
  );
}

Custom History Implementation

// For server-side rendering or custom history management
const customHistory = {
  push: (path) => {
    /* custom push logic */
  },
  replace: (path) => {
    /* custom replace logic */
  },
  go: (delta) => {
    /* custom go logic */
  },
};

// Pass to router
<Router history={customHistory}>{/* routes */}</Router>;

🎨 Styling Routes

Routes can have their own styling:

// Route-specific styles
<Route path="/dashboard" component={Dashboard} className="dashboard-route" />;

// Conditional styling based on active route
function Navigation() {
  const location = useLocation();

  return (
    <nav>
      <A href="/" className={() => (location.pathname === "/" ? "active" : "")}>
        Home
      </A>
      <A
        href="/about"
        className={() => (location.pathname === "/about" ? "active" : "")}
      >
        About
      </A>
    </nav>
  );
}

🐛 Error Handling

Route Errors

function App() {
  return (
    <Router
      notFound={() => <div>Custom 404 Page</div>}
      onError={(error) => {
        console.error("Route error:", error);
        // Handle route errors
      }}
    >
      {/* routes */}
    </Router>
  );
}

Navigation Errors

import { navigate } from "@shadow-js/router";

try {
  await navigate("/protected-route");
} catch (error) {
  if (error.code === "UNAUTHORIZED") {
    navigate("/login");
  }
}

📊 Performance

Route Preloading

// Preload routes on hover for better UX
function Link({ href, children }) {
  const [isPreloading, setIsPreloading] = useStore(false);

  return (
    <A
      href={href}
      onMouseEnter={() => {
        setIsPreloading(true);
        // Preload route component
        import(`./pages${href}.js`);
      }}
      className={() => (isPreloading() ? "preloading" : "")}
    >
      {children}
    </A>
  );
}

Route Memoization

Routes automatically memoize components to prevent unnecessary re-renders.

🧪 Testing

import { render } from "@shadow-js/core";
import { Router, Route } from "@shadow-js/router";

describe("Routing", () => {
  test("renders correct component for route", () => {
    const container = document.createElement("div");

    render(
      <Router>
        <Route path="/test" component={() => <div>Test Component</div>} />
      </Router>,
      container
    );

    // Navigate to test route
    window.history.pushState({}, "", "/test");

    expect(container.innerHTML).toContain("Test Component");
  });
});

📚 Examples

See the examples documentation for:

  • Basic routing patterns
  • Nested routes and layouts
  • Dynamic routing
  • Protected routes
  • Search and query parameters

🤝 Contributing

We welcome contributions! See the Contributing Guide for details.

📄 License

MIT License - see LICENSE for details.

📞 Support


Built with ❤️ for the ShadowJS ecosystem.