npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-a11y-panel

v0.1.2

Published

Accessible, configurable accessibility panel component for Svelte 5

Readme

svelte-a11y-panel

A configurable, open-source accessibility panel for Svelte 5 apps. Lets users adjust your site for their needs without any server-side setup.

Originally built for Care Culture's public website, then extracted into a standalone package.

What it does

Adds an accessibility widget that lets site visitors adjust your site for their needs:

  • Profiles: Epilepsy Safe, Low Vision, Cognitive, ADHD, Blindness
  • Readable Experience: font size, line height, letter spacing, readable/dyslexia fonts, text magnifier
  • Visual: dark/high contrast, monochrome, saturation, custom colour overrides
  • Orientation: reading guide, reading mask, hide images/emoji, stop animations, cursor size, focus/hover highlights
  • Cognitive: text-to-speech, voice navigation, keyboard shortcuts, link navigator, virtual keyboard

The panel UI renders in a Shadow DOM — fully isolated from your site's CSS.

Note: The accessibility effects actively reach into your page (injecting CSS, appending overlays, muting audio). That is the point. See Host page effects below.

Documentation

Full docs and live demo: https://a11y.clmartin.dev

Requirements

  • Svelte 5 (runes)
  • SvelteKit 2+ (or a Vite project)
  • Node 18+

Installation

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

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: 'Your Organisation',
    email: '[email protected]',
    assessmentDate: 'January 2026',
  }
}} />

<AccessibilityButton accentColor="#2563eb" />

{@render children()}

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

Setup CLI (optional)

Prefer a guided setup? Run from the root of your SvelteKit project:

npx svelte-a11y-panel init

The CLI detects your layout file and adds a configured PanelMount with your brand colour, org name, and contact email pre-filled.

Configuration

Pass a config object to PanelMount:

<PanelMount config={myConfig} />

| Option | Type | Default | Description | |---|---|---|---| | accentColor | string | '#2563eb' | Colour for buttons, toggles, focus rings, and host-page overlays | | uiFontFamily | string | 'system-ui, sans-serif' | Font for the panel UI and all overlays | | dyslexiaFontUrl | string | jsDelivr CDN | WOFF2 URL for the OpenDyslexic font | | storageKey | string | 'a11y-panel-state' | localStorage key for persisted state | | positionKey | string | 'a11y-panel-pos' | sessionStorage key for dragged panel position | | statement.orgName | string | '' | Organisation name in accessibility statement | | statement.email | string | '' | Contact email in accessibility statement | | statement.conformanceStatus | string | WCAG 2.1 AA string | Conformance statement text | | statement.limitations | string[] | [] | Known limitations to list | | statement.assessmentDate | string | '' | Date string for the statement |

Security note

Config values (accentColor, dyslexiaFontUrl) are interpolated into a CSS stylesheet injected into the Shadow DOM. Do not set these from untrusted user input or unvalidated CMS fields. Treat config as a build-time constant, not a runtime user setting.

Theming the panel

The panel renders inside a Shadow DOM — your page's CSS (including custom properties on :root) cannot reach it. Theming is done through config:

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

AccessibilityButton props

| Prop | Type | Default | Description | |---|---|---|---| | accentColor | string | '#2563eb' | Background colour of the button | | label | string | 'Accessibility options' | Accessible label for screen readers | | class | string | '' | Additional CSS classes |

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.

Host page effects

When users enable features, the panel actively modifies your page:

| Feature | What it does to your page | |---|---| | Font size / contrast / filters | Injects <style id="a11y-panel-host-styles"> into <head> | | Reading guide / mask / magnifier | Appends overlay <div>s to <body> | | Mute sounds | Sets .muted = true on all <audio> and <video> elements | | Hide emoji | Wraps emoji text nodes in <span data-a11y-panel-emoji> | | Link navigator | Appends a <dialog> to <body> | | Virtual keyboard | Appends a keyboard <div> to <body>, dispatches synthetic KeyboardEvents | | Text-to-speech | Attaches a click listener to document, uses window.speechSynthesis | | Voice navigation | Uses window.SpeechRecognition, calls window.scrollBy / history | | Navigation keys | Attaches a keydown listener to document | | State persistence | Saves to localStorage under config.storageKey |

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 |

Custom accessibility statement

Pass a customStatement snippet to PanelMount to replace the default statement content entirely:

<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:[email protected]">[email protected]</a>.</p>
  {/snippet}
</PanelMount>

When customStatement is provided, the default statement is replaced entirely by your content. The back button and statement header are still rendered.

Content Security Policy (CSP)

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

| Feature | CSP directive required | |---|---| | Host-page style injection | style-src 'unsafe-inline' (or a nonce) | | OpenDyslexic font (if using dyslexia mode) | font-src cdn.jsdelivr.net | | Self-hosted font | font-src 'self' (set dyslexiaFontUrl to your own URL) |

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

Privacy and permissions

Voice navigation uses the browser's SpeechRecognition API, which requires microphone permission. The browser will prompt the user the first time they enable voice navigation. Speech is processed entirely in the browser — no audio data is sent to any server.

Text-to-speech reads aloud the text content of any element the user clicks. On pages with sensitive information, users should be aware that reading aloud may expose content to bystanders.

localStorage stores the user's accessibility preferences (toggle states, font size, colours). 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.

License

MIT — see LICENSE