Skip to content

Commit fc01a47

Browse files
Improve accessibility for notifications (#115)
* Provide screen readers with information on unread notifications * Add visual and ARIA clarifications for dismissing notifications * Add title to notifications page This allows screen readers and other accessibility tools to identify the page to the user.
1 parent 6b80c30 commit fc01a47

3 files changed

Lines changed: 12 additions & 2 deletions

File tree

src/DfE.ExternalApplications.Web/Pages/Notifications/Index.cshtml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
@page
22
@model DfE.ExternalApplications.Web.Pages.Notifications.IndexModel
3+
@{
4+
ViewData["Title"] = "Notifications";
5+
}
36

47
<h1 class="govuk-heading-l">Notifications</h1>
58

@@ -49,7 +52,7 @@
4952
<td class="govuk-table__cell">${n.message || ''}</td>
5053
<td class="govuk-table__cell">${mapTypeTag(n.type)}</td>
5154
<td class="govuk-table__cell">${dateStr}</td>
52-
<td class="govuk-table__cell"><a class="govuk-link" data-id="${n.id}" href="#">Dismiss</a></td>
55+
<td class="govuk-table__cell"><a class="govuk-link" data-id="${n.id}" href="#">Dismiss <span class="govuk-visually-hidden">this notification</span></a></td>
5356
`;
5457
tr.querySelector('a').onclick = async (e) => {
5558
e.preventDefault();
@@ -61,7 +64,8 @@
6164
headers: { 'RequestVerificationToken': antiForgeryToken() }
6265
});
6366
if (response.ok) {
64-
tr.remove();
67+
tr.innerHTML = '<td class="govuk-table__cell" aria-live="assertive" role="alert" colspan="4">Notification dismissed.</td>';
68+
setTimeout(() => tr.remove(), 3000);
6569
} else {
6670
console.error('Failed to dismiss notification:', response.status, await response.text());
6771
alert('Failed to dismiss notification. Please try again.');

src/DfE.ExternalApplications.Web/Pages/Shared/_Layout.cshtml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
<li class="govuk-service-navigation__item">
189189
<a class="govuk-service-navigation__link app-badge-container" href="/Notifications" style="position: relative;">
190190
Notifications
191+
<span id="notifications-unread-sr" class="govuk-visually-hidden"></span>
191192
<span id="notifications-unread-badge" class="app-notifications-badge" aria-hidden="true"></span>
192193
</a>
193194
</li>

src/DfE.ExternalApplications.Web/wwwroot/js/notifications.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const container = () => document.getElementById('notification-container');
44
const unreadBadge = () => document.getElementById('notifications-unread-badge');
5+
const unreadSr = () => document.getElementById('notifications-unread-sr');
56
const antiForgeryToken = () => {
67
const input = document.querySelector('input[name="__RequestVerificationToken"]');
78
return input?.value;
@@ -115,9 +116,12 @@ async function refreshUnreadCount() {
115116
const unread = await fetch(`/notifications/unread`, { credentials: 'include' }).then(r => r.ok ? r.json() : []);
116117
const count = Array.isArray(unread) ? unread.length : 0;
117118
const badge = unreadBadge();
119+
const notificationsUnreadSr = unreadSr();
118120
if (!badge) return;
119121
if (count > 0) {
120122
badge.textContent = count > 99 ? '99+' : String(count);
123+
const accessibleCount = count > 99 ? 'over 99' : String(count);
124+
notificationsUnreadSr.textContent = `(You have ${accessibleCount} unread ${count === 1 ? 'notification' : 'notifications'})`;
121125
// Ensure visual badge even if CSS fails to load (24x24 circle)
122126
badge.style.display = 'inline-block';
123127
badge.style.position = 'absolute';
@@ -135,6 +139,7 @@ async function refreshUnreadCount() {
135139
badge.style.zIndex = '2';
136140
} else {
137141
badge.textContent = '';
142+
notificationsUnreadSr.textContent = '(You have no unread notifications)';
138143
badge.style.display = 'none';
139144
}
140145
} catch { }

0 commit comments

Comments
 (0)