04Common-value penalty — the strength value is docked when the input value matches an entry in a preset common-value list
When the input value matches one entry in a preset list of common values, a fixed amount is subtracted from the strength value.
Best forA signup form that discourages reused easy passwords, an account setting that filters out commonly leaked values
COMMON 배열 10종PENALTY = 40.cl--hit

<main class="stage">
<div class="cl is-demo" id="cl">
<label class="cl__field">
<input class="cl__input" id="clInput" type="text" autocomplete="off" spellcheck="false" value="">
</label>
<div class="cl__track"><div class="cl__fill" id="clFill"></div></div>
<div class="cl__row">
<span class="cl__score">Value <b id="clScore">0</b>/100</span>
<span class="cl__warn" id="clWarn">Common value <b id="clPenalty">-40</b></span>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 계산 겹: 길이 기반 기본 점수에서, 값이 가상 흔한 값 목록과 완전히 일치하면
// 고정 감점(-40)을 적용한다 — 목록 포함 여부가 곧 감점 여부를 가르는 분기다.
@include stage($stage-paper);
.cl {
width: 100%;
max-width: 320px;
display: grid;
gap: $sp-3;
}
.cl__field { display: block; }
.cl__input {
width: 100%;
box-sizing: border-box;
padding: $sp-3 $sp-4;
border-radius: $r-control;
border: 0;
background: $subject-blue-light;
color: $subject-blue-deep;
font: 500 16px/1.3 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: .04em;
-webkit-text-security: disc;
&:focus { outline: 2px solid $subject-blue-deep; outline-offset: 2px; }
}
.cl__track {
position: relative;
height: $sp-3;
border-radius: $r-sm;
background: rgba($subject-blue-deep, .14);
overflow: hidden;
}
.cl__fill {
position: absolute;
inset: 0;
border-radius: $r-sm;
background: $subject-blue;
transform: scaleX(0);
transform-origin: left center;
transition: transform $dur-base $ease-out, background-color $dur-base $ease-out;
}
.cl--hit .cl__fill { background: $error; }
.cl__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: $sp-2;
font: 600 13px/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: $subject-blue-deep;
}
.cl__score b { font-variant-numeric: tabular-nums; }
.cl__warn {
color: $error;
opacity: 0;
transform: scale(.7);
transform-origin: right center;
transition: opacity $dur-base $ease-out, transform $dur-base $ease-out;
}
@media (max-width: 340px) {
.cl { max-width: 260px; }
.cl__input { padding: $sp-2 $sp-3; font-size: 14px; }
.cl__row { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.cl__fill, .cl__warn { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cl {
width: 100%;
max-width: 320px;
display: grid;
gap: 12px;
}
.cl__field {
display: block;
}
.cl__input {
width: 100%;
box-sizing: border-box;
padding: 12px 16px;
border-radius: 12px;
border: 0;
background: #5b8cf8;
color: #0a4fe8;
font: 500 16px/1.3 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: 0.04em;
-webkit-text-security: disc;
}
.cl__input:focus {
outline: 2px solid #0a4fe8;
outline-offset: 2px;
}
.cl__track {
position: relative;
height: 12px;
border-radius: 8px;
background: rgba(10, 79, 232, 0.14);
overflow: hidden;
}
.cl__fill {
position: absolute;
inset: 0;
border-radius: 8px;
background: #2f6df6;
transform: scaleX(0);
transform-origin: left center;
transition: transform 300ms ease-out, background-color 300ms ease-out;
}
.cl--hit .cl__fill {
background: #c2361a;
}
.cl__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
font: 600 13px/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: #0a4fe8;
}
.cl__score b {
font-variant-numeric: tabular-nums;
}
.cl__warn {
color: #c2361a;
opacity: 0;
transform: scale(0.7);
transform-origin: right center;
transition: opacity 300ms ease-out, transform 300ms ease-out;
}
@media (max-width: 340px) {
.cl {
max-width: 260px;
}
.cl__input {
padding: 8px 12px;
font-size: 14px;
}
.cl__row {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.cl__fill, .cl__warn {
transition: none;
}
}(function () {
var root = document.getElementById('cl');
var input = document.getElementById('clInput');
var fill = document.getElementById('clFill');
var scoreEl = document.getElementById('clScore');
var warn = document.getElementById('clWarn');
// 계산 겹: 가상 Common value 소형 목록(일반 패턴형 10개, 외부 유출 목록 아님) — 실제 서비스명 0.
var COMMON = ['password', '123456', 'qwerty', '111111', 'abc123',
'letmein', 'iloveyou', 'admin', 'welcome', 'monkey'];
var PENALTY = 40;
function scoreOf(value) {
var base = Math.min(100, value.length * 8);
var hit = value.length > 0 && COMMON.indexOf(value.toLowerCase()) !== -1;
var score = hit ? Math.max(0, base - PENALTY) : base;
return { score: score, hit: hit };
}
function paint(value) {
var r = scoreOf(value);
fill.style.transform = 'scaleX(' + (r.score / 100) + ')';
root.classList.toggle('cl--hit', r.hit);
scoreEl.textContent = String(r.score);
warn.style.opacity = r.hit ? '1' : '0';
warn.style.transform = r.hit ? 'scale(1)' : 'scale(.7)';
}
input.addEventListener('input', function () {
root.classList.remove('is-demo');
stopAutoplay();
paint(input.value);
});
paint(input.value);
// 자동 루프: 시드 고정 문자열 배열 — 흔한 값과 일반 값을 번갈아 입력한다.
var SEQ = ['', 'x7Ba9K2', 'password', 'x7Ba9K2Q', '123456', 'GreenTiger42', 'qwerty', ''];
var si = 0;
var autoTimer = null;
function autoTick() { input.value = SEQ[si]; paint(SEQ[si]); si = (si + 1) % SEQ.length; }
function stopAutoplay() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } }
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduce) { autoTimer = setInterval(autoTick, 360); autoTick(); }
})();import { useEffect, useRef } from "react";
import styles from "./CommonListPenalty.module.scss";
type Box<T> = { current: T };
const PENALTY = 40;
export default function CommonListPenalty() {
const rootRef = useRef(null) as unknown as Box<HTMLDivElement | null>;
useEffect(() => {
const root = rootRef.current;
if (!root) return;
const input = root.querySelector(`.${styles.input}`) as HTMLInputElement | null;
const fill = root.querySelector(`.${styles.fill}`) as HTMLElement | null;
const scoreEl = root.querySelector(`.${styles.scoreNum}`) as HTMLElement | null;
const warn = root.querySelector(`.${styles.warn}`) as HTMLElement | null;
if (!input || !fill || !scoreEl || !warn) return;
// 계산 겹: 가상 흔한 값 소형 목록(일반 패턴형 10개, 외부 유출 목록 아님) — 실제 서비스명 0.
const COMMON = ["password", "123456", "qwerty", "111111", "abc123",
"letmein", "iloveyou", "admin", "welcome", "monkey"];
function scoreOf(value: string) {
const base = Math.min(100, value.length * 8);
const hit = value.length > 0 && COMMON.indexOf(value.toLowerCase()) !== -1;
const score = hit ? Math.max(0, base - PENALTY) : base;
return { score, hit };
}
function paint(value: string) {
const r = scoreOf(value);
fill!.style.transform = "scaleX(" + r.score / 100 + ")";
root!.classList.toggle(styles["cl--hit"], r.hit);
scoreEl!.textContent = String(r.score);
warn!.style.opacity = r.hit ? "1" : "0";
warn!.style.transform = r.hit ? "scale(1)" : "scale(.7)";
}
function onInput() {
root!.classList.remove(styles["is-demo"]);
stopAutoplay();
paint(input!.value);
}
input.addEventListener("input", onInput);
paint(input.value);
// 자동 루프: 시드 고정 문자열 배열 — 흔한 값과 일반 값을 번갈아 입력한다.
const SEQ = ["", "x7Ba9K2", "password", "x7Ba9K2Q", "123456", "GreenTiger42", "qwerty", ""];
let si = 0;
let autoTimer: number | null = null;
function autoTick() { input!.value = SEQ[si]; paint(SEQ[si]); si = (si + 1) % SEQ.length; }
function stopAutoplay() { if (autoTimer !== null) { window.clearInterval(autoTimer); autoTimer = null; } }
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduce) { autoTimer = window.setInterval(autoTick, 360); autoTick(); }
return () => {
stopAutoplay();
input.removeEventListener("input", onInput);
};
}, []);
return (
<div ref={rootRef} className={`${styles.stage} ${styles.cl} ${styles["is-demo"]}`}>
<label className={styles.field}>
<input className={styles.input} type="text" autoComplete="off" spellCheck={false} defaultValue="" />
</label>
<div className={styles.track}><div className={styles.fill} /></div>
<div className={styles.row}>
<span className={styles.score}>값 <b className={styles.scoreNum}>0</b>/100</span>
<span className={styles.warn}>흔한 값 <b>-{PENALTY}</b></span>
</div>
</div>
);
}04 Common-value penalty — the strength value is docked when the input value matches an entry in a preset common-value list — Measured area 3.392% · moving frames 21/23Original by GODRICH · SCSS and React ship in the zip · Password Strength Meter UI: 9 Calculation Patterns
The post this demo lives in
Password Strength Meter UI: 9 Calculation PatternsOne strength value is set by how many character types mix in, one drops when the input matches a common value, and one f See all demos in this post
If you like this effect - similar demos
Inputs08 Pattern-match value toggle — when the leading digit value matches a set pattern value, a shape value beside it smoothly changesCredit Card Input UI: 9 Patterns
Inputs02 Debounce shimmer — gathering input for 300ms while the list shimmers as a waiting stateAutocomplete Input: 9 Search Dropdowns
Inputs01 Check-draw toggle — when the checked value turns on, the checkmark's line-length value draws from zero to full in orderCSS Custom Checkbox: 9 Interaction Patterns