# @symbo.ls/router

> Client-side router plugin for DOMQL. Handles route matching, navigation, scroll management, and state updates within DOMQL elements.

Latest version **3.14.601** (published 2026-09-03) · CC-BY-NC-4.0 license · 0 weekly downloads

## Install

```sh
npm install @symbo.ls/router
pnpm add @symbo.ls/router
yarn add @symbo.ls/router
bun add @symbo.ls/router
```

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types.

## Facts

| | |
|---|---|
| Version | 3.14.601 |
| Published | 2026-09-03 |
| First published | 2023-01-20 |
| Weekly downloads | 0 |
| License | CC-BY-NC-4.0 |
| TypeScript types | none |
| Module format | ESM + CommonJS |
| Dependencies | 2 |
| Unpacked size | 37.6 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | elanor, nikoloza, bala-symbols, gallenjohnson, tiny, zajim, lberia, svinchy, chejuichen, tokoyoung, baronsilver, zacharybetzen, bsachdeva, tthomasagg, bneeli33 |

## Links

- npm: https://www.npmjs.com/package/@symbo.ls/router
- npm.io page: https://npm.io/package/@symbo.ls/router

## Dependencies (2)

- [@symbo.ls/utils](https://npm.io/package/@symbo.ls/utils.md) ^3.14.599
- [@symbo.ls/element](https://npm.io/package/@symbo.ls/element.md) ^3.14.599

## Recent versions

- 3.14.601 (latest) — 2026-09-03
- 3.14.600 — 2026-08-26
- 3.14.599 — 2026-07-28
- 3.14.595 — 2026-07-27
- 3.14.542 — 2026-06-28
- 3.14.105 — 2026-05-19
- 3.14.104 — 2026-05-18
- 3.14.103 — 2026-05-18
- 3.14.102 — 2026-05-18
- 3.14.101 — 2026-05-18
- 3.14.100 — 2026-05-18
- 3.14.14 — 2026-05-18
- 3.14.13 — 2026-05-18
- 3.14.12 — 2026-05-17
- 3.14.11 — 2026-05-17
- … 367 more at https://npm.io/package/@symbo.ls/router/versions

## README

# @domql/router

Client-side router plugin for DOMQL. Handles route matching, navigation, scroll management, and state updates within DOMQL elements.

## Install

```bash
npm install @domql/router
```

## Basic Usage

```js
import { router } from '@domql/router'

// Navigate to a path
router('/about', element)

// With state and options
router('/dashboard', element, { userId: 1 }, { scrollToTop: true })
```

Define routes on your DOMQL element:

```js
const App = {
  routes: {
    '/': HomePage,
    '/about': AboutPage,
    '/contact': ContactPage,
    '/*': NotFoundPage
  }
}
```

## Dynamic Route Params

Match routes with `:param` segments. Enable with `useParamsMatching: true`.

```js
const App = {
  routes: {
    '/': HomePage,
    '/:id': UserPage,
    '/:category/:slug': ArticlePage,
    '/*': NotFoundPage
  }
}

router('/users/42', element, {}, { useParamsMatching: true })
// state.params = { id: '42' }

router('/tech/my-article', element, {}, { useParamsMatching: true })
// state.params = { category: 'tech', slug: 'my-article' }
```

Exact segments score higher than params, so `/about` will match a literal `/about` route before `/:id`.

## Query String Parsing

Query parameters are automatically parsed and stored in state.

```js
router('/search?q=hello&tag=a&tag=b', element)
// state.query = { q: 'hello', tag: ['a', 'b'] }
```

Duplicate keys are collected into arrays.

## Guards / Middleware

Run async guard functions before navigation. Return `true` to allow, `false` to block, or a string to redirect.

```js
const authGuard = ({ element }) => {
  if (!element.state.root.isLoggedIn) return '/login'
  return true
}

const roleGuard = ({ params }) => {
  if (params.section === 'admin') return false
  return true
}

router('/dashboard', element, {}, {
  guards: [authGuard, roleGuard]
})
```

Guard functions receive a context object:

```js
{
  pathname,  // full pathname
  route,     // matched route key
  params,    // dynamic route params
  query,     // parsed query string
  hash,      // URL hash
  element,   // DOMQL element
  state      // navigation state
}
```

## 404 Handling

Provide an `onNotFound` callback for unmatched routes:

```js
router('/unknown', element, {}, {
  onNotFound: ({ pathname, route, element }) => {
    console.warn(`No route found for ${pathname}`)
  }
})
```

You can also define a `/*` wildcard route as a catch-all fallback.

## Custom Router Element

Use `customRouterElement` in your `config.js` to route pages into a specific element within the component tree instead of the root. This is useful for persistent layouts where only the content area changes between routes.

```js
// symbols/config.js
export default {
  router: {
    customRouterElement: 'Folder.Content'
  }
}
```

The value is a dot-separated path resolved from the root element. For example, `'Folder.Content'` means the router will find `root.Folder.Content` and render page content inside it.

This allows you to define a layout once in the `/` (main) page and have sub-pages render inside a specific container:

```js
// pages/main.js — defines the persistent layout
export const main = {
  extends: 'Layout',
  Folder: {
    Content: {
      // default content for '/' route
    }
  }
}

// pages/team.js — only defines content (rendered inside Folder.Content)
export const team = {
  state: 'team',
  extends: 'Grid',
  childExtends: 'TeamItem',
  childrenAs: 'state',
  children: (el, s) => s.data,
}
```

## Options

| Option | Type | Default | Description |
|---|---|---|---|
| `level` | `number` | `0` | Route nesting level (which path segment to match) |
| `pushState` | `boolean` | `true` | Push to browser history |
| `initialRender` | `boolean` | `false` | Whether this is the initial page render |
| `scrollToTop` | `boolean` | `true` | Scroll to top after navigation |
| `scrollToNode` | `boolean` | `false` | Scroll within the element node |
| `scrollNode` | `Element` | `document.documentElement` | Node to scroll |
| `scrollToOffset` | `number` | `0` | Offset when scrolling to hash anchors |
| `scrollToOptions` | `object` | `{ behavior: 'smooth' }` | Options passed to `scrollTo()` |
| `useFragment` | `boolean` | `false` | Use fragment tag for content |
| `updateState` | `boolean` | `true` | Update element state on navigation |
| `contentElementKey` | `string` | `'content'` | Key for the content element slot |
| `removeOldElement` | `boolean` | `false` | Remove old content element before setting new |
| `useParamsMatching` | `boolean` | `false` | Enable dynamic `:param` route matching |
| `guards` | `function[]` | `undefined` | Array of guard/middleware functions |
| `onNotFound` | `function` | `undefined` | Callback when no route matches |

## Exported Utilities

### `getActiveRoute(level, route)`

Returns the active route segment at the given nesting level.

```js
import { getActiveRoute } from '@domql/router'

getActiveRoute(0, '/users/42')  // '/users'
getActiveRoute(1, '/users/42')  // '/42'
```

### `parseQuery(search)`

Parses a query string into an object.

```js
import { parseQuery } from '@domql/router'

parseQuery('?page=1&sort=name')  // { page: '1', sort: 'name' }
```

### `matchRoute(pathname, routes, level)`

Matches a pathname against a routes object. Returns `{ key, content, params }`.

```js
import { matchRoute } from '@domql/router'

const routes = { '/': Home, '/:id': Detail, '/*': NotFound }
const result = matchRoute('/42', routes)
// { key: '/:id', content: Detail, params: { id: '42' } }
```

### `parseRoutePattern(pattern)`

Parses a route pattern string into segments, param definitions, and wildcard flag. Results are cached.

### `runGuards(guards, context)`

Runs an array of async guard functions sequentially. Returns `true`, `false`, or a redirect path string.

## Events

The router triggers an `onRouteChanged` event on the element after navigation completes. Listen for it in your element definition:

```js
const App = {
  routes: { ... },
  onRouteChanged: (element, options) => {
    console.log('Route changed:', element.state.route)
  }
}
```

## License

MIT

---
_Source: https://npm.io/package/@symbo.ls/router · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
