開発情報・ナレッジ

投稿者: 株式会社ゴンドラ 2025年8月27日 (水)

UXを向上させるリアルタイム・パスワード強度チェッカー

入力されたパスワード強度を視覚的に分かるUI

概要

ユーザーが入力するパスワードの強度をリアルタイムで視覚的にフィードバックするUIコンポーネントです。
強度メーターと条件チェックリストにより、ユーザーは安全なパスワードを直感的に作成できます。セキュリティ向上とUX改善に繋がり、会員登録フォームなどに最適です。
単一のHTMLファイルで完結しているため、既存のプロジェクトにも容易に組み込むことが可能です。
デモサイト

ソースコード

まずはソースコードをご覧ください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>オリジナルパスワード強度チェッカー</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">

    <style>
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap');
        :root {
            --color-weak: #e74c3c;
            --color-medium: #f39c12;
            --color-strong: #2ecc71;
            --color-bg: #f4f7f6;
            --color-card-bg: #ffffff;
            --color-text: #333;
            --color-gray: #bdc3c7;
            --border-radius: 8px;
        }
        body {
            font-family: 'Noto Sans JP', sans-serif;
            background-color: var(--color-bg);
            color: var(--color-text);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            padding: 20px;
            box-sizing: border-box;
        }
        .card {
            background-color: var(--color-card-bg);
            padding: 30px 40px;
            border-radius: var(--border-radius);
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 400px;
        }
        h1 {
            margin: 0 0 10px;
            text-align: center;
            font-size: 24px;
        }
        .subtitle {
            margin: 0 0 30px;
            text-align: center;
            color: #7f8c8d;
        }
        .form-group {
            margin-bottom: 20px;
        }
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 700;
        }
        .input-wrapper {
            position: relative;
        }
        .input-wrapper input {
            width: 100%;
            padding: 12px 40px 12px 15px;
            border: 1px solid var(--color-gray);
            border-radius: var(--border-radius);
            font-size: 16px;
            box-sizing: border-box;
            transition: border-color 0.3s;
        }
        .input-wrapper input:focus {
            outline: none;
            border-color: #3498db;
        }
        .toggle-password {
            position: absolute;
            top: 50%;
            right: 15px;
            transform: translateY(-50%);
            cursor: pointer;
            color: #95a5a6;
        }
        .strength-meter {
            height: 10px;
            background-color: #ecf0f1;
            border-radius: 10px;
            overflow: hidden;
            margin-top: -10px;
        }
        .strength-bar {
            height: 100%;
            width: 0;
            background-color: var(--color-weak);
            transition: width 0.3s, background-color 0.3s;
        }
        .strength-text {
            font-size: 14px;
            font-weight: 700;
            margin: 8px 0 15px;
            text-align: right;
            height: 1em;
        }
        .requirements-list {
            list-style: none;
            padding: 0;
            margin: 0 0 25px;
            font-size: 14px;
            color: #7f8c8d;
        }
        .requirements-list li {
            margin-bottom: 8px;
            transition: color 0.3s;
        }
        .requirements-list li::before {
            font-family: "Font Awesome 5 Free";
            font-weight: 900;
            content: "\f00d";
            margin-right: 10px;
            color: var(--color-weak);
            transition: content 0.3s, color 0.3s;
        }
        .requirements-list li.valid {
            color: var(--color-text);
        }
        .requirements-list li.valid::before {
            content: "\f00c";
            color: var(--color-strong);
        }
        .match-text {
            font-size: 14px;
            margin-top: 8px;
            height: 1em;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <main class="card">
        <h1>パスワードを新規設定</h1>
        <p class="subtitle">安全なパスワードを設定してください</p>

        <form action="#" method="post" id="passwordForm">
            <div class="form-group">
                <label for="password">パスワード</label>
                <div class="input-wrapper">
                    <input type="password" id="password" name="password" placeholder="パスワードを入力" required>
                    <i class="fa-solid fa-eye-slash toggle-password"></i>
                </div>
            </div>

            <div class="strength-meter">
                <div class="strength-bar"></div>
            </div>
            <p class="strength-text"></p>
            <ul class="requirements-list">
                <li data-requirement="length">8文字以上</li>
                <li data-requirement="lowercase">小文字の英字を1つ以上含む</li>
                <li data-requirement="uppercase">大文字の英字を1つ以上含む</li>
                <li data-requirement="number">数字を1つ以上含む</li>
                <li data-requirement="special">記号 (!, @, #, $, % など) を1つ以上含む</li>
            </ul>
            <div class="form-group">
                <label for="password-cf">パスワード(確認用)</label>
                <div class="input-wrapper">
                    <input type="password" id="password-cf" name="password:cf" placeholder="もう一度パスワードを入力" required>
                </div>
                <p class="match-text"></p>
            </div>
        </form>
    </main>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const passwordInput = document.getElementById('password');
            const passwordCfInput = document.getElementById('password-cf');
            const strengthBar = document.querySelector('.strength-bar');
            const strengthText = document.querySelector('.strength-text');
            const requirementsList = document.querySelector('.requirements-list');
            const matchText = document.querySelector('.match-text');
            const togglePassword = document.querySelector('.toggle-password');

            const requirements = [
                { regex: /.{8,}/, element: requirementsList.querySelector('[data-requirement="length"]') },
                { regex: /[a-z]/, element: requirementsList.querySelector('[data-requirement="lowercase"]') },
                { regex: /[A-Z]/, element: requirementsList.querySelector('[data-requirement="uppercase"]') },
                { regex: /[0-9]/, element: requirementsList.querySelector('[data-requirement="number"]') },
                { regex: /[^A-Za-z0-9]/, element: requirementsList.querySelector('[data-requirement="special"]') }
            ];
            passwordInput.addEventListener('input', () => {
                const password = passwordInput.value;

                // --- 1. UIチェックリストの更新(この部分はUI表示のためだけ) ---
                requirements.forEach(req => {
                    if (req.regex.test(password)) {
                        req.element.classList.add('valid');
                    } else {
                        req.element.classList.remove('valid');
                    }
                });

                // --- 2. 強度判定ロジック ---
                if (password.length === 0) {
                    strengthBar.style.width = '0%';
                    strengthText.textContent = '';
                    return; // 何も入力されていない場合はここで処理終了
                }

                // まず、長さが足りなければ無条件で「弱い」
                if (password.length < 8) {
                    strengthBar.style.width = '25%';
                    strengthBar.style.backgroundColor = 'var(--color-weak)';
                    strengthText.textContent = '弱い';
                    strengthText.style.color = 'var(--color-weak)';
                    return; 
                }

                // 長さが足りている場合、文字の種類の数で判定
                let varietyCount = 0;
                if (/[a-z]/.test(password)) varietyCount++;
                if (/[A-Z]/.test(password)) varietyCount++;
                if (/[0-9]/.test(password)) varietyCount++;
                if (/[^A-Za-z0-9]/.test(password)) varietyCount++;

                switch (varietyCount) {
                    case 1: // 長くても文字の種類が1つだけなら「弱い」
                        strengthBar.style.width = '25%';
                        strengthBar.style.backgroundColor = 'var(--color-weak)';
                        strengthText.textContent = '弱い';
                        strengthText.style.color = 'var(--color-weak)';
                        break;
                    case 2:
                        strengthBar.style.width = '50%';
                        strengthBar.style.backgroundColor = 'var(--color-medium)';
                        strengthText.textContent = '普通';
                        strengthText.style.color = 'var(--color-medium)';
                        break;
                    case 3:
                    case 4:
                        strengthBar.style.width = '100%';
                        strengthBar.style.backgroundColor = 'var(--color-strong)';
                        strengthText.textContent = '安全';
                        strengthText.style.color = 'var(--color-strong)';
                        break;
                }
                checkPasswordMatch();
            });

            passwordCfInput.addEventListener('input', checkPasswordMatch);

            function checkPasswordMatch() {
                if (passwordCfInput.value.length === 0) {
                    matchText.textContent = '';
                    return;
                }
                if (passwordInput.value === passwordCfInput.value) {
                    matchText.textContent = '◎ パスワードが一致しました';
                    matchText.style.color = 'var(--color-strong)';
                } else {
                    matchText.textContent = '× パスワードが一致しません';
                    matchText.style.color = 'var(--color-weak)';
                }
            }
            togglePassword.addEventListener('click', () => {
                const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
                passwordInput.setAttribute('type', type);
                togglePassword.classList.toggle('fa-eye');
                togglePassword.classList.toggle('fa-eye-slash');
            });
        });
    </script>
</body>
</html>
      

SPIRALのデフォルトのデザインから少し離れていますが、必要な部分を切り取ってご利用ください。

入力文字強度の詳細

弱い (Weak) と判定される例

パスワードが8文字未満の場合、または8文字以上でも使われている文字の種類が1種類のみの場合に「弱い」と判定されます。

短いパスワード
例:p@ss123 (7文字のため、文字の種類に関わらず「弱い」と判定されます)

1種類の文字だけで構成された長いパスワード
例:passwordlong (8文字以上ですが、使われている文字が小文字の1種類だけのため「弱い」と判定されます)
例:AAAAAAAAAA (8文字以上ですが、使われている文字が大文字の1種類だけのため「弱い」と判定されます)

普通 (Medium) と判定される例

パスワードが8文字以上で、かつ使われている文字の種類が2種類の場合に「普通」と判定されます。

例: password12345 (小文字 + 数字 の2種類)
例: Passwordtest (大文字 + 小文字 の2種類)
例: password-test (小文字 + 記号 の2種類)

安全 (Strong) と判定される例

パスワードが8文字以上で、かつ使われている文字の種類が3種類以上の場合に「安全」と判定されます。

3種類利用の例 (大文字 + 小文字 + 数字)
例:Password12345

4種類すべて利用の例 (最も安全)
例:Str0ng-Pa55!word

注意事項

このコンポーネントを利用する上で、特に重要な点は以下の通りです。
1. フロントエンドでの検証について
このツールが行っているのは、あくまでユーザーの利便性を高めるためのフロントエンド(ブラウザ側)でのチェックです。入力されたパスワードがそのままサーバーに送信されるのを防ぐものではありません。

2. 強度の判定基準のカスタマイズ
パスワードの強度を決めるルール(文字数や必須の文字種)は、JavaScript内の配列で定義されています。サイトのセキュリティポリシーに応じて、この基準は自由に変更・強化することが可能です。

// この部分の正規表現を変更することで、ルールをカスタマイズできます
const requirements = [
    { regex: /.{8,}/, element: requirementsList.querySelector('[data-requirement="length"]') },
    { regex: /[a-z]/, element: requirementsList.querySelector('[data-requirement="lowercase"]') },
    { regex: /[A-Z]/, element: requirementsList.querySelector('[data-requirement="uppercase"]') },
    { regex: /[0-9]/, element: requirementsList.querySelector('[data-requirement="number"]') },
    { regex: /[^A-Za-z0-9]/, element: requirementsList.querySelector('[data-requirement="special"]') }
];
      

3. 外部ライブラリへの依存
チェックリストやパスワード表示/非表示のアイコンは、外部のWebサービスである「Font Awesome」から読み込んでいます。
そのため、オフライン環境や、セキュリティポリシーで外部CDNへのアクセスが制限されている環境では、アイコンが表示されない場合があります。

おわりに

今回は、パスワードの強度を視覚的にチェックし、ユーザーの入力を補助するUIコンポーネントを紹介しました。
複雑なパスワード要件をユーザーに分かりやすく伝え、より安全なパスワードの設定を促すことで、サイト全体のセキュリティ向上とUX改善に直接的に貢献します。
提供したコードは単一ファイルで完結しているため、様々なプロジェクトへ容易に導入できるのが特徴です。
ここで紹介したコードは、あくまで一つのサンプルです。デザインや強度の判定ロジックを、ご自身のサービスの要件に合わせて自由にカスタマイズしてみてください。

【お知らせ】本実装の構築代行・カスタマイズを承ります

記事でご紹介した内容や、貴社の要件に合わせた柔軟なカスタマイズ開発を承っております。

SPIRALの専門チームが要件定義から実装まで一貫してサポートいたしますので、まずはお見積もりをご依頼ください。

カスタマイズの見積もりを依頼する
解決しない場合はこちら コンテンツに関しての
要望はこちら