From 640d38b895d5ac1f365b0fd61a458f2634152166 Mon Sep 17 00:00:00 2001 From: Lucian Stoian Date: Thu, 26 Mar 2026 11:14:22 +0200 Subject: [PATCH] fix(selectlist-item): SITES-24721 sync aria-selected after render (WCAG 4.1.2) When selection is applied before render() sets role="option", the selected setter runs without a role attribute and skips aria-selected; the subsequent early return prevents updating after role is assigned. Call _syncAriaSelected() after render so listbox options expose state to assistive technology. Skill-Version: cursor-toolkit/commit-and-pr v0.2.1 Made-with: Cursor --- .../src/scripts/SelectListItem.js | 20 +++++++++++++++---- .../src/tests/test.SelectList.Item.js | 9 +++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/coral-component-list/src/scripts/SelectListItem.js b/coral-component-list/src/scripts/SelectListItem.js index c2f968c90d..981744661e 100644 --- a/coral-component-list/src/scripts/SelectListItem.js +++ b/coral-component-list/src/scripts/SelectListItem.js @@ -149,6 +149,19 @@ const SelectListItem = Decorator(class extends BaseComponent(HTMLElement) { return this._elements.icon; } + /** + Sync aria-selected with {@link #selected} when the element has a role that supports it. + Must run after {@link #render} assigns the default {@code role="option"}, because selection + may be set before the role attribute exists (WCAG 4.1.2). + @private + */ + _syncAriaSelected() { + if (this.hasAttribute('role') && + VALID_ARIA_SELECTED_ROLES_REGEXP.test(this.getAttribute('role'))) { + this.setAttribute('aria-selected', this._selected); + } + } + /** Whether the item is selected. @@ -172,10 +185,7 @@ const SelectListItem = Decorator(class extends BaseComponent(HTMLElement) { this._reflectAttribute('selected', this.disabled ? false : this._selected); this.classList.toggle('is-selected', this._selected); - if (this.hasAttribute('role') && - VALID_ARIA_SELECTED_ROLES_REGEXP.test(this.getAttribute('role'))) { - this.setAttribute('aria-selected', this._selected); - } + this._syncAriaSelected(); // Toggle check icon this._elements.checkIcon.hidden = !this._selected; @@ -245,6 +255,8 @@ const SelectListItem = Decorator(class extends BaseComponent(HTMLElement) { // Assign the content zones, moving them into place in the process this.content = content; + + this._syncAriaSelected(); } }); diff --git a/coral-component-list/src/tests/test.SelectList.Item.js b/coral-component-list/src/tests/test.SelectList.Item.js index 8d8107f458..d1b65cc63a 100644 --- a/coral-component-list/src/tests/test.SelectList.Item.js +++ b/coral-component-list/src/tests/test.SelectList.Item.js @@ -47,6 +47,15 @@ describe('SelectList.Item', function () { const el = new SelectList.Item(); expect(el.selected).to.be.false; }); + + it('should expose aria-selected after render when selected was set before role exists (SITES-24721)', function () { + const el = new SelectList.Item(); + el.textContent = 'Default'; + el.selected = true; + helpers.build(el); + expect(el.getAttribute('role')).to.equal('option'); + expect(el.getAttribute('aria-selected')).to.equal('true'); + }); }); describe('#disabled', function () {