From 8ed65942cb2f938558a5285abe8f38d4930b999a Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 23 Oct 2025 11:24:17 +0800 Subject: [PATCH 1/2] MDL-85774 login: Remove visually-hidden links Visually hidden links on login error/info disrupt tab order. We must remove them. Instead, announce the div containing the login error/info messages on page reload. The visually hidden signup link has also been removed. --- auth/tests/behat/loginform.feature | 4 +++- lib/templates/loginform.mustache | 36 +++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/auth/tests/behat/loginform.feature b/auth/tests/behat/loginform.feature index 7942678268d..2f6c37d7e2b 100644 --- a/auth/tests/behat/loginform.feature +++ b/auth/tests/behat/loginform.feature @@ -96,13 +96,15 @@ Feature: Test if the login form provides the correct feedback And I follow "Log in" Then the focused element is "Password" "field" + @accessibility Scenario: Test the login page focus after error feature Given I follow "Log in" And I set the field "Username" to "admin" And I set the field "Password" to "wrongpassword" And I press "Log in" - And I press the tab key + And I wait until the page is ready Then the focused element is "Username" "field" + And the page should meet accessibility standards with "best-practice" extra tests Scenario: Display the password visibility toggle icon Given the following config values are set as admin: diff --git a/lib/templates/loginform.mustache b/lib/templates/loginform.mustache index 55a8a25e93c..3d0e6d7ca68 100644 --- a/lib/templates/loginform.mustache +++ b/lib/templates/loginform.mustache @@ -115,16 +115,11 @@ {{/maintenance}} {{#error}} - {{error}} - + {{/error}} {{#info}} - {{info}} - +
{{info}}
{{/info}} - {{#cansignup}} - {{#str}} tocreatenewaccount {{/str}} - {{/cansignup}} {{#showloginform}}
@@ -235,9 +230,30 @@ }); {{/autofocusform}} {{/error}} - {{#error}} - document.getElementById('loginerrormessage').focus(); - {{/error}} + require(['core/pending'], function(Pending) { + const errorMessageDiv = document.getElementById('loginerrormessage'); + const infoMessageDiv = document.getElementById('logininfomessage'); + const errorMessage = errorMessageDiv?.textContent.trim(); + const infoMessage = infoMessageDiv?.textContent.trim(); + if (errorMessage || infoMessage) { + const pendingJS = new Pending('login-move-focus'); + const usernameField = document.getElementById('username'); + setTimeout(function() { + // Focus on the username field on error. + if (errorMessage && usernameField) { + usernameField.focus(); + } + // Append a non-breaking space to the error/status message so screen readers will announce them after page load. + if (errorMessage) { + errorMessageDiv.innerHTML += " "; + } + if (infoMessage) { + infoMessageDiv.innerHTML += " "; + } + pendingJS.resolve(); + }, 500); + } + }); {{#togglepassword}} require(['core/togglesensitive'], function(ToggleSensitive) { ToggleSensitive.init("password", {{smallscreensonly}}); From 53a9a3f1088f33ded99abfd1078ab62a1c428079 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 23 Oct 2025 11:26:44 +0800 Subject: [PATCH 2/2] MDL-85774 login: Check for username/password fields before moving focus When the login form is not shown but $CFG->loginautofocus is turned on, we need to verify first that the username and password fields exist before we move the focus. Otherwise, the JS will break. --- lib/templates/loginform.mustache | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/templates/loginform.mustache b/lib/templates/loginform.mustache index 3d0e6d7ca68..9b2a16e2335 100644 --- a/lib/templates/loginform.mustache +++ b/lib/templates/loginform.mustache @@ -219,10 +219,11 @@ require(['core_form/events'], function(FormEvent) { function autoFocus() { const userNameField = document.getElementById('username'); - if (userNameField.value.length == 0) { + const passwordField = document.getElementById('password'); + if (userNameField && userNameField.value.length == 0) { userNameField.focus(); - } else { - document.getElementById('password').focus(); + } else if (passwordField) { + passwordField.focus(); } } autoFocus();