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

ally-widget

v1.1.16

Published

A lightweight, customizable accessibility widget for any website. Supports CDN, direct download, and npm.

Readme

Ally Widget

A lightweight, framework-free accessibility widget for any website. Drop it in with a single <script> tag or import it as an npm package — it adds a floating panel of accessibility controls that users can customize per-session.

  • No dependencies — vanilla JS, ~154 KB minified
  • Three delivery formats — CDN (IIFE), ESM (bundlers), CJS (Node/SSR)
  • Languages — English and Nepali built-in
  • WCAG 2.x aligned — focus trapping, ARIA roles, keyboard navigation throughout

Installation

CDN / Direct script tag

<script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>

Or download dist/ally-widget.min.js and host it yourself.

npm

npm install ally-widget
// ESM bundler (Vite, webpack, Rollup…)
import AllyWidget from 'ally-widget';

// CommonJS (Node / SSR)
const AllyWidget = require('ally-widget');

Quick Start

Script tag (zero config)

The widget auto-initialises on DOMContentLoaded. Just include the script:

<script src="dist/ally-widget.min.js"></script>

A button appears at the bottom-right corner. Press Alt + A to toggle the panel.

Configure before loading

Set window.AllyWidgetOptions before the script tag:

<script>
  window.AllyWidgetOptions = {
    position: 'bottom-right',
    lang: 'ne',                      // 'en' | 'ne'
    primaryColor: '#e11d48',
    poweredByText: 'My Company',
    poweredByUrl: 'https://mycompany.com'
  };
</script>
<script src="dist/ally-widget.min.js"></script>

ESM / npm

import AllyWidget from 'ally-widget';

const widget = new AllyWidget({
  position: 'bottom-left',
  primaryColor: '#0ea5e9',
  disableFeatures: ['hide-images', 'annotations'],
  onFeatureToggle(key, enabled) {
    analytics.track('a11y_toggle', { key, enabled });
  }
});

widget.startAccessibleWebWidget();

All Options

| Option | Type | Default | Description | |---|---|---|---| | position | string | 'bottom-right' | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | | offset | [number, number] | [20, 20] | [horizontal, vertical] px from edge | | size | string | '52px' | Toggle button size — px, em, rem, or % | | lang | string | browser lang | 'en' or 'ne' | | storageKey | string | 'ally-wgt' | localStorage key — change to avoid collisions when multiple instances share a domain | | keyboardShortcut | boolean | true | Alt+A toggles the panel. Set false to disable | | showViolationBubble | boolean | true | Red badge on the toggle button showing the axe-core violation count. Set false to hide it | | icon | string | 'default' | Built-in variant name or a raw SVG string for the toggle button icon | | icons | object | — | Override any icon in the full icon set (see Icon Keys) | | poweredByText | string | 'Ally Widget' | Footer link label | | poweredByUrl | string | widget repo URL | Footer link href | | disableFeatures | string[] | [] | Feature keys to hide from the panel (see Feature Keys) | | featureOverrides | object | {} | Override label, icon, iconColor, or iconInnerColor per feature key (use '*' as wildcard) | | theme | object | — | All theme tokens nested under one key (alternative to passing tokens at the top level) | | ttsNativeVoiceName | string | — | Exact browser voice name to prefer for TTS | | ttsNativeVoiceLang | string | — | BCP-47 language tag for TTS voice selection (e.g. 'ne-NP') | | ttsProxyUrl | string | — | URL of your server-side TTS proxy (see Nepali TTS — Server Proxy). When set, the widget POSTs ?text=…&lang=ne to this URL instead of calling Google directly. | | ttsRate | number | 1 | Speech rate — 0.5 to 2 | | ttsPitch | number | 1 | Speech pitch — 0 to 2 | | onOpen | function | — | Called when the panel opens | | onClose | function | — | Called when the panel closes | | onFeatureToggle | function(key, enabled) | — | Called on every feature toggle | | onReset | function | — | Called when the user resets all settings |


Nepali TTS — Server Proxy

Most desktop browsers do not ship a ne-NP (Nepali) voice. When lang: 'ne' is set and no native voice is found, the widget automatically falls back to Google Translate TTS. However, Google's TTS endpoint blocks direct browser requests.

The solution is a server-side proxy: a small endpoint on your own server that forwards the request to Google with the correct headers. Set ttsProxyUrl to your proxy's path and the widget will call it instead.

AllyWidget.init({
  lang: 'ne',
  ttsProxyUrl: '/api/tts',   // your proxy endpoint
});

The widget calls: GET /api/tts?text=<encoded-text>&lang=ne

Your proxy must return the audio (audio/mpeg) directly in the response body.

See INTEGRATION.md → Nepali TTS Proxy for copy-paste implementations in Next.js, Express, PHP, Laravel, and WordPress.


Theme Customization

Pass any of these tokens directly in options, or group them under theme: { … }:

window.AllyWidgetOptions = {
  // Option A — flat
  primaryColor: '#e11d48',
  borderRadius: '8px',

  // Option B — grouped (same result)
  theme: {
    primaryColor: '#e11d48',
    borderRadius: '8px'
  }
};

Theme tokens

| Token | Default | Notes | |---|---|---| | primaryColor | #1976d2 | Main brand color — fallback for any unset granular key | | primaryColorLight | #42a5f5 | Lighter tint | | primaryColorDark | #0d47a1 | Darker shade | | backgroundColor | #f5f7fa | Panel background | | textColor | #222222 | Panel text | | textColorInverted | #ffffff | Text on primary background | | cardBackground | #ffffff | Feature card background | | borderColor | #d1d5db | Dividers and borders | | focusRingColor | #1976d2 | Keyboard focus ring | | hoverColor | #42a5f5 | Button hover border/shadow color | | activeColor | #0d47a1 | Legacy active color | | borderRadius | 8px | Panel corner radius | | buttonBorderRadius | 0.4rem | Toggle button shape | | headerHeight | 54px | Panel header height | | focusBorderWidth | 3px | Focus ring width | | focusOutlineOffset | 2px | Focus ring offset | | zIndex | 100000 | Stacking context | | buttonSize | 52px | Alias for size |

Granular color tokens

Each element of the widget can be independently colored. Unset keys fall back to primaryColor.

Toggle button (floating launcher)

| Token | Default | Controls | |---|---|---| | toggleButtonBg | primaryColor | Floating button background | | toggleButtonRingColor | primaryColor | Inset ring color | | toggleButtonIconColor | #ffffff | SVG fill on the toggle icon | | toggleButtonBorderRadius | 50% | Button corner radius (e.g. '16px' for rounded-square) | | toggleButtonShadow | 0 4px 14px rgba(0,0,0,0.22) | Resting drop shadow | | toggleButtonShadowHover | 0 8px 22px rgba(0,0,0,0.22) | Shadow on hover / focus | | toggleButtonLabel | 'Accessibility' | Text revealed on hover-expand | | toggleButtonShortcut | '' | Shortcut hint revealed on hover-expand (e.g. 'Ctrl+F2') | | toggleLabelFontSize | 1rem | Font size of the hover label | | toggleLabelFontWeight | 700 | Font weight of the hover label | | toggleShortcutFontSize | 0.7rem | Font size of the shortcut hint | | toggleShortcutFontWeight | 600 | Font weight of the shortcut hint |

Menu header bar

| Token | Default | Controls | |---|---|---| | menuHeaderBg | primaryColor | Header background | | menuHeaderColor | #ffffff | Header title text and icon fill |

Feature buttons (inside the menu)

| Token | Default | Controls | |---|---|---| | featureIconColor | #222222 | Default icon fill for all feature buttons | | featureIconActiveColor | #ffffff | Icon fill when a feature is ON | | featureButtonActiveBg | primaryColor | Button background when feature is ON | | featureButtonActiveBorderColor | primaryColor | Button border when feature is ON | | featureButtonHoverBg | #f3f4f6 | Button hover background |

Section headings

| Token | Default | Controls | |---|---|---| | sectionTitleColor | #6b7280 | Section heading text color |

Panel sizing & scaling

| Token | Default | Controls | |---|---|---| | menuScale | 1 | CSS transform: scale() — scales the entire panel uniformly (text, icons, padding). transform-origin auto-set from position. E.g. '0.85' | | menuMaxWidth | 430px | Maximum width of the panel | | menuFontSize | 16px | Base font size of the panel | | menuBorderRadius | 16px | Panel corner radius | | menuContentPadding | 14px | Horizontal padding inside the panel | | menuSectionGap | 16px | Gap between top-level sections (Speech, Text, Color…) | | menuCategoryGap | 10px | Gap between items within a section | | sectionTitleFontSize | 14px | Font size of section headings (Speech, Text…) | | featureButtonGap | 12px | Gap between feature buttons in the grid | | featureButtonPadding | 10px | Inner padding of each feature button | | featureButtonFontSize | 15px | Base font size inside feature buttons | | featureButtonLabelSize | 13px | Font size of feature button labels | | featureIconSize | 24px | SVG icon size inside feature buttons | | headerIconSize | 28px | SVG icon size in the menu header (close / back) |


Feature Keys

Use these keys with disableFeatures and featureOverrides.

Text

| Key | Label | |---|---| | text-scale | Font Size (slider) | | bold-text | Font Weight | | line-spacing | Line Height | | letter-spacing | Letter Spacing | | readable-text | Dyslexia Font |

Color & Contrast

| Key | Label | |---|---| | contrast-toggle | Contrast (cycles light → dark) | | saturation-toggle | Saturation (cycles low → high) | | invert-colors | Invert Colors |

Reading Aids

| Key | Label | |---|---| | highlight-links | Highlight Links | | highlight-title | Highlight Titles | | reading-aid | Reading Guide | | simple-layout | Simplify Layout |

Interaction

| Key | Label | |---|---| | large-pointer | Big Cursor | | pause-motion | Stop Animations | | hide-images | Hide Images | | high-contrast-mode | High Contrast |

Speech

| Key | Label | |---|---| | text-to-speech | Text to Speech |

Dev mode only (?ally-dev=true)

| Key | Label | |---|---| | annotations | Annotations (axe-core overlays) | | accessibility-report | Accessibility Report |


Disabling Features

window.AllyWidgetOptions = {
  disableFeatures: [
    'hide-images',
    'readable-text',   // remove Dyslexia Font button
    'annotations',
    'accessibility-report'
  ]
};

Feature Label, Icon & Color Overrides

Override any feature button's label, icon SVG, or icon colors — per feature key, without touching the source.

window.AllyWidgetOptions = {
  featureOverrides: {
    // Rename a button
    'bold-text': {
      label: 'Bold'
    },

    // Rename + swap icon SVG
    'text-to-speech': {
      label: 'Read Aloud',
      icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
    },

    // Color overrides — no SVG rewrite needed
    'reading-aid': {
      iconColor: '#0369a1'            // main icon fill
    },
    'contrast-toggle': {
      iconColor: '#6d28d9',           // outer path color
      iconInnerColor: '#c4b5fd'       // inner/secondary path color
    },

    // Wildcard — applies to all features not individually overridden
    '*': {
      iconColor: '#374151'
    }
  }
};

iconColor sets the main SVG fill. iconInnerColor targets secondary paths on compound icons (contrast, high-contrast, saturation, invert-colors). Both override featureIconColor from the theme. Per-feature values always beat the wildcard.


Icon Keys

Pass a partial icons object to swap out any icon in the set:

window.AllyWidgetOptions = {
  icons: {
    largePointer:      '<svg>…</svg>',
    pauseMotion:       '<svg>…</svg>',
    readingAid:        '<svg>…</svg>',
    textToSpeech:      '<svg>…</svg>',
    highContrast:      '<svg>…</svg>',
    simplifyLayout:    '<svg>…</svg>',
    boldText:          '<svg>…</svg>',
    lineSpacing:       '<svg>…</svg>',
    letterSpacing:     '<svg>…</svg>',
    hideImages:        '<svg>…</svg>',
    dyslexiaFont:      '<svg>…</svg>',
    highlightLinks:    '<svg>…</svg>',
    highlightTitle:    '<svg>…</svg>',
    adjustFontSize:    '<svg>…</svg>',
    contrast:          '<svg>…</svg>',
    invertColors:      '<svg>…</svg>',
    saturation:        '<svg>…</svg>',
    annotations:       '<svg>…</svg>',
    accessibilityReport: '<svg>…</svg>',
    reset:             '<svg>…</svg>',
    accessibility:     '<svg>…</svg>'  // toggle button default icon
  }
};

Language

The widget ships with English (en) and Nepali (ne).

window.AllyWidgetOptions = { lang: 'ne' };

The language picker in the panel footer lets users switch at runtime. The selection is persisted to localStorage.


Text to Speech

The widget uses the browser's native Web Speech API — no external service or API key required.

When TTS is enabled, click any text element on the page to hear it read aloud.

Nepali TTS — modern browsers (Chrome, Edge) include a ne-NP voice. To force a specific voice:

window.AllyWidgetOptions = {
  ttsNativeVoiceLang: 'ne-NP',
  ttsNativeVoiceName: 'Google नेपाली',  // optional — exact voice name
  ttsRate: 0.9,
  ttsPitch: 1.0
};

If the requested voice is unavailable the widget falls back to the browser's default voice.


Event Callbacks

window.AllyWidgetOptions = {
  onOpen() {
    console.log('Panel opened');
  },

  onClose() {
    console.log('Panel closed');
  },

  onFeatureToggle(key, enabled) {
    // key   — feature key string (e.g. 'bold-text')
    // enabled — boolean (true = on, false = off)
    analytics.track('a11y_feature', { key, enabled });
  },

  onReset() {
    console.log('All settings cleared');
  }
};

Data Attributes

You can configure the widget via attributes on the script tag instead of window.AllyWidgetOptions:

<script
  src="dist/ally-widget.min.js"
  data-ally-lang="ne"
  data-ally-position="bottom-left"
  data-ally-size="60px"
  data-ally-offset="24,24"
></script>

Supported attributes: data-ally-lang, data-ally-position, data-ally-offset, data-ally-size, data-ally-icon.


Framework Integration

The widget is browser-only. AllyWidget.init() returns null when called server-side, so it is always safe to import — but you must ensure it mounts after the DOM is ready.

| Stack | Approach | |---|---| | Plain HTML | Script tag — just works | | React / Vite | useEffect dynamic import | | Next.js App Router | 'use client' component + useEffect | | Next.js Pages Router | useEffect in _app.jsx | | Vue 3 | onMounted in App.vue | | Nuxt 3 | .client.js plugin | | SvelteKit | onMount in +layout.svelte | | Astro | <script> with is:inline in Layout | | WordPress | wp_enqueue_script in functions.php | | Laravel / Blade | Script tag in layout | | Angular | ngAfterViewInit in AppComponent | | PHP (plain) | Script tag before </body> |


Plain HTML

<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
  <!-- your content -->

  <script>
    window.AllyWidgetOptions = {
      position: 'bottom-right',
      primaryColor: '#6366f1',
      lang: 'en'
    };
  </script>
  <script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>
</body>
</html>

React (Vite / CRA)

// src/components/AllyWidget.jsx
import { useEffect } from 'react'

export default function AllyWidget() {
  useEffect(() => {
    import('ally-widget').then(({ default: AllyWidget }) => {
      AllyWidget.init({ primaryColor: '#6366f1' })
    })
  }, [])

  return null
}
// src/App.jsx
import AllyWidget from './components/AllyWidget'

export default function App() {
  return (
    <>
      <YourApp />
      <AllyWidget />
    </>
  )
}

Next.js (App Router)

// components/AllyWidget.jsx
'use client'
import { useEffect } from 'react'

export default function AllyWidget() {
  useEffect(() => {
    import('ally-widget').then(({ default: AllyWidget }) => {
      AllyWidget.init({ primaryColor: '#6366f1', lang: 'en' })
    })
  }, [])

  return null
}
// app/layout.jsx
import AllyWidget from '@/components/AllyWidget'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <AllyWidget />
      </body>
    </html>
  )
}

Or — CDN via next/script (zero npm install):

// app/layout.jsx
import Script from 'next/script'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script id="ally-cfg" strategy="beforeInteractive">{`
          window.AllyWidgetOptions = { primaryColor: '#6366f1' };
        `}</Script>
        <Script
          src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"
          strategy="afterInteractive"
        />
      </body>
    </html>
  )
}

Next.js (Pages Router)

// pages/_app.jsx
import { useEffect } from 'react'

export default function App({ Component, pageProps }) {
  useEffect(() => {
    import('ally-widget').then(({ default: AllyWidget }) => {
      AllyWidget.init({ primaryColor: '#6366f1' })
    })
  }, [])

  return <Component {...pageProps} />
}

Vue 3 (Vite)

// src/App.vue
<script setup>
import { onMounted } from 'vue'

onMounted(async () => {
  const { default: AllyWidget } = await import('ally-widget')
  AllyWidget.init({ primaryColor: '#6366f1' })
})
</script>

Nuxt 3

// plugins/ally-widget.client.js
// The .client suffix tells Nuxt this is browser-only — no SSR guard needed
import AllyWidget from 'ally-widget'

export default defineNuxtPlugin(() => {
  AllyWidget.init({ primaryColor: '#6366f1' })
})

SvelteKit

<!-- src/routes/+layout.svelte -->
<script>
  import { onMount } from 'svelte'

  onMount(async () => {
    const { default: AllyWidget } = await import('ally-widget')
    AllyWidget.init({ primaryColor: '#6366f1' })
  })
</script>

<slot />

Astro

<!-- src/layouts/Layout.astro -->
---
// server-side — nothing here
---
<html lang="en">
  <head>...</head>
  <body>
    <slot />

    <script>
      // This block runs in the browser only
      import AllyWidget from 'ally-widget'
      AllyWidget.init({ primaryColor: '#6366f1' })
    </script>
  </body>
</html>

WordPress

// functions.php (child theme or plugin)
function enqueue_ally_widget() {
    wp_enqueue_script(
        'ally-widget',
        'https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js',
        [],
        '1.0.0',
        true   // load in footer
    );

    wp_add_inline_script(
        'ally-widget',
        "window.AllyWidgetOptions = {
            position: 'bottom-right',
            primaryColor: '#6366f1',
            poweredByText: '" . get_bloginfo('name') . "'
        };",
        'before'
    );
}
add_action('wp_enqueue_scripts', 'enqueue_ally_widget');

Laravel / Blade

{{-- resources/views/layouts/app.blade.php --}}
<script>
  window.AllyWidgetOptions = {
    position: 'bottom-right',
    primaryColor: '#6366f1',
    poweredByText: '{{ config("app.name") }}'
  };
</script>
<script src="{{ asset('vendor/ally-widget/ally-widget.min.js') }}"></script>
{{-- or CDN: src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js" --}}

Angular

// src/app/app.component.ts
import { Component, AfterViewInit } from '@angular/core'

@Component({ selector: 'app-root', templateUrl: './app.component.html' })
export class AppComponent implements AfterViewInit {
  ngAfterViewInit() {
    import('ally-widget').then(({ default: AllyWidget }) => {
      AllyWidget.init({ primaryColor: '#6366f1' })
    })
  }
}

PHP (plain)

<!-- footer.php or before </body> -->
<script>
  window.AllyWidgetOptions = {
    position: 'bottom-right',
    primaryColor: '#6366f1'
  };
</script>
<script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>

AllyWidget.init() reference

const instance = AllyWidget.init(options)
// Returns the widget instance, or null when called server-side.
// Handles DOMContentLoaded automatically — safe to call at any time.

Disabling auto-init

Suppress the automatic CDN init when you are calling AllyWidget.init() yourself:

<script>
  window.AllyWidgetOptions = { autoInit: false };
</script>

Customization Guide

Minimal setup — just the essentials

Hide features your site doesn't need to keep the panel focused:

AllyWidget.init({
  disableFeatures: [
    'readable-text',       // remove Dyslexia Font
    'hide-images',         // remove Hide Images
    'annotations',         // hide dev tools in prod
    'accessibility-report'
  ]
})

Custom colour theme

Pass any CSS value. Use primaryColor as a single fallback, or override each element independently with the granular tokens:

AllyWidget.init({
  // Single fallback — all granular keys derive from this if not set
  primaryColor: '#e11d48',

  // ── Toggle button ────────────────────────────────────────
  toggleButtonBg:       '#0f172a',     // dark button
  toggleButtonRingColor:'#e11d48',     // red ring
  toggleButtonIconColor:'#f8fafc',     // near-white icon

  // ── Menu header ──────────────────────────────────────────
  menuHeaderBg:         '#1e293b',     // dark header
  menuHeaderColor:      '#ffffff',

  // ── Feature buttons ──────────────────────────────────────
  featureIconColor:     '#374151',     // default icon fill
  featureIconActiveColor: '#ffffff',   // icon color when ON
  featureButtonActiveBg:  '#e11d48',  // active button bg
  featureButtonHoverBg:   '#f1f5f9',  // hover bg

  // ── Section titles ───────────────────────────────────────
  sectionTitleColor:    '#9ca3af',

  // ── Panel ────────────────────────────────────────────────
  backgroundColor:      '#0f172a',
  cardBackground:       '#1e293b',
  borderColor:          '#334155',
  borderRadius:         '8px',
  buttonBorderRadius:   '12px',
  zIndex:               99999
})

Expanding FAB toggle button

The toggle button expands on hover to reveal a label and optional keyboard shortcut. All aspects are configurable:

AllyWidget.init({
  size:                    '58px',
  toggleButtonBg:          '#C5161D',     // button background
  toggleButtonIconColor:   '#ffffff',     // icon color
  toggleButtonBorderRadius:'16px',        // rounded-square (use '50%' for circle)
  toggleButtonShadow:      '0 4px 14px rgba(0,0,0,0.25)',
  toggleButtonShadowHover: '0 8px 22px rgba(0,0,0,0.28)',

  // Text revealed on hover
  toggleButtonLabel:       'Accessibility',
  toggleButtonShortcut:    'Ctrl+F2',     // omit or set '' to hide
  toggleLabelFontSize:     '1.1rem',
  toggleLabelFontWeight:   '700',
  toggleShortcutFontSize:  '0.7rem',
  toggleShortcutFontWeight:'600',
})

The button animates from its square size to roughly 3× its width on hover, then snaps back when the menu is open.


Custom branding

Replace the footer link with your own, or pass an empty string to hide it entirely:

AllyWidget.init({
  poweredByText: 'My Company',      // shown in the menu footer
  poweredByUrl:  'https://mycompany.com'
})

// Hide the branding link completely:
AllyWidget.init({ poweredByText: '' })

Rename, re-icon, or recolor any button

AllyWidget.init({
  featureOverrides: {
    // Rename
    'text-to-speech': { label: 'Read Page Aloud' },
    'reading-aid':    { label: 'Focus Line' },

    // Rename + custom SVG
    'bold-text': {
      label: 'Bold',
      icon:  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
    },

    // Color only — no SVG required
    'contrast-toggle':  { iconColor: '#6d28d9', iconInnerColor: '#c4b5fd' },
    'highlight-links':  { iconColor: '#0369a1' },

    // Wildcard — all others
    '*': { iconColor: '#374151' }
  }
})

Custom toggle button icon

AllyWidget.init({
  // Built-in variants: 'default' | 'person' | 'eye' | 'universal'
  icon: 'universal',

  // Or pass any SVG string:
  icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
})

Position and size

AllyWidget.init({
  position: 'bottom-left',   // bottom-right | bottom-left | top-right | top-left
  offset:   [24, 32],        // [horizontal, vertical] px from edge
  size:     '60px'           // toggle button size
})

Event callbacks for analytics

AllyWidget.init({
  onOpen()  { analytics.track('ally_open') },
  onClose() { analytics.track('ally_close') },

  onFeatureToggle(key, enabled) {
    analytics.track('ally_feature', { key, enabled })
    // key examples: 'bold-text', 'contrast-toggle', 'text-to-speech'
  },

  onReset() { analytics.track('ally_reset') }
})

Full example — everything configured

window.AllyWidgetOptions = {
  // Placement
  position:         'bottom-right',
  offset:           [20, 20],
  size:             '56px',
  keyboardShortcut: true,            // Alt+A

  // Language
  lang:             'ne',            // 'en' | 'ne'

  // Storage
  storageKey:       'myapp-ally',

  // Branding
  poweredByText:    'My Company',
  poweredByUrl:     'https://mycompany.com',

  // Theme — base
  primaryColor:     '#6366f1',
  borderRadius:     '12px',
  buttonBorderRadius: '50%',
  zIndex:           9999,

  // Theme — toggle button FAB
  toggleButtonBg:           '#6366f1',
  toggleButtonIconColor:    '#ffffff',
  toggleButtonBorderRadius: '50%',
  toggleButtonShadow:       '0 4px 14px rgba(0,0,0,0.22)',
  toggleButtonShadowHover:  '0 8px 22px rgba(0,0,0,0.22)',
  toggleButtonLabel:        'Accessibility',
  toggleButtonShortcut:     'Ctrl+F2',
  toggleLabelFontSize:      '1rem',
  toggleLabelFontWeight:    '700',
  toggleShortcutFontSize:   '0.7rem',
  toggleShortcutFontWeight: '600',

  // Theme — menu
  menuHeaderBg:           '#4f46e5',
  featureIconColor:       '#374151',
  featureIconActiveColor: '#ffffff',
  featureButtonActiveBg:  '#6366f1',
  sectionTitleColor:      '#6b7280',

  // Features
  disableFeatures:  ['annotations', 'accessibility-report'],

  featureOverrides: {
    'text-to-speech': { label: 'Read Aloud' },
    '*':              { iconColor: '#374151' },
    'contrast-toggle':{ iconColor: '#6366f1', iconInnerColor: '#a5b4fc' }
  },

  // TTS
  ttsNativeVoiceLang: 'ne-NP',
  ttsRate:            0.9,
  ttsPitch:           1.0,

  // Callbacks
  onOpen()  { console.log('opened') },
  onClose() { console.log('closed') },
  onFeatureToggle(key, enabled) { console.log(key, enabled) },
  onReset() { console.log('reset') }
}

Dev Mode

Append ?ally-dev=true to any URL to enable the Annotations and Accessibility Report tools. These run an axe-core scan and overlay issue markers on the page — useful during development, hidden in production.

https://yoursite.com/page?ally-dev=true

The axe-core library is loaded on demand in dev mode only — zero weight in production.

Hiding the violation badge

When violations are found, a red number badge appears on the toggle button. To hide it:

AllyWidget.init({
  showViolationBubble: false
});

Keyboard Shortcut

Alt + A toggles the panel on any page. Disable it with keyboardShortcut: false.


Publishing to npm

# Set your token
export NPM_TOKEN=your_token_here   # or put it in .env

# Build then publish
npm run build
npm publish

The .npmrc at the project root reads NPM_TOKEN automatically:

//registry.npmjs.org/:_authToken=${NPM_TOKEN}

License

MIT — see LICENSE