xiǎo: small, young

Xiao (pronounced "Shh!" + "Ow!") is a small, accessible, framework agnostic, dependency free, browser-driven routing system. Make single-page applications with progressive enhancement. See the docs folder for the working demo, or view it here.


npm i xiao-router


Xiao is just a small script you include in your web page. You have the option of using the ES5 or slightly smaller but less well-supported ES6 version.


<script src="../node_modules/xiao-router/xiao-es5.min.js"></script>


<script src="../node_modules/xiao-router/xiao.min.js"></script>


In Xiao, routes are just subdocuments of web pages with metadata and (sometimes) methods attached to them. Each is identified by an id which corresponds to both a page element (say id="home") and a hash fragment (say #home).

Before initializing your Xiao app, you define a routes array. Only the route id is mandatory.

const routes = [
    id: 'home'
    id: 'about'
    id: 'upload'

This routes array is supplied as the first argument of the instantiated Xiao app. The default route — the one the user is directed to when hitting the root of the application — is the second mandatory argument.

const app = new Xiao(routes, 'home')

Whether written by hand or via templating, each route is just an element with an id in an HTML document:

<div id="home">
  <!-- the (initially) static content of the route -->

On initialization, Xiao gives each element corresponding to a route role="region" then calculates a value for an aria-label, to further identify the route to assistive technologies. The label is:

  • The text content of the route element's first <h1> or <h2> if it exists
  • Or the content of the route object's label property (e.g. label: 'About my project')
  • Or — as a fallback — the route object's id

<div id="about" role="region" aria-label="About my project">
  <!-- the (initially) static content of the route -->

Traversing a Xiao routed app

When a user navigates to a hash fragment, Xiao determines if that hash fragment either

  • Corresponds to an element that corresponds to a route in the routes array
  • Corresponds to an element inside an element that corresponds to a route in the routes array

In default operation, whether you navigate to a route element or a route child element, the previous route element is hidden and the new one revealed. For keyboard accessibility, focus is sent to the newly revealed route element.

Current links

Links corresponding to currently active routes receive the aria-current="true" attribution:

<a href="#home" aria-current="true">Home</a>

This identifies current links to assistive technologies and doubles as a styling hook where desired.

nav [aria-current] {
  border-bottom: 2px solid;

The <title>

It is recommended that the <title> value you supply is the name of the app. Xiao appends the label for the current route after a separator.

<title>My App | Home</title>

The arrived and departed methods

You can hook into lifecycle events for routes to perform operations. In Xiao, these are named arrived and departed. You simply add them as properties on the route object.

  id: 'about',
  label: 'About my project'.
  arrived(elem, params, routes) {
    // Add a class, pull in some dynamic content, whatever
  departed(elem, params, routes) {
    // Save some settings, remove some content, whatever

As you can see, there are three parameters available in each case:

  • elem (node): the HTML element that corresponds to the route (carries the route id)
  • params (object): Any params passed to the route via a query string (e.g. ?foo=bar&ding=dong will be passed as {foo: 'bar', ding: 'dong'}). In a well-formed URL, the parameters should precede the hash (e.g. href="?foo=bar#myRouteElem")
  • routes (object): The whole routes array, for convenience



Whenever a new route is invoked, the reroute event is dispatched from window. This allows you to affect any part of the page in response to a reroute. Secreted in this CustomEvent's details object are the old and new route objects.

window.addEventListener('reroute', e => {
  console.log('Old route:', e.detail.oldRoute)
  console.log('New route:', e.detail.newRoute)

Rerouting programmatically

Xiao capitalizes on standard browser behavior, letting you use links and hash fragments to invoke routes. However, there will be times you want to reroute the user programmatically. A redirect maybe. For this, you can use the reroute method.

The first argument is the desired route (or route child element) id and the second any params you may want to supply.

app.reroute('login', '?redirect=true')


The third (optional) argument when instantiating a Xiao app is the settings object. These are the options:

  • separator: The string used to separate the app's name from the route label in the <title> (default: "|")
  • showHide: Whether to show only one route at a time. If set to false, routes are all persistently visible and the browser navigates between them like standard hash fragments (default: true)
  • arrived: Like the arrived method available for individual routes, but applies to all routes (see above)
  • departed: Like the departed method available for individual routes, but applies to all routes (see above)

Framework independence

Xiao is just a simple router which respects browser standards. The actual functionality you provide within individual Xiao routes is totally up to you. You can use plain JavaScript, React or Vue components, whatever you like. With Xiao, simple single-page applications can be just that: simple. But you can add as many dependencies and as much code to a Xiao skeleton as you like.