From 1a5213c5b2c7bb3429e9966fe85910e861c72adc Mon Sep 17 00:00:00 2001 From: karthikeyansundaram2 Date: Sat, 18 Apr 2026 21:46:48 +0530 Subject: [PATCH] fix: correct ARIA expanded and activedescendant states for menu buttons When navigating the Chapters or Captions menus with a screen reader, the and attributes were not being updated correctly. This broke keyboard navigation and caused confusion for assistive technology users. This commit fixes the issue by: - Setting when the menu is shown (on hover or press) - Setting and removing when the menu is hidden - Pointing to the ID of the currently focused menu item Fixes #7688 --- src/js/menu/menu-button.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/js/menu/menu-button.js b/src/js/menu/menu-button.js index ca26b94328..5d4c5a33d0 100644 --- a/src/js/menu/menu-button.js +++ b/src/js/menu/menu-button.js @@ -58,6 +58,7 @@ class MenuButton extends Component { this.on(this.menuButton_, 'mouseenter', () => { this.addClass('vjs-hover'); this.menu.show(); + this.menuButton_.el_.setAttribute('aria-expanded', 'true'); Events.on(document, 'keyup', this.handleMenuKeyUp_); }); this.on('mouseleave', (e) => this.handleMouseLeave(e)); @@ -272,6 +273,11 @@ class MenuButton extends Component { handleMouseLeave(event) { this.removeClass('vjs-hover'); Events.off(document, 'keyup', this.handleMenuKeyUp_); + + if (!this.buttonPressed_) { + this.menuButton_.el_.setAttribute('aria-expanded', 'false'); + this.menuButton_.el_.removeAttribute('aria-activedescendant'); + } } /** @@ -395,6 +401,7 @@ class MenuButton extends Component { } this.menu.focus(); + this.updateActiveDescendant_(); } } @@ -407,7 +414,31 @@ class MenuButton extends Component { this.menu.unlockShowing(); this.menu.hide(); this.menuButton_.el_.setAttribute('aria-expanded', 'false'); + this.menuButton_.el_.removeAttribute('aria-activedescendant'); + } + } + + /** + * Synchronize `aria-activedescendant` on the menu button with the currently + * focused menu item. If the focused item has no `id`, one is generated so it + * can be referenced. + * + * @private + */ + updateActiveDescendant_() { + const children = this.menu.children(); + const focusedChild = children && children[this.menu.focusedChild_]; + + if (!focusedChild || !focusedChild.el_) { + this.menuButton_.el_.removeAttribute('aria-activedescendant'); + return; } + + if (!focusedChild.el_.id) { + focusedChild.el_.id = `${this.menuButton_.id()}-item-${this.menu.focusedChild_}`; + } + + this.menuButton_.el_.setAttribute('aria-activedescendant', focusedChild.el_.id); } /**