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, -apple-system, BlinkMacSystemFont, 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 | '' | Optional 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: set
accentColorfor buttons, toggles, focus rings, and overlays. - Font: set
uiFontFamilyfor 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:
| 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 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.
| 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 |
The panel removes its injected styles, overlays, and event listeners when the relevant setting is turned off or the panel is unmounted.
Browser support
| Feature | Support |
|---|---|
| Panel UI | Current Chrome, Edge, Firefox, and Safari |
| Text-to-speech | Current Chrome, Edge, Firefox, and Safari |
| Voice navigation | Chrome, Edge, and Safari (Web Speech API; not Firefox) |
| Virtual keyboard | Current Chrome, Edge, Firefox, and Safari |