If you have customized your PeopleFluent LMS login page to include a show/hide password toggle, review each section below and apply the changes that apply to your implementation.
1. HTML Markup
Your password field and toggle button must be wrapped in a container that establishes a positioning context for the button. The toggle button must immediately follow the password input as a sibling element so that CSS scoping and the :has() selector work correctly.
Standard login page (login.wm)
The password input and toggle button must be inside the existing .group div:
<div class="group">
<input name="PWD" type="password" placeholder="Password" id="PWD"/>
<button type="button" id="togglePassword" class="toggle-button" aria-label="Show password">
<svg id="eye-icon-show" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="20" height="20">
<!-- eye open path -->
</svg>
<svg id="eye-icon-hide" class="hidden" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="20" height="20">
<!-- eye-slash path -->
</svg>
</button>
</div>Key points:
- The “show” icon (eye-icon-show) has no hidden class initially — it is visible when the password is masked.
- The “hide” icon (eye-icon-hide) has the hidden class initially — it is hidden until the password is revealed.
- Both id values on the SVGs (eye-icon-show, eye-icon-hide) are required by the JavaScript.
2. CSS
Standard login page — login.css
Add the following rules at the end of login.css. Do not remove the existing input[type="text"] and input[type="password"] rules already in the file
/* Positioning context for the toggle button */
#login-box .group {
position: relative;
width: 100%;
}
/* Reserve space for the toggle button - only on inputs that have one */
#login-box .group input:has(+ .toggle-button) {
width: 100%;
padding-right: 40px;
box-sizing: border-box;
}
/* Toggle button placement */
#login-box .group .toggle-button {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
background: transparent;
border: none;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
color: #6b7280;
}
/* Icon sizing */
#login-box .group .toggle-button svg {
width: 20px;
height: 20px;
display: block;
}
/* Hide inactive icon - scoped to avoid affecting other elements */
#login-box .group .toggle-button svg.hidden {
display: none;
}
/* General utility - restore if your stylesheet previously included this */
.hidden {
display: none !important;
}
input:has(+ .toggle-button) note: This selector targets only the input immediately followed by the toggle button, so other inputs in .group (e.g., the User ID field) are not affected. This selector is supported in all modern browsers. If you must support IE11, replace it with a dedicated class on the password input (e.g., input.password-input) and style that instead.
3. JavaScript
Add the following script block, or update your existing toggle script to match. The logic below is ES5-compatible (uses var, not const/let) so it does not break in legacy browsers or interfere with other scripts sharing the same <script> tag.
Replace any previous toggle implementation with this:
// Show/Hide password toggle
document.addEventListener('DOMContentLoaded', function() {
var passwordInput = document.getElementById('password'); // use 'PWD' for standard login
var toggleButton = document.getElementById('togglePassword');
var iconShow = document.getElementById('eye-icon-show');
var iconHide = document.getElementById('eye-icon-hide');
// Guard: bail out cleanly if any element is missing
if (!passwordInput || !toggleButton || !iconShow || !iconHide) {
console.error("Error: Could not find password toggle elements.");
return;
}
// Updates aria-label and aria-pressed to reflect current state
function updatePasswordToggleState() {
var passwordShown = passwordInput.getAttribute('type') === 'text';
toggleButton.setAttribute('aria-label', passwordShown ? 'Hide password' : 'Show password');
toggleButton.setAttribute('aria-pressed', passwordShown ? 'true' : 'false');
}
// Associate button with input for screen readers
toggleButton.setAttribute('aria-controls', 'PWD'); // use 'PWD' for standard login
// Set correct initial state (matches the initial type="password" in the markup)
updatePasswordToggleState();
toggleButton.addEventListener('click', function() {
var newType = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
passwordInput.setAttribute('type', newType);
updatePasswordToggleState();
// Set icon visibility deterministically - do not use classList.toggle()
if (newType === 'text') {
iconShow.classList.add('hidden');
iconHide.classList.remove('hidden');
} else {
iconShow.classList.remove('hidden');
iconHide.classList.add('hidden');
}
});
});
Standard login page note: The password input ID is PWD, not password. Update both getElementById calls and the aria-controls value accordingly.
4. Common Mistakes to Avoid
| Issue | Symptom | Fix |
|---|---|---|
| CSS scoped to input[type="password"] | Toggle button overlaps text after revealing password | Use input:has(+ .toggle-button) or a dedicated class on the input |
| Using classList.toggle() | Icons desync if DOM initial state ever drifts | Use explicit classList.add/remove driven by the computed new type |
| Using const/let in a shared <script> block | Entire script block fails to parse in IE11, breaking login | Use var throughout the toggle script |
| passwordInput not in the guard clause | Runtime error if the password field is renamed or removed | Include all four element references in the null check |
| Removing the global .hidden utility class | Other elements using class="hidden" become visible | Keep .hidden { display: none !important; } in the stylesheet |
| Using top: 42% for the toggle button | Button drifts off-center across font sizes or input heights | Use top: 50% with transform: translateY(-50%) |