From a45b56f658a3b5c5722ab8e1da2f9688a7be1ea4 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 19 Jun 2026 01:20:19 +0200 Subject: [PATCH] dashboard(modal): label the dialog via aria-labelledby for a11y Give the themed dialog an accessible name (role=dialog requires one): label it by its title when present, else by its message, via aria-labelledby on the box. Addresses an a11y review note on the stop-confirm modal. --- frontend/packages/dashboard/src/modal.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/packages/dashboard/src/modal.js b/frontend/packages/dashboard/src/modal.js index 5e6fd406..01ff8f24 100644 --- a/frontend/packages/dashboard/src/modal.js +++ b/frontend/packages/dashboard/src/modal.js @@ -52,9 +52,19 @@ export function openDialog(opts = {}) { return { spec: b, btn }; }); - const box = el('div', { class: 'tc-box', role: 'dialog', 'aria-modal': 'true' }, - title ? el('div', { class: 'tc-title' }, title) : null, - message ? el('div', { class: 'tc-message' }, message) : null, + // Give the dialog an accessible name: label it by its title if present, + // else by its message, via `aria-labelledby` (a11y — role=dialog needs a + // name). Only the labelling element carries the id. + const labelId = 'tc-dlg-' + Math.random().toString(36).slice(2, 9); + const titleEl = title ? el('div', { class: 'tc-title', id: labelId }, title) : null; + const messageEl = message + ? el('div', title ? { class: 'tc-message' } : { class: 'tc-message', id: labelId }, message) + : null; + const boxAttrs = { class: 'tc-box', role: 'dialog', 'aria-modal': 'true' }; + if (titleEl || messageEl) boxAttrs['aria-labelledby'] = labelId; + const box = el('div', boxAttrs, + titleEl, + messageEl, content || null, el('div', { class: 'tc-actions' }, ...btnEls.map((b) => b.btn)));