In this small task, we will implement password strength indicator with exact error messages using plain JavaScript. We do not use any third party library for this task. We use bootstrap CSS for the UI. The final output will be something like this

Which password is considered as strong or week depends on the application to application. For our example, we are considering the following rules

  • Password must contain at least 6 characters
  • Password must contain a lower case letter
  • Password must contain an upper case letter
  • Password must contain a number
  • Password must contain a special character
  • More than 8 characters is optional, if it is more than that, we consider that as very good password

We are giving One (1) point when the password entered by the user meets each one of the above rules. So the maximum score can be 6. When the password strength is 3, we are considering it as poor, 4 for average, 5 for good and 6 for very good. You can adjust these numbers as per your requirements.

How we are checking each rule ?
  • We are using length property of a string to determine the length of the password
  • We check the password against the Regular Expression “/[a-z]/”, to check if it contains a lowercase letter
  • We check the password against the Regular Expression “/[A-Z]/”, to check if it contains a uppercase letter
  • We check the password against the Regular Expression “/\d/”, to check it it contains a number
  • We check the password against the Regular Expression “/[-+_!@#$%^&*.,?{}()\[\]]/”, to check it it contains a special character. You can add or remove any special character to/from this regular expression depending on your requirement.

HTML Part of the application:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <link rel="stylesheet" href="styles.css">
    <title>Document</title>
</head>

<body class="container">
    <div class="mb-3 mt-5 ">
        <label for="password" class="form-label">Password</label>
        <input type="password" class="form-control" id="password">
    </div>
    <div class="password-strength" id="passwordStrengthMeter">
        <span class="password-strength-background" id="poor"></span>
        <span class="password-strength-background" id="average"></span>
        <span class="password-strength-background" id="good"></span>
        <span class="password-strength-background" id="very-good"></span>
    </div>
    <div style="width: 30vw;">
        <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
            <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
                <path
                    d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
            </symbol>

            <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
                <path
                    d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
            </symbol>
        </svg>

        <div class="alert alert-danger d-flex align-items-center small p-1" role="alert" id="lengthMessage">
            <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:">
                <use xlink:href="#exclamation-triangle-fill" />
            </svg>
            <div>
                Password must be of 6 characters length
            </div>
        </div>

        <div class="alert alert-danger d-flex align-items-center small p-1" role="alert" id="lowerCaseLetter">
            <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:">
                <use xlink:href="#exclamation-triangle-fill" />
            </svg>
            <div>
                Password must contain a lower case letter
            </div>
        </div>

        <div class="alert alert-danger d-flex align-items-center small p-1" role="alert" id="upperCaseLetter">
            <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:">
                <use xlink:href="#exclamation-triangle-fill" />
            </svg>
            <div>
                Password must contain a upper case letter
            </div>
        </div>

        <div class="alert alert-danger d-flex align-items-center small p-1" role="alert" id="numberCharacter">
            <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:">
                <use xlink:href="#exclamation-triangle-fill" />
            </svg>
            <div>
                Password must contain a number
            </div>
        </div>

        <div class="alert alert-danger d-flex align-items-center small p-1" role="alert" id="specialCharacter">
            <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:">
                <use xlink:href="#exclamation-triangle-fill" />
            </svg>
            <div>
                Password must contain a special character
            </div>
        </div>
    </div>
    <script src="index.js"></script>
</body>

</html>

CSS:

.show-password-meter{
    display: flex;
    width: 50%;
    height: 15px;
}

.hide-password-meter{
    display: none;
}

.password-strength span{
    position: relative;    
    height: 5px;
    width: 25%;
    border-radius: 5px;
    margin: 5px;
}

.password-strength-background {
    background-color: gray;
}

.weak-password {
    background-color: red;
}

.average-password {
    background-color: orange;
}

.good-password {
    background-color: yellow;
}

.verygood-password {
    background-color: green;
}

Now, the important part, JavaScript

let regexForLowerLetter = /[a-z]/;
let regexForUpperLetter = /[A-Z]/;
let regexForNumber = /\d/;
let regexForSpecialCharacter = /[-+_!@#$%^&*.,?{}()\[\]]/
let passwordStrength = 0;

let lengthScore = 0;
let goodLengthScore = 0;
let lowerLetterScore = 0;
let upperLetterScore = 0;
let numberScore = 0;
let specialCharacterScore = 0;

let passwordElement = document.getElementById('password');
let passwordStrengthMeterElement = document.getElementById('passwordStrengthMeter')
let lengthMessageElement = document.getElementById('lengthMessage');
let lowerCaseMessageElement = document.getElementById('lowerCaseLetter');
let upperCaseMessageElement = document.getElementById('upperCaseLetter');
let numberMessageElement = document.getElementById('numberCharacter');
let specialCharacterMessageElement = document.getElementById('specialCharacter');

let poorPasswordElement = document.getElementById('poor')
let averagePasswordElement = document.getElementById('average')
let goodPasswordElement = document.getElementById('good')
let veryGoodPasswordElement = document.getElementById('very-good')

passwordElement.addEventListener('input', () => {
    let pw = passwordElement.value;

    if (pw.length > 5) {
        lengthScore = 1;

        passwordStrengthMeterElement.classList.remove('hide-password-meter');
        passwordStrengthMeterElement.classList.add('show-password-meter')

        lengthMessageElement.classList.remove('alert-danger')
        lengthMessageElement.classList.add('alert-success')

        lengthMessageElement.firstElementChild.ariaLabel = 'Success:'
        lengthMessageElement.firstElementChild.innerHTML = '<use xlink:href="#check-circle-fill" />'

        if (pw.match(regexForLowerLetter)) {
            lowerLetterScore = 1;
            lowerCaseMessageElement.classList.remove('alert-danger')
            lowerCaseMessageElement.classList.add('alert-success')
            lowerCaseMessageElement.firstElementChild.ariaLabel = 'Success:'
            lowerCaseMessageElement.firstElementChild.innerHTML = '<use xlink:href="#check-circle-fill" />'
        } else {
            lowerLetterScore = 0;
            lowerCaseMessageElement.classList.remove('alert-success')
            lowerCaseMessageElement.classList.add('alert-danger')
            lowerCaseMessageElement.firstElementChild.ariaLabel = 'Danger:'
            lowerCaseMessageElement.firstElementChild.innerHTML = '<use xlink:href="#exclamation-triangle-fill" />'
        }

        if (pw.match(regexForUpperLetter)) {
            upperLetterScore = 1;
            upperCaseMessageElement.classList.remove('alert-danger')
            upperCaseMessageElement.classList.add('alert-success')
            upperCaseMessageElement.firstElementChild.ariaLabel = 'Success:'
            upperCaseMessageElement.firstElementChild.innerHTML = '<use xlink:href="#check-circle-fill" />'
        } else {
            upperLetterScore = 0;
            upperCaseMessageElement.classList.remove('alert-success')
            upperCaseMessageElement.classList.add('alert-danger')
            upperCaseMessageElement.firstElementChild.ariaLabel = 'Danger:'
            upperCaseMessageElement.firstElementChild.innerHTML = '<use xlink:href="#exclamation-triangle-fill" />'
        }

        if (pw.match(regexForNumber)) {
            numberScore = 1;
            numberMessageElement.classList.remove('alert-danger')
            numberMessageElement.classList.add('alert-success')
            numberMessageElement.firstElementChild.ariaLabel = 'Success:'
            numberMessageElement.firstElementChild.innerHTML = '<use xlink:href="#check-circle-fill" />'
        } else {
            numberScore = 0;
            numberMessageElement.classList.remove('alert-success')
            numberMessageElement.classList.add('alert-danger')
            numberMessageElement.firstElementChild.ariaLabel = 'Danger:'
            numberMessageElement.firstElementChild.innerHTML = '<use xlink:href="#exclamation-triangle-fill" />'
        }

        if (pw.match(regexForSpecialCharacter)) {
            specialCharacterScore = 1;
            specialCharacterMessageElement.classList.remove('alert-danger')
            specialCharacterMessageElement.classList.add('alert-success')
            specialCharacterMessageElement.firstElementChild.ariaLabel = 'Success:'
            specialCharacterMessageElement.firstElementChild.innerHTML = '<use xlink:href="#check-circle-fill" />'
        } else {
            specialCharacterScore = 0;
            specialCharacterMessageElement.classList.remove('alert-success')
            specialCharacterMessageElement.classList.add('alert-danger')
            specialCharacterMessageElement.firstElementChild.ariaLabel = 'Danger:'
            specialCharacterMessageElement.firstElementChild.innerHTML = '<use xlink:href="#exclamation-triangle-fill" />'
        }

        if (pw.length > 8) {
            goodLengthScore = 1;
         }  else {
            goodLengthScore = 0;
         } 

        let totalScore = lengthScore + lowerLetterScore + upperLetterScore + numberScore + specialCharacterScore + goodLengthScore;
        if (totalScore === 3) {
            poorPasswordElement.className = ""
            averagePasswordElement.className = ""
            goodPasswordElement.className = ""
            veryGoodPasswordElement.className = ""
            poorPasswordElement.classList.add('weak-password')
            averagePasswordElement.classList.add('password-strength-background')
            goodPasswordElement.classList.add('password-strength-background')
            veryGoodPasswordElement.classList.add('password-strength-background')
        } else if (totalScore === 4) {
            poorPasswordElement.className = ""
            averagePasswordElement.className = ""
            goodPasswordElement.className = ""
            veryGoodPasswordElement.className = ""
            poorPasswordElement.classList.add('average-password')
            averagePasswordElement.classList.add('average-password')
            goodPasswordElement.classList.add('password-strength-background')
            veryGoodPasswordElement.classList.add('password-strength-background')
        } else if (totalScore === 5) {
            poorPasswordElement.className = ""
            averagePasswordElement.className = ""
            goodPasswordElement.className = ""
            veryGoodPasswordElement.className = ""
            poorPasswordElement.classList.add('good-password')
            averagePasswordElement.classList.add('good-password')
            goodPasswordElement.classList.add('good-password')
            veryGoodPasswordElement.classList.add('password-strength-background')
        } else if (totalScore === 6) {
            poorPasswordElement.className = ""
            averagePasswordElement.className = ""
            goodPasswordElement.className = ""
            veryGoodPasswordElement.className = ""
            poorPasswordElement.classList.add('verygood-password')
            averagePasswordElement.classList.add('verygood-password')
            goodPasswordElement.classList.add('verygood-password')
            veryGoodPasswordElement.classList.add('verygood-password')
        }

    } else {
        lengthScore = 0;
        passwordStrengthMeterElement.classList.remove('show-password-meter')
        passwordStrengthMeterElement.classList.add('hide-password-meter');

        lengthMessageElement.classList.add('alert-danger')
        lengthMessageElement.classList.remove('alert-success')
        lengthMessageElement.firstElementChild.ariaLabel = 'Danger:'
        lengthMessageElement.firstElementChild.innerHTML = '<use xlink:href="#exclamation-triangle-fill" />'
    }

}
)

In the above JavaScript code, we are accessing the value of the password entered by the user using the ‘input’ event of the input element. We are checking checking this against several conditions which are mentioned above to determine the strength score.

We are displaying the strength indicator or the messages only when the password length is more than 5 characters.

We have used the alert messages from Bootstrap to display the appropriate alert message. Pay attention, how we are either adding or removing the appropriate class names and also setting the values for ariaLabel.

To the password strength meter, we are adding classes based on the total score to the corresponding element and adding the default class name to the remaining elements.

Please note that, we can achieve the same functionality in JavaScript in many other different ways also. Since we try to experiment with the code in different ways, we may not always follow the specific patterns, so, if your application follows any specific design pattern, you can adjust the code accordingly.