Skip to content

usePasswordStrength

Compute a coarse-grained password strength based on length and character class diversity. Returns a ComputedRef<Result | null> that emits null while the password is empty.

The result reports the password length, the matched rules (lowercase, uppercase, number, symbol) and a strength bucket (too_weak, weak, medium, strong).

Demo

Strength:

  • lowercase
  • uppercase
  • number
  • symbol

<script setup lang="ts">
    import { computed, ref } from 'vue';
    import { usePasswordStrength } from '@basmilius/common';

    const password = ref('');
    const result = usePasswordStrength(password);

    const allRules = ['lowercase', 'uppercase', 'number', 'symbol'] as const;

    const labels = {
        too_weak: 'Too weak',
        weak: 'Weak',
        medium: 'Medium',
        strong: 'Strong'
    } as const;

    const label = computed(() => result.value ? labels[result.value.strength] : '—');
</script>

<template>
    <div class="demo">
        <input v-model="password" class="input" type="text" placeholder="Type a password…"/>

        <div class="bar" :data-strength="result?.strength ?? 'none'">
            <span/>
            <span/>
            <span/>
            <span/>
        </div>

        <p class="label">Strength: <b>{{ label }}</b></p>

        <ul class="rules">
            <li v-for="rule in allRules" :key="rule" :class="{met: result?.rules.includes(rule)}">
                {{ rule }}
            </li>
        </ul>
    </div>
</template>

<style scoped>
    .demo {
        padding: 24px;
        border: 1px solid var(--vp-c-border);
        border-radius: 12px;
        background: var(--vp-c-bg-soft);
    }

    .input {
        width: 100%;
        margin-bottom: 16px;
        padding: 8px 12px;
        border: 1px solid var(--vp-c-border);
        border-radius: 8px;
        background: var(--vp-c-bg);
        font-size: 14px;
        color: var(--vp-c-text-1);
    }

    .bar {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 6px;
        margin-bottom: 12px;
    }

    .bar span {
        height: 6px;
        border-radius: 999px;
        background: var(--vp-c-bg);
        box-shadow: 0 0 0 1px var(--vp-c-border) inset;
    }

    .bar[data-strength='too_weak'] span:nth-child(-n + 1),
    .bar[data-strength='weak'] span:nth-child(-n + 2),
    .bar[data-strength='medium'] span:nth-child(-n + 3),
    .bar[data-strength='strong'] span:nth-child(-n + 4) {
        background: var(--vp-c-brand-1);
        box-shadow: none;
    }

    .label {
        margin: 0 0 12px;
        font-size: 14px;
        color: var(--vp-c-text-2);
    }

    .rules {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        margin: 0;
        padding: 0;
        list-style: none;
    }

    .rules li {
        padding: 4px 12px;
        border-radius: 999px;
        background: var(--vp-c-bg);
        box-shadow: 0 0 0 1px var(--vp-c-border) inset;
        font-family: var(--vp-font-family-mono);
        font-size: 12px;
        color: var(--vp-c-text-3);
    }

    .rules li.met {
        color: var(--vp-c-brand-1);
        box-shadow: 0 0 0 1px var(--vp-c-brand-1) inset;
    }
</style>

Importing

ts
import { usePasswordStrength } from '@basmilius/common';

Usage

vue
<script setup lang="ts">
    import { ref } from 'vue';
    import { usePasswordStrength } from '@basmilius/common';

    const password = ref('');
    const strength = usePasswordStrength(password);
</script>

<template>
    <input v-model="password" type="password"/>
    <p v-if="strength">
        Strength: {{ strength.strength }} ({{ strength.length }} chars,
        {{ strength.rules.length }} rule(s) matched)
    </p>
</template>

Strength buckets

BucketMinimum lengthMinimum diversity
too_weak00
weak62
medium83
strong104

Diversity counts how many of the four rules (lowercase, uppercase, number, symbol) the password matches. The resulting bucket is the highest one whose length and diversity thresholds are both satisfied.

Result shape

ts
type PasswordRuleType =
    | 'lowercase'
    | 'uppercase'
    | 'number'
    | 'symbol';

type PasswordStrengthType =
    | 'too_weak'
    | 'weak'
    | 'medium'
    | 'strong';

type Result = {
    readonly length: number;
    readonly rules: PasswordRuleType[];
    readonly strength: PasswordStrengthType;
};

Type signature

ts
declare function usePasswordStrength(
    passwordRef: Ref<string>
): ComputedRef<Result | null>;