Installation

Install the package from npm:

pnpm add svelte-a11y-panel
# or
npm install svelte-a11y-panel
# or
yarn add svelte-a11y-panel

Requirements: Svelte 5 (runes), SvelteKit 2+, Node 18+.

Quick start

Add PanelMount and AccessibilityButton to your root layout:

<!-- src/routes/+layout.svelte -->
<script>
  import { PanelMount, AccessibilityButton } from 'svelte-a11y-panel';
  let { children } = $props();
</script>

<PanelMount config={{
  accentColor: '#2563eb',
  statement: {
    orgName: 'My Organisation',
    email: 'accessibility@mysite.com',
    assessmentDate: 'January 2026',
  }
}} />

<AccessibilityButton accentColor="#2563eb" />

{@render children()}

PanelMount renders nothing visible. It sets up the panel in a Shadow DOM appended to document.body. AccessibilityButton is a fixed-position floating button (bottom-right) that opens and closes the panel.

No CSS imports needed. The panel manages all its own styles.

AccessibilityButton props

PropTypeDefaultDescription
accentColorstring'#2563eb'Background colour of the button. Should match PanelMount's accentColor.
labelstring'Accessibility options'Accessible label (aria-label) for screen readers.
classstring''Additional CSS class(es) to apply to the button element.

Configuration

Pass a config object to PanelMount:

<PanelMount config={myConfig} />

Full config reference

OptionTypeDefaultDescription
accentColorstring'#2563eb'Hex colour for buttons, toggles, focus rings, and host-page overlays
uiFontFamilystring'system-ui, -apple-system, BlinkMacSystemFont, sans-serif'Font for the panel UI and all overlays (link navigator, virtual keyboard)
dyslexiaFontUrlstringjsDelivr CDNWOFF2 URL for OpenDyslexic font. Override to self-host.
storageKeystring'a11y-panel-state'localStorage key for persisted state
positionKeystring'a11y-panel-pos'sessionStorage key for panel position
statement.orgNamestring''Organisation name in the accessibility statement
statement.emailstring''Contact email in the accessibility statement
statement.conformanceStatusstring''Optional conformance statement text
statement.limitationsstring[][]Known accessibility limitations to list
statement.assessmentDatestring''Date the statement was prepared

Security note

accentColor and dyslexiaFontUrl are interpolated into a CSS stylesheet injected into the Shadow DOM. Do not set these from untrusted user input. Treat config as a build-time constant.

Init CLI

If you'd prefer a guided setup, run the init command from the root of your SvelteKit project:

npx svelte-a11y-panel init

The CLI will:

  • Detect your SvelteKit project
  • Ask for your accent colour, org name, contact email, and statement date
  • Modify (or create) src/routes/+layout.svelte with a configured PanelMount
  • Show you the next step (adding AccessibilityButton)

The CLI only runs when you invoke it. It is not a postinstall hook and never runs automatically.

Theming

The panel renders inside a Shadow DOM, so your page's global CSS cannot reach it. Theming is done entirely through config:

  • Accent colour: set accentColor for buttons, toggles, focus rings, and overlays.
  • Font: set uiFontFamily for the panel UI and all overlays.
<PanelMount config={{
  accentColor: '#7c3aed',
  uiFontFamily: "'Inter', system-ui, sans-serif",
}} />

Colours and fonts set via CSS on the host page (including custom properties on :root) cannot reach inside the Shadow DOM. Use config instead.

Custom accessibility statement

To replace the default statement content, pass a customStatement snippet to PanelMount:

<PanelMount config={myConfig}>
  {#snippet customStatement()}
    <h2>Accessibility at Example Corp</h2>
    <p>This site aims to meet WCAG 2.2 AA. Known gaps are listed on our accessibility page.</p>
    <p>
      Found a problem? Email
      <a href="mailto:access@example.com">access@example.com</a>.
    </p>
  {/snippet}
</PanelMount>

When customStatement is provided, the default statement is replaced entirely. The back button and header are still rendered. For simple cases (org name, email, date), use the statement config fields instead.

Custom trigger button

AccessibilityButton is intentionally simple. Build your own trigger using the state functions directly:

<script>
  import { openPanel, closePanel, getOpen } from 'svelte-a11y-panel';
  let buttonEl = $state(null);
</script>

<button
  bind:this={buttonEl}
  onclick={() => getOpen() ? closePanel() : openPanel(buttonEl)}
  aria-expanded={getOpen()}
  aria-controls="a11y-panel"
  aria-label="Accessibility options"
>
  Accessibility settings
</button>

openPanel(element) takes your trigger element so the panel can return focus to it when closed. getOpen() is a reactive getter backed by Svelte 5 $state. It re-runs any template that reads it.

CSP & privacy

Content Security Policy

If your site uses a strict CSP, you will need to permit the following:

FeatureCSP directive required
Host-page style injectionstyle-src 'unsafe-inline' (or a nonce)
OpenDyslexic font (dyslexia mode)font-src cdn.jsdelivr.net
Self-hosted fontfont-src 'self'; set dyslexiaFontUrl to your own URL

If style-src 'unsafe-inline' is blocked, the panel UI still works but host-page effects (font overrides, contrast filters, cursor changes) will be silent no-ops.

Microphone access

Voice navigation uses the Web Speech API (SpeechRecognition), which requests microphone permission from the user the first time it is enabled. The library itself never records or transmits audio, but the browser's speech engine may: Chrome, for example, can send captured audio to Google's servers for recognition. You should mention microphone use in your privacy policy if you enable this feature.

localStorage

User accessibility preferences are persisted to localStorage. The panel does not intentionally store names, email addresses, or account identifiers.

CDN font

When dyslexia mode is enabled, a request is made to cdn.jsdelivr.net. To avoid this, provide your own font URL via dyslexiaFontUrl.

Host page effects

When users enable accessibility features, the library actively manipulates the host page. These changes are how the panel applies adjustments across the site.

EffectWhat it does
Style injectionInjects <style id="a11y-panel-host-styles"> into <head>
Reading guide / maskAppends overlay <div>s to <body>
Text magnifierAppends a magnifier <div> that follows the cursor
Link navigatorAppends a <dialog> listing all a[href] elements
Virtual keyboardAppends a keyboard <div>; dispatches synthetic KeyboardEvents
Text-to-speechAttaches a click listener to document; uses window.speechSynthesis
Voice navigationUses window.SpeechRecognition; calls window.scrollBy, history.back()
Navigation keysAttaches a keydown listener to document
Mute soundsSets muted = true on all <audio> and <video> elements
Hide emojiWraps emoji text nodes in <span>s with a hidden class

The panel removes its injected styles, overlays, and event listeners when the relevant setting is turned off or the panel is unmounted.

Browser support

FeatureSupport
Panel UICurrent Chrome, Edge, Firefox, and Safari
Text-to-speechCurrent Chrome, Edge, Firefox, and Safari
Voice navigationChrome, Edge, and Safari (Web Speech API; not Firefox)
Virtual keyboardCurrent Chrome, Edge, Firefox, and Safari