---
url: https://lit-ui-router.dev/api.md
---
# API Overview

This guide provides a quick overview of the `lit-ui-router` API. For detailed type signatures and complete documentation, see the [API Reference](./reference/). Companion packages have their own home — see [Companion Packages](/packages/).

## Installation

```bash
npm install lit-ui-router lit @uirouter/core
```

## Entry Points

| Import                                        | Effect                                                                                                                                                                                                             |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `import { ... } from 'lit-ui-router'`         | The router, the elements, the directives and the controllers. Any value import registers the `<ui-router>`/`<ui-view>` custom elements as a side effect.                                                           |
| `import { ... } from 'lit-ui-router/pure'`    | The same API — element classes included — with no registration and no `HTMLElementTagNameMap` globals.                                                                                                             |
| `import 'lit-ui-router/register'`             | Registration only: defines `<ui-router>`/`<ui-view>` and carries their `HTMLElementTagNameMap` entries.                                                                                                            |
| `import 'lit-ui-router/ui-view.register'`     | Single-element registration: defines just that element with its tag-map entry (`ui-router.register` ditto).                                                                                                        |
| `import { ... } from 'lit-ui-router/context'` | The only home of the `context-request` key, event and request helper, and of the tree-less router hand-off — nothing from `lit` — see [Reactive Components](/guides/reactive-components#router-discovery).         |
| `lit-ui-router/custom-elements.json`          | The [Custom Elements Manifest](https://custom-elements-manifest.open-wc.org/) for `<ui-router>` and `<ui-view>`, also named by the `customElements` field. Bundlers load it as a URL or JSON, e.g. `?url` in Vite. |
| `import type { ... } from 'lit-ui-router'`    | Types are erased at compile time — always free, from any entry.                                                                                                                                                    |

These entries are the supported import paths. Deep `lit-ui-router/dist/...` paths still resolve, but they are unsupported and 2.0 removes them.

## Quick Start

```ts
import {
  UIRouterLit,
  uiSref,
  uiSrefActive,
  LitStateDeclaration,
} from 'lit-ui-router';
import { hashLocationPlugin } from '@uirouter/core';
import { html } from 'lit';

// 1. Create router and add location plugin
const router = new UIRouterLit();
router.plugin(hashLocationPlugin);

// 2. Define states
const states: LitStateDeclaration[] = [
  { name: 'home', url: '/home', component: () => html`<h1>Home</h1>` },
  { name: 'users', url: '/users', component: UserListElement },
];

// 3. Register states and start
states.forEach((state) => router.stateRegistry.register(state));
router.urlService.rules.initial({ state: 'home' });
router.start();
```

```html
<!-- 4. Use in your app -->
<ui-router .uiRouter="${router}">
  <nav>
    <a ${uiSref('home')} ${uiSrefActive({ activeClasses: ['active'] })}>Home</a>
    <a ${uiSref('users')} ${uiSrefActive({ activeClasses: ['active'] })}>Users</a>
  </nav>
  <ui-view></ui-view>
</ui-router>
```

`rules.initial({ state: 'home' })` lands on the state with no params; a landing
state that reads the query needs the function form — see
[Unmatched URLs](/guides/unmatched-urls#a-landing-state-that-reads-the-query).

## Core Concepts

### Router

[`UIRouterLit`](./reference/core/UIRouterLit) is the main router class. It extends `@uirouter/core`'s UIRouter with Lit-specific view handling.

[`viewRendered(router, transition?)`](./reference/core/viewRendered) resolves once every `<ui-view>` registered with the router, and the element each one rendered, has completed its update for the transition. It waits on `updateComplete`, never on a frame — see [Component Lifecycle](/guides/component-lifecycle#after-the-transition-waiting-for-the-view-to-render).

### Components

* [**`<ui-router>`**](./reference/components/UIRouterLitElement) - Root component that provides router context to descendants
* [**`<ui-view>`**](./reference/components/UiView) - Viewport that renders the component for the current state

### Directives

* **[`uiSref`](./reference/directives/uiSref)** - Creates navigation links to states
* **[`uiSrefActive`](./reference/directives/uiSrefActive)** - Adds CSS classes when linked state is active, and sets `aria-current` on active links
* **[`srefHref`](./reference/directives/srefHref)** - `uiSref` bound in the `href` attribute
* **[`srefActiveClass`](./reference/directives/srefActiveClass)** - `uiSrefActive`'s classes, bound in the `class` attribute
* **[`srefAriaCurrent`](./reference/directives/srefAriaCurrent)** - `uiSrefActive`'s `aria-current`, bound in the attribute

#### Attribute-part forms

`uiSref` and `uiSrefActive` are element parts: they sit on the element and
write to it from the outside, so a reader that is not a live browser — a
server renderer, an accessibility linter — sees an `<a>` with no `href`.
The `sref*` directives do the same jobs from inside the attribute they
affect, so the template says what the browser will show. A linter reads that,
and so does a server render given a router — see
[Server Rendering](#server-rendering):

```html
<a href=${srefHref('users')}
   class="nav-link ${srefActiveClass({ state: 'users', activeClasses: ['active'] })}"
   aria-current=${srefAriaCurrent({ state: 'users' })}>Users</a>
<!-- while at `users`:        <a href="/users" class="nav-link active" aria-current="page"> -->
<!-- while at `users.detail`: <a href="/users" class="nav-link active"> -->
```

* `srefHref` takes `uiSref`'s arguments and does everything `uiSref` does —
  the click navigates, and an enclosing `uiSrefActive` still tracks it. There
  is no `assignHref`: the attribute is the binding. Use one form or the other
  on an element, not both.
* `srefActiveClass` follows lit's
  [`classMap`](https://lit.dev/docs/templates/directives/#classmap): bind it in
  `class`, alone or beside static classes, and it toggles only the classes it
  names. It cannot share the attribute with `classMap` itself, so pass those
  classes as `classes: { 'nav-link': true, disabled: locked }`. Leave
  `state` out on a wrapper to watch the `srefHref` links inside it, as
  `uiSrefActive` does.
* `srefAriaCurrent` is the one piece a `class` binding cannot reach, so it is
  its own directive. Binding the attribute is the opt-in: it writes `'page'`
  while the exact state is active and removes the attribute otherwise, on any
  element, with none of `uiSrefActive`'s link detection or takeover rules.
  `value` picks another token or `{ exact, active }` for ancestors.

#### Composing with `classMap`

A `class` attribute holds one toggling directive, so `srefActiveClass` and
`classMap` cannot share it. When a component wants both — the active flag
next to its own bindings —
[`SrefStatusController`](./reference/controllers/SrefStatusController) hands
the status to the host instead of writing an attribute, and the template
composes it freely:

```ts
@property({ type: Boolean }) locked = false;
private users = new SrefStatusController(this, { state: 'users' });
// class=${classMap({ 'nav-link': true, active: this.users.active, disabled: this.locked })}
// aria-current=${this.users.ariaCurrent()}
```

See [Reactive Components](../guides/reactive-components#active-link-status).

#### Accessible active links

`uiSrefActive` conveys active state to assistive technology as well as to CSS. On a
link element (`<a>`, `<area>`, or anything with `role="link"`) it sets
`aria-current="page"` while the **exact** linked state is active, and removes the
attribute when it is not — so the nav above needs no extra markup:

```html
<a ${uiSref('users')} ${uiSrefActive({ activeClasses: ['active'] })}>Users</a>
<!-- while at `users`:            <a href="/users" class="active" aria-current="page"> -->
<!-- while at `users.detail`:     <a href="/users" class="active"> -->
```

`aria-current="page"` is applied on exact match only, deliberately: an ancestor
state being active means the link points at a section containing the current page,
not at the current page itself, and a nav in which several ancestor links all claim
`aria-current="page"` is worse for a screen reader user than one with none.

Three knobs, via `ariaCurrentValue`:

* **Another token** — `'page'` (default), `'step'`, `'location'`, `'date'`,
  `'time'`, or `'true'`. Passing a value explicitly also opts **non-link** elements
  in, which is otherwise off; `aria-current` on a wrapping `<li>` or `<tr>` is valid
  ARIA but rarely what an author means, so wrappers stay silent unless asked.

  ```html
  <li ${uiSrefActive({ activeClasses: ['active'] })}>
    <!-- classes on the wrapper, aria-current on the link -->
    <a ${uiSref('users')} ${uiSrefActive({})}>Users</a>
  </li>

  <!-- an explicit value opts a non-link element in; 'auto' keeps href off it -->
  <tr ${uiSref('.message', { messageId }, { assignHref: 'auto' })}
      ${uiSrefActive({ activeClasses: ['active'], ariaCurrentValue: 'true' })}></tr>
  ```

* **`false`** — leave `aria-current` alone entirely; the directive will neither set
  nor remove it. This is the opt-out to reach for when the application manages
  `aria-current` itself: a value written in the template survives untouched, in
  every routing state.

  ```html
  <a ${uiSref('home')} ${uiSrefActive({ activeClasses: ['active'], ariaCurrentValue: false })}>Home</a>
  ```

* **`{ exact, active }`** — mark ancestors too, which is otherwise off. `active`
  applies while a child state is active and this one is not the exact match;
  `'location'` is the ARIA token meant for exactly that.

  ```html
  <a ${uiSref('users')}
     ${uiSrefActive({
       activeClasses: ['active'],
       ariaCurrentValue: { exact: 'page', active: 'location' },
     })}>Users</a>
  <!-- while at `users`:        aria-current="page"     -->
  <!-- while at `users.detail`: aria-current="location" -->
  ```

  Note this pair does **not** combine the way `activeClasses` and `exactClasses`
  do. Both class sets land in `class` on an exactly-active link, because an exact
  match is also an active one. `aria-current` is a single attribute with a single
  value, so the two are branches of one decision: an exactly-active element takes
  `exact` and never falls through to `active`. Each key keeps its own default when
  omitted, so `{ active: 'location' }` alone still defaults `exact` to `'page'` on
  links — write `{ exact: false, active: 'location' }` to mark only ancestors.

The directive only removes an `aria-current` it wrote itself. A value authored in the
template is therefore left alone — but only until the directive first writes one of its
own, after which it owns the attribute and will clear it on the next inactive render.
`ariaCurrentValue: false` is the way to keep a template-authored value for good, since
the directive then never writes and never takes ownership.

### State Declaration

[`LitStateDeclaration`](./reference/types/LitStateDeclaration) defines a state with its URL and component.

#### Component Styles

lit-ui-router supports multiple ways to define route components:

**Inline Template Function** (simplest)

```ts
{ name: 'home', url: '/', component: () => html`<h1>Home</h1>` }
```

**Template with Route Parameters**

```ts
{
  name: 'user',
  url: '/user/:id',
  component: (props) => html`<h1>User ${props?.transition?.params().id}</h1>`
}
```

**Template with Resolved Data**

```ts
{
  name: 'users',
  url: '/users',
  component: (props) => html`
    <ul>${props?.resolves?.users?.map(u => html`<li>${u.name}</li>`)}</ul>
  `,
  resolve: [{ token: 'users', resolveFn: () => fetchUsers() }]
}
```

**LitElement Class** (for complex components with lifecycle/state)

```ts
{ name: 'dashboard', url: '/dashboard', component: DashboardElement }
```

| Style                      | Best For                                       |
| -------------------------- | ---------------------------------------------- |
| `` () => html`...` ``      | Simple static views                            |
| `` (props) => html`...` `` | Views needing params or resolves               |
| `MyElement`                | Complex views with lifecycle, state, or styles |

#### Typing Resolves

`LitStateDeclaration<T>` takes the resolves type as a generic, defaulting to
`Record<string, any>`. A view receives `T` as an argument, so the check runs in
reverse: a view that requires a resolve does not fit the default, and TypeScript
reports the error at `component:` rather than at the generic.

```ts
const GalleryView: RoutedLitTemplate<{ manifest: Manifest }> = (props) =>
  html`<p>${props.resolves.manifest.sheets.length}</p>`;

const states: LitStateDeclaration[] = [
  { name: 'gallery', url: '/', component: GalleryView }, // TS2322 at component:
];
```

Two shapes type-check:

* **Thread the generic** onto the declaration: `LitStateDeclaration<{ manifest: Manifest }>[]`.
  A bare `RoutedLitTemplate` view still fits beside the typed one.
* **A table mixing resolve shapes** threads one type alias that covers every view,
  such as `GalleryResolves & SheetResolves`. Each view keeps its own required
  members. An alias with every member optional also fits, at the cost of `?.` at
  each use inside the views.

### Lifecycle Hooks

Components can implement these interfaces to respond to routing events:

* **[`UiOnExit`](./reference/hooks/UiOnExit)** - Called before navigating away (can cancel navigation)
* **[`UiOnParamsChanged`](./reference/hooks/UiOnParamsChanged)** - Called when route parameters change

### Injected Props

Routed components receive [`UIViewInjectedProps`](./reference/types/UIViewInjectedProps) with:

* `router` - The UIRouter instance
* `transition` - The current transition
* `resolves` - Resolved data from state declarations

## Location Plugins

Import from `@uirouter/core`:

```ts
import { hashLocationPlugin, pushStateLocationPlugin } from '@uirouter/core';

// Hash URLs: /#/home
router.plugin(hashLocationPlugin);

// HTML5 pushState: /home
router.plugin(pushStateLocationPlugin);
```

See the [@uirouter/core location plugins documentation](https://ui-router.github.io/core/docs/latest/):

* [PushStateLocationService](https://ui-router.github.io/core/docs/latest/classes/_vanilla_pushstatelocationservice_.pushstatelocationservice.html) - HTML5 history API
* [HashLocationService](https://ui-router.github.io/core/docs/latest/classes/_vanilla_hashlocationservice_.hashlocationservice.html) - Hash-based URLs

On the server, `serverLocationPlugin` from
[`ui-router-server/location`](/packages/server) is the in-memory plugin whose
hrefs are paths, so a prerendered link matches what the pushState client writes.

## Server Rendering

`lit-ui-router/context` carries the router hand-off a server render needs and an
element cannot supply, because on the server a directive has no element to seek
a router from.

* `provideRouter(root, router)` - answers `context-request` on any event target, `globalThis.litServerRoot` included
* `withRouterSync(router, run)` - scopes a router to one synchronous call
* `getScopedRouter()` - reads the router that call scoped

`srefHref`, `srefActiveClass`, `srefAriaCurrent` and `lit-ui-router-effect`'s
`RouterRefController` read the `withRouterSync` slot and nothing else, so a nav
rendered inside it carries real hrefs and active markup; `provideRouter` serves
elements that ask by protocol, not attribute parts.

See [Server-Side Routing](/guides/server-route-matching#the-router-on-the-server).

## Companion Packages

`lit-ui-router` is the core package; optional **companion packages** layer on extra integrations — [MobX bindings](/packages/mobx), the [Navigation API location plugin](/packages/navigation-plugin), and [server-side routing verdicts](/packages/server) with [`ui-router-server`](/api/ui-router-server/) (in development), each independently versioned with its own guide and API reference. See [Companion Packages](/packages/).

## Further Reading

* [Tutorial](/tutorial/helloworld) - Step-by-step guide
* [API Reference](./reference/) - Complete type documentation
* [@uirouter/core docs](https://ui-router.github.io/core/docs/latest/) - Core router documentation
