Skip to content

Astro ​

Astro doesn't have a dedicated @a11y-hud/astro package because Astro delegates its UI to component islands (React, Vue, Svelte, Solid). a11y-hud works in Astro via two complementary paths.

Path 1 — Vanilla core on the global page ​

For Astro sites without islands (or to audit the full page including static content), use the vanilla core directly in a layout component:

layouts/BaseLayout.astro
astro
---
const isDev = import.meta.env.DEV;
---

<html lang="en">
  <head><!-- ... --></head>
  <body>
    <slot />
    {isDev && (
      <script>
        import { mount } from "a11y-hud";
        mount({ theme: "auto" });
      </script>
    )}
  </body>
</html>

mount() creates the <a11y-hud> element itself, so there is no need to also write the tag into the markup.

Or with the CDN script tag:

astro
---
const isDev = import.meta.env.DEV;
---

{isDev && (
  <>
    <script src="https://cdn.jsdelivr.net/npm/a11y-hud/dist/index.umd.js" />
    <script>window.A11yHud?.mount({ theme: "auto" });</script>
  </>
)}

The vanilla core mounts once and auto-rescans on DOM mutations — it picks up island hydration automatically.

Server-side rendering ​

Astro renders every page on the server (or at build time). Importing a11y-hud or any adapter in that context is safe: the Custom Element is only registered when customElements exists. The HUD mounts only when the inline <script> runs in the browser (or when an island's client lifecycle hook fires), so nothing is rendered server-side.

Path 2 — Adapter inside an island ​

If you want rescans tied to a specific island's render lifecycle, use the matching framework adapter inside that island. This example uses the React adapter — swap in the Vue, Svelte, or Solid adapter to match your island's framework:

src/components/DevTools.tsx
tsx
import { A11yHud } from "@a11y-hud/react";

export default function DevTools() {
  if (!import.meta.env.DEV) return null;
  return <A11yHud theme="auto" />;
}
layouts/BaseLayout.astro
astro
---
import DevTools from "../components/DevTools.tsx";
---

<body>
  <slot />
  <DevTools client:only="react" />
</body>

Use client:only so the HUD never renders server-side.

Which path to use? ​

SituationRecommendation
Mostly static Astro pages, few or no islandsVanilla core in the layout
Heavy React/Vue/Svelte app inside AstroFramework adapter inside the main island
Multiple islands of different frameworksVanilla core — one HUD for the whole page

Limitations ​

  • Pre-hydration DOM: axe runs on the live DOM. Static HTML rendered server-side is still valid DOM — violations in static content are caught once the page loads.
  • Island isolation: If island content is inside a Shadow DOM (unlikely with standard Astro islands), axe may not reach it. Standard Astro islands render into the regular DOM, so there is no issue.

Rescanning after island hydration ​

The vanilla core's MutationObserver catches DOM changes from island hydration automatically. No manual rescan is needed. If you need to rescan explicitly (e.g., after a user interaction loads new content), call:

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

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

// Later, after content loads:
await hud.runScan();