Routing
Reactive location, search params and URL patterns, plus navigate: a router built from signals.
All location signals react to popstate (back/forward) automatically. They also listen for pushstate and replacestate custom events, which must be dispatched by your router or by patching history:
// Patch history so pushState/replaceState fire custom events
for (const method of ["pushState", "replaceState"] as const) {
const original = history[method].bind(history);
history[method] = (...args) => {
original(...args);
window.dispatchEvent(new Event(method.toLowerCase()));
};
}Without this patch, signals still update on back/forward — only programmatic navigation via pushState/replaceState won't be reflected.
currentLocation
Returns reactive signals for hash, href, pathname, and search — all sharing a single event listener set.
currentLocation is a module-level singleton suitable for most apps.
import { currentLocation } from "elements-kit/utilities/location";
// Singleton — shared across the app
effect(() => console.log(currentLocation.pathname()));type LocationResult = {
hash: Computed<string>;
href: Computed<string>;
pathname: Computed<string>;
search: Computed<string>;
};
const currentLocation: LocationResultcreateSearchParam
Returns Computed<string | null> for a single URL search parameter.
import { createSearchParam } from "elements-kit/utilities/search-params";
const tab = createSearchParam("tab");
effect(() => console.log("tab:", tab())); // null when absentfunction createSearchParam(key: string): Computed<string | null>createURLPattern
Reactively tests a URL source against a URLPattern. The source can be a plain string/URL or a reactive getter.
No polyfill needed for modern browsers (Chrome 95+, Safari 16.4+, Firefox 117+). Use urlpattern-polyfill on npm for legacy targets.
import { createURLPattern } from "elements-kit/utilities/url-pattern";
import { currentLocation } from "elements-kit/utilities/location";
const match = createURLPattern(currentLocation.href, { pathname: "/users/:id" });
effect(() => console.log(match()?.pathname.groups.id));function createURLPattern(
source: string | URL | Computed<string | URL>,
input?: URLPatternInput,
options?: URLPatternOptions,
): Computed<URLPatternResult | null>navigate
Navigates to a URL via history.pushState (or replaceState). Patches history once on first call so all programmatic navigation — including third-party router calls — fires the pushstate / replacestate custom events that currentLocation signals react to.
import { navigate } from "elements-kit/utilities/routing";
navigate("/users/42");
navigate("/users/42", { replace: true }); // replaceState — no new history entry
navigate("/users/42", { state: { id: 42 } }); // pass history statefunction navigate(url: string | URL, options?: { replace?: boolean; state?: unknown }): voidisLocalNavigationEvent
Returns true when a click event on an <a> element should be handled client-side — same origin, primary button, no modifier keys, no download attribute, no target="_blank". Walks up to the nearest anchor via closest("a"), so it works on container elements too.
Use alongside navigate() to intercept anchor clicks without hardwiring routing logic into this utility.
import { isLocalNavigationEvent, navigate } from "elements-kit/utilities/routing";
document.querySelector("nav")!.addEventListener("click", (e) => {
if (isLocalNavigationEvent(e)) {
e.preventDefault();
navigate((e.target as HTMLAnchorElement).href);
}
});function isLocalNavigationEvent(e: MouseEvent): booleanmatches
Returns Computed<boolean> — true when the current URL matches input. Uses URLPattern.test() — faster than match when you don't need captured groups.
Always use the object form { pathname: "..." } — relative string patterns require a base URL and will throw.
Requires urlpattern-polyfill for Safari < 26 and Firefox < 142.
import { matches } from "elements-kit/utilities/routing";
const isHome = matches({ pathname: "/" });
effect(() => {
if (isHome()) showHomeNav();
});Conditional rendering with React:
import { useSignal } from "elements-kit/integrations/react";
import { matches } from "elements-kit/utilities/routing";
const isSettings = matches({ pathname: "/settings" });
function App() {
return useSignal(isSettings) ? <Settings /> : <NotFound />;
}function matches(input: URLPatternInput, options?: URLPatternOptions): Computed<boolean>match
Returns Computed<URLPatternResult | null> — the full match result when the current URL matches input, null when it does not. Use when you need captured groups. For a boolean gate, prefer matches().
Always use the object form { pathname: "..." } — relative string patterns require a base URL and will throw.
Requires urlpattern-polyfill for Safari < 26 and Firefox < 142.
import { match } from "elements-kit/utilities/routing";
const postMatch = match({ pathname: "/posts/:slug" });
effect(() => {
const slug = postMatch()?.pathname.groups.slug;
if (slug) loadPost(slug);
});Extracting params in React:
import { useSignal } from "elements-kit/integrations/react";
import { match } from "elements-kit/utilities/routing";
const userMatch = match({ pathname: "/users/:id" });
function App() {
const result = useSignal(userMatch);
const id = result?.pathname.groups.id;
return id ? <UserPage id={id} /> : <NotFound />;
}function match(input: URLPatternInput, options?: URLPatternOptions): Computed<URLPatternResult | null>