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
| Prop | Type | Default | Description |
|---|---|---|---|
accentColor | string | '#2563eb' | Background colour of the button. Should match PanelMount's accentColor. |
label | string | 'Accessibility options' | Accessible label (aria-label) for screen readers. |
class | string | '' | Additional CSS class(es) to apply to the button element. |
Configuration
Pass a config object to PanelMount:
<PanelMount config={myConfig} /> Full config reference
| Option | Type | Default | Description |
|---|---|---|---|
accentColor | string | '#2563eb' | Hex colour for buttons, toggles, focus rings, and host-page overlays |
uiFontFamily | string | 'system-ui, sans-serif' | Font for the panel UI and all overlays (link navigator, virtual keyboard) |
dyslexiaFontUrl | string | jsDelivr CDN | WOFF2 URL for OpenDyslexic font. Override to self-host. |
storageKey | string | 'a11y-panel-state' | localStorage key for persisted state |
positionKey | string | 'a11y-panel-pos' | sessionStorage key for panel position |
statement.orgName | string | '' | Organisation name in the accessibility statement |
statement.email | string | '' | Contact email in the accessibility statement |
statement.conformanceStatus | string | WCAG 2.1 AA string | Conformance statement text |
statement.limitations | string[] | [] | Known accessibility limitations to list |
statement.assessmentDate | string | '' | 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.sveltewith a configuredPanelMount - 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 (buttons, toggles, focus rings, overlays) — set
accentColorin config. - Font (panel UI and all overlays) — set
uiFontFamilyin config.
<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 fully replace the default statement content, pass a customStatement snippet to PanelMount:
<PanelMount config={myConfig}>
{#snippet customStatement()}
<h2>Our Accessibility Statement</h2>
<p>We are committed to making our site accessible to everyone.</p>
<p>
Contact us at
<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:
| Feature | CSP directive required |
|---|---|
| Host-page style injection | style-src 'unsafe-inline' (or a nonce) |
| OpenDyslexic font (dyslexia mode) | font-src cdn.jsdelivr.net |
| Self-hosted font | font-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 window.SpeechRecognition, which requests microphone permission from the user the first time it is enabled.
Speech is processed entirely in the browser — no audio is sent to any server.
You may want to mention microphone use in your privacy policy.
localStorage
User accessibility preferences are persisted to localStorage.
No personally identifiable information is stored.
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. This is intentional — it is the only way to apply adjustments across the entire site.
| Effect | What it does |
|---|---|
| Style injection | Injects <style id="a11y-panel-host-styles"> into <head> |
| Reading guide / mask | Appends overlay <div>s to <body> |
| Text magnifier | Appends a magnifier <div> that follows the cursor |
| Link navigator | Appends a <dialog> listing all a[href] elements |
| Virtual keyboard | Appends a keyboard <div>; dispatches synthetic KeyboardEvents |
| Text-to-speech | Attaches a click listener to document; uses window.speechSynthesis |
| Voice navigation | Uses window.SpeechRecognition; calls window.scrollBy, history.back() |
| Navigation keys | Attaches a keydown listener to document |
| Mute sounds | Sets muted = true on all <audio> and <video> elements |
| Hide emoji | Wraps emoji text nodes in <span>s with a hidden class |
All effects are fully reversed when the user turns them off or the panel is unmounted.
Browser support
| Feature | Support |
|---|---|
| Panel UI | All modern browsers |
| Text-to-speech | All modern browsers |
| Voice navigation | Chrome / Edge only (Web Speech Recognition API) |
| Virtual keyboard | All modern browsers |