04Password dot morph
Text starts hidden behind password-style dots, then each word gently scales up as the dots fade out and the real letters appear underneath.
Best forSignup-complete screens, sensitive-info toggle UI demos
position:absolute stackopacity+scale@keyframes pd-show

<main class="stage">
<span class="pd-wrap is-demo" aria-label="SECRET">
<span class="pd-dots">••••••</span>
<span class="pd-real">SECRET</span>
</span>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop;
$easing: $ease-spring;
$color: $subject-blue;
@include stage($stage-paper);
.pd-wrap {
position: relative;
display: inline-block;
font: 800 clamp(24px, 8vw, 38px)/1.2 "Space Mono", "Courier New", monospace;
color: $color;
}
.pd-dots { display: block; letter-spacing: .12em; }
.pd-real {
position: absolute;
inset: 0;
opacity: 0;
transform: scale(.9);
letter-spacing: .04em;
}
// 데모 루프: 점으로 가려진 글자가 살짝 커지며 크로스페이드로 실제 글자로 바뀐다
@keyframes pd-hide {
0%, 18% { opacity: 1; transform: scale(1); }
48%, 100% { opacity: 0; transform: scale(1.08); }
}
@keyframes pd-show {
0%, 18% { opacity: 0; transform: scale(.9); }
48%, 78% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(.9); }
}
.pd-wrap.is-demo .pd-dots { animation: pd-hide $duration $easing infinite; }
.pd-wrap.is-demo .pd-real { animation: pd-show $duration $easing infinite; }
@media (prefers-reduced-motion: reduce) {
.pd-dots, .pd-wrap.is-demo .pd-dots { animation: none !important; opacity: 0 !important; }
.pd-real, .pd-wrap.is-demo .pd-real { animation: none !important; opacity: 1 !important; transform: none !important; }
}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);
}
.pd-wrap {
position: relative;
display: inline-block;
font: 800 clamp(24px, 8vw, 38px)/1.2 "Space Mono", "Courier New", monospace;
color: #2f6df6;
}
.pd-dots {
display: block;
letter-spacing: 0.12em;
}
.pd-real {
position: absolute;
inset: 0;
opacity: 0;
transform: scale(0.9);
letter-spacing: 0.04em;
}
@keyframes pd-hide {
0%, 18% {
opacity: 1;
transform: scale(1);
}
48%, 100% {
opacity: 0;
transform: scale(1.08);
}
}
@keyframes pd-show {
0%, 18% {
opacity: 0;
transform: scale(0.9);
}
48%, 78% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.9);
}
}
.pd-wrap.is-demo .pd-dots {
animation: pd-hide 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.pd-wrap.is-demo .pd-real {
animation: pd-show 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@media (prefers-reduced-motion: reduce) {
.pd-dots, .pd-wrap.is-demo .pd-dots {
animation: none !important;
opacity: 0 !important;
}
.pd-real, .pd-wrap.is-demo .pd-real {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}import { CSSProperties, useEffect, useRef, useState } from "react";
import styles from "./PasswordDotMorph.module.scss";
export interface PasswordDotMorphProps {
text?: string;
duration?: string;
easing?: string;
}
// 실사용: 뷰포트에 들어오면 비밀번호 점이 살짝 커지며 크로스페이드로 실제 글자로 바뀐다.
export default function PasswordDotMorph({
text = "SECRET",
duration = "420ms",
easing = "cubic-bezier(.2,.8,.2,1)",
}: PasswordDotMorphProps) {
const ref = useRef<HTMLSpanElement>(null);
const [revealed, setRevealed] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => { if (e.isIntersecting) { setRevealed(true); io.unobserve(e.target); } });
}, { threshold: 0.3 });
io.observe(el);
return () => io.disconnect();
}, []);
const style = { "--duration": duration, "--easing": easing } as CSSProperties;
return (
<span className={styles.wrap} style={style} ref={ref}>
<span className={`${styles.dots} ${revealed ? styles.hide : ""}`}>{"•".repeat(text.length)}</span>
<span className={`${styles.real} ${revealed ? styles.show : ""}`}>{text}</span>
</span>
);
}04 Password dot morph — Measured area 1.936% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · 9 CSS Text Reveal Effects — Hex, Cipher, Radar
The post this demo lives in
9 CSS Text Reveal Effects — Hex, Cipher, RadarA css text reveal effect is a short animation for text appearing on screen, ready to paste in with no coding needed. All See all demos in this post


