Skip to content

Route-change rescans ​

Framework adapters rescan after every render commit, which covers most route transitions automatically. This recipe shows the explicit patterns for cases where you need finer control.

Why adapters handle most cases automatically ​

  • React: useEffect with no deps fires after every commit, including route-change renders.
  • Vue: the core's MutationObserver picks up DOM changes from navigation; a post-flush watch on scope rescans when the scope prop changes.
  • Angular: afterEveryRender fires after every render commit, including navigation-driven renders.
  • Svelte 5: $effect fires after every reactive update.
  • Solid: createEffect tracks reactive reads, including router signals.

If you mount <A11yHud> in your root layout and your router triggers a full re-render on navigation, the rescan happens for free.

When you need explicit rescans ​

  • The router replaces only the page content region, not the whole tree.
  • You want to rescan after an async data load that follows the navigation.
  • You need to rate-limit rescans during heavy route transitions.

React Router ​

tsx
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { useA11yHud } from "@a11y-hud/react";

function A11yHudWithRouter() {
  const hud = useA11yHud({ theme: "auto" });
  const location = useLocation();

  useEffect(() => {
    // location.pathname changes on every navigation
    hud.runScan();
  }, [location.pathname]);

  return null;
}

Vue Router ​

ts
import { watch } from "vue";
import { useRoute } from "vue-router";
import { useA11yHud } from "@a11y-hud/vue";

const hud = useA11yHud({ theme: "auto" });
const route = useRoute();

watch(
  () => route.fullPath,
  () => hud.runScan(),
  { flush: "post" }  // after DOM update
);

Angular Router ​

Grab the component instance with @ViewChild and call runScan() on it. (Do not inject A11yHudService separately — the component provides its own instance, and a second, un-initialised instance would resolve null from runScan().)

typescript
import { Component, ViewChild, inject } from "@angular/core";
import { Router, NavigationEnd, RouterOutlet } from "@angular/router";
import { filter } from "rxjs/operators";
import { A11yHudComponent } from "@a11y-hud/angular";

@Component({
  standalone: true,
  imports: [A11yHudComponent, RouterOutlet],
  template: `
    <a11y-hud-angular [theme]="'auto'" />
    <router-outlet />
  `,
})
export class RootComponent {
  @ViewChild(A11yHudComponent) hud?: A11yHudComponent;
  private readonly router = inject(Router);

  constructor() {
    this.router.events
      .pipe(filter((e) => e instanceof NavigationEnd))
      .subscribe(() => void this.hud?.runScan());
  }
}

SvelteKit ​

src/routes/+layout.svelte
svelte
<script lang="ts">
  import { page } from "$app/state";
  import { useA11yHud } from "@a11y-hud/svelte";

  const hud = useA11yHud(() => ({ theme: "auto" }));

  $effect(() => {
    // track the pathname — re-runs on every navigation
    page.url.pathname;
    hud.runScan();
  });
</script>

<slot />

Solid Router ​

tsx
import { createEffect } from "solid-js";
import { useLocation } from "@solidjs/router";
import { createA11yHud } from "@a11y-hud/solid";

function A11yHudWithRouter() {
  const hud = createA11yHud({ theme: "auto" });
  const location = useLocation();

  createEffect(() => {
    // location.pathname is a reactive signal
    location.pathname;
    hud.runScan();
  });

  return null;
}

Vanilla (any router) ​

js
import { mount } from "a11y-hud";

const hud = mount({ theme: "auto" });

// Listen to the History API
const originalPushState = history.pushState.bind(history);
history.pushState = (...args) => {
  originalPushState(...args);
  hud.runScan();
};

window.addEventListener("popstate", () => hud.runScan());