From c8db419c7f4c99ff3956e72bc2e9fddd67a42bfc Mon Sep 17 00:00:00 2001 From: sherwinski Date: Wed, 22 Jul 2026 12:26:20 -0700 Subject: [PATCH 1/3] fix: add accessible name to subscription bell launcher button The bell launcher is an icon-only button with no discernible text, so customer sites fail accessibility audits ("Buttons must have discernible text"). Add a default aria-label, configurable and localizable via the existing bell text config as 'launcher.label', and hide the decorative bell SVG from the accessibility tree. --- package.json | 2 +- src/page/bell/Bell.test.ts | 30 ++++++++++++++++++++++++++++++ src/page/bell/Bell.ts | 9 ++++++++- src/page/bell/Message.ts | 2 +- src/shared/prompts/types.ts | 1 + 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 78c980805..36ab7086e 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ }, { "path": "./build/releases/OneSignalSDK.page.es6.js", - "limit": "42.65 kB", + "limit": "42.7 kB", "gzip": true }, { diff --git a/src/page/bell/Bell.test.ts b/src/page/bell/Bell.test.ts index 39f850f19..effafa3a2 100644 --- a/src/page/bell/Bell.test.ts +++ b/src/page/bell/Bell.test.ts @@ -156,6 +156,36 @@ describe('Bell', () => { ); } + test('launcher button has a default accessible name', async () => { + mockCreateDeps(); + const bell = new Bell({ enable: true, showLauncherAfter: 0 }); + await bell._create(); + + expect(bell._button._element!.getAttribute('aria-label')).toBe('Manage notifications'); + }); + + test('launcher button accessible name is configurable via text', async () => { + mockCreateDeps(); + const bell = new Bell({ + enable: true, + showLauncherAfter: 0, + // @ts-expect-error - partial text config + text: { 'launcher.label': 'Διαχείριση ειδοποιήσεων' }, + }); + await bell._create(); + + expect(bell._button._element!.getAttribute('aria-label')).toBe('Διαχείριση ειδοποιήσεων'); + }); + + test('bell svg is hidden from the accessibility tree', async () => { + mockCreateDeps(); + const bell = new Bell({ enable: true, showLauncherAfter: 0 }); + await bell._create(); + + const svg = bell._button._element!.querySelector('svg')!; + expect(svg.getAttribute('aria-hidden')).toBe('true'); + }); + test('mouseleave on launcher blurs the button', async () => { mockCreateDeps(); const bell = new Bell({ enable: true, showLauncherAfter: 0 }); diff --git a/src/page/bell/Bell.ts b/src/page/bell/Bell.ts index 57b485bd8..fbf6e309f 100755 --- a/src/page/bell/Bell.ts +++ b/src/page/bell/Bell.ts @@ -21,7 +21,7 @@ import Dialog from './Dialog'; import Launcher from './Launcher'; import Message from './Message'; -const logoSvg = ``; +const logoSvg = ``; const DEFAULT_SIZE: BellSize = 'medium'; const DEFAULT_POSITION: BellPosition = 'bottom-right'; @@ -31,7 +31,10 @@ const VALID_SIZES: readonly string[] = ['small', 'medium', 'large']; const VALID_POSITIONS: readonly string[] = ['bottom-left', 'bottom-right']; const VALID_THEMES: readonly string[] = ['default', 'inverse']; +const DEFAULT_LAUNCHER_LABEL = 'Manage notifications'; + const DEFAULT_TEXT: BellText = { + 'launcher.label': DEFAULT_LAUNCHER_LABEL, 'tip.state.unsubscribed': 'Subscribe to notifications', 'tip.state.subscribed': "You're subscribed to notifications", 'tip.state.blocked': "You've blocked notifications", @@ -213,6 +216,10 @@ export default class Bell { 'beforeend', '', ); + this._button._element?.setAttribute( + 'aria-label', + this._options.text['launcher.label'] || DEFAULT_LAUNCHER_LABEL, + ); addDomElement( this._launcher._selector, 'beforeend', diff --git a/src/page/bell/Message.ts b/src/page/bell/Message.ts index d1491beb5..d0a80d806 100755 --- a/src/page/bell/Message.ts +++ b/src/page/bell/Message.ts @@ -64,6 +64,6 @@ export default class Message { _getTipForState(): string { const key = TIP_KEYS[this._bell._state]; - return key ? this._bell._options.text[key] : ''; + return key ? (this._bell._options.text[key] ?? '') : ''; } } diff --git a/src/shared/prompts/types.ts b/src/shared/prompts/types.ts index ca095a72a..98156605a 100644 --- a/src/shared/prompts/types.ts +++ b/src/shared/prompts/types.ts @@ -116,6 +116,7 @@ export type BellSize = 'small' | 'medium' | 'large'; export type BellPosition = 'bottom-left' | 'bottom-right'; export interface BellText { + 'launcher.label'?: string; 'tip.state.unsubscribed': string; 'tip.state.subscribed': string; 'tip.state.blocked': string; From a1970bdea0b95cc89823ae02a2675943c7dc32f4 Mon Sep 17 00:00:00 2001 From: sherwinski Date: Wed, 22 Jul 2026 12:29:08 -0700 Subject: [PATCH 2/3] test: use English custom label in bell aria-label test --- src/page/bell/Bell.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/page/bell/Bell.test.ts b/src/page/bell/Bell.test.ts index effafa3a2..f5645cf6c 100644 --- a/src/page/bell/Bell.test.ts +++ b/src/page/bell/Bell.test.ts @@ -170,11 +170,11 @@ describe('Bell', () => { enable: true, showLauncherAfter: 0, // @ts-expect-error - partial text config - text: { 'launcher.label': 'Διαχείριση ειδοποιήσεων' }, + text: { 'launcher.label': 'Notification preferences' }, }); await bell._create(); - expect(bell._button._element!.getAttribute('aria-label')).toBe('Διαχείριση ειδοποιήσεων'); + expect(bell._button._element!.getAttribute('aria-label')).toBe('Notification preferences'); }); test('bell svg is hidden from the accessibility tree', async () => { From bb13fd4f6a85d52fb68b81a2108c9dd78cad422d Mon Sep 17 00:00:00 2001 From: sherwinski Date: Wed, 22 Jul 2026 14:20:27 -0700 Subject: [PATCH 3/3] refactor: rename bell label key to launcher.button.aria-label The key never renders visible text, unlike other BellText keys, so name it after the attribute it populates to avoid ambiguity. --- src/page/bell/Bell.test.ts | 2 +- src/page/bell/Bell.ts | 4 ++-- src/shared/prompts/types.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/page/bell/Bell.test.ts b/src/page/bell/Bell.test.ts index f5645cf6c..6bb2f7092 100644 --- a/src/page/bell/Bell.test.ts +++ b/src/page/bell/Bell.test.ts @@ -170,7 +170,7 @@ describe('Bell', () => { enable: true, showLauncherAfter: 0, // @ts-expect-error - partial text config - text: { 'launcher.label': 'Notification preferences' }, + text: { 'launcher.button.aria-label': 'Notification preferences' }, }); await bell._create(); diff --git a/src/page/bell/Bell.ts b/src/page/bell/Bell.ts index fbf6e309f..4efd2e194 100755 --- a/src/page/bell/Bell.ts +++ b/src/page/bell/Bell.ts @@ -34,7 +34,7 @@ const VALID_THEMES: readonly string[] = ['default', 'inverse']; const DEFAULT_LAUNCHER_LABEL = 'Manage notifications'; const DEFAULT_TEXT: BellText = { - 'launcher.label': DEFAULT_LAUNCHER_LABEL, + 'launcher.button.aria-label': DEFAULT_LAUNCHER_LABEL, 'tip.state.unsubscribed': 'Subscribe to notifications', 'tip.state.subscribed': "You're subscribed to notifications", 'tip.state.blocked': "You've blocked notifications", @@ -218,7 +218,7 @@ export default class Bell { ); this._button._element?.setAttribute( 'aria-label', - this._options.text['launcher.label'] || DEFAULT_LAUNCHER_LABEL, + this._options.text['launcher.button.aria-label'] || DEFAULT_LAUNCHER_LABEL, ); addDomElement( this._launcher._selector, diff --git a/src/shared/prompts/types.ts b/src/shared/prompts/types.ts index 98156605a..d1404ca01 100644 --- a/src/shared/prompts/types.ts +++ b/src/shared/prompts/types.ts @@ -116,7 +116,7 @@ export type BellSize = 'small' | 'medium' | 'large'; export type BellPosition = 'bottom-left' | 'bottom-right'; export interface BellText { - 'launcher.label'?: string; + 'launcher.button.aria-label'?: string; 'tip.state.unsubscribed': string; 'tip.state.subscribed': string; 'tip.state.blocked': string;