04Security question card
A pre-registered question fades onto the card, and the answer field's border cools from orange to plain white as you type.
Best forFallback verification when an OTP can't be delivered, secondary checks on admin accounts
@keyframes sq-panelbox-shadow 글로우aria-label

<main class="stage">
<div class="sq-card is-demo">
<div class="sq-card__panel">
<span class="sq-card__icon" aria-hidden="true">
<svg viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88"/></g></svg>
</span>
<p class="sq-card__question">What was your first pet's name?</p>
<input class="sq-card__input" type="text" placeholder="Your answer" aria-label="Security question answer">
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 답변 카드가 식었다 다시 데워지는 한 주기
$easing: $ease-out;
$color: $error; // 뜨거운 경고 축 (식기 전 색)
@include stage($stage-orange);
.sq-card { display: flex; align-items: center; justify-content: center; }
.sq-card__panel {
width: min(240px, 84vw);
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-3;
padding: $sp-5 $sp-4;
background: $stage-paper;
border-radius: $r-card;
box-shadow: $shadow-raised;
text-align: center;
}
.sq-card__icon { width: 28px; height: 28px; color: $color; }
.sq-card__question { margin: 0; font-size: 13px; font-weight: 600; color: $stage-ink; font-family: inherit; }
.sq-card__input {
width: 100%;
box-sizing: border-box;
padding: $sp-2 $sp-3;
border: 2px solid #d8d2c4;
border-radius: $r-sm;
background: $stage-paper;
font-size: 13px;
font-family: inherit;
color: $stage-ink;
outline: none;
}
// 실제 사용 — 입력창에 포커스하면 즉시 반응 (is-demo 없이 열었을 때)
.sq-card__input:focus-visible { border-color: $subject-blue; box-shadow: 0 0 0 3px rgba($subject-blue, .25); }
// 자동 재생 루프 — 미리 등록한 답을 다시 확인하는 동안 카드가 뜨거운 색에서 식는다
.sq-card.is-demo .sq-card__panel { animation: sq-panel $duration $easing infinite; }
.sq-card.is-demo .sq-card__input { animation: sq-input $duration $easing infinite; }
@keyframes sq-panel {
0%, 8% { background: mix($color, white, 24%); box-shadow: 0 0 0 3px rgba($color, .3), 0 0 34px 18px rgba($color, .3), $shadow-raised; }
23%, 77% { background: $stage-paper; box-shadow: $shadow-raised; }
92%, 100% { background: mix($color, white, 24%); box-shadow: 0 0 0 3px rgba($color, .3), 0 0 34px 18px rgba($color, .3), $shadow-raised; }
}
@keyframes sq-input {
0%, 8% { border-color: $color; box-shadow: 0 0 0 4px rgba($color, .22), 0 0 20px 10px rgba($color, .25); }
23%, 77% { border-color: #d8d2c4; box-shadow: none; }
92%, 100% { border-color: $color; box-shadow: 0 0 0 4px rgba($color, .22), 0 0 20px 10px rgba($color, .25); }
}
@media (prefers-reduced-motion: reduce) {
.sq-card.is-demo .sq-card__panel,
.sq-card.is-demo .sq-card__input { animation: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.sq-card {
display: flex;
align-items: center;
justify-content: center;
}
.sq-card__panel {
width: min(240px, 84vw);
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 20px 16px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
text-align: center;
}
.sq-card__icon {
width: 28px;
height: 28px;
color: #c2361a;
}
.sq-card__question {
margin: 0;
font-size: 13px;
font-weight: 600;
color: #17141a;
font-family: inherit;
}
.sq-card__input {
width: 100%;
box-sizing: border-box;
padding: 8px 12px;
border: 2px solid #d8d2c4;
border-radius: 8px;
background: #ffffff;
font-size: 13px;
font-family: inherit;
color: #17141a;
outline: none;
}
.sq-card__input:focus-visible {
border-color: #2f6df6;
box-shadow: 0 0 0 3px rgba(47, 109, 246, 0.25);
}
.sq-card.is-demo .sq-card__panel {
animation: sq-panel 2s ease-out infinite;
}
.sq-card.is-demo .sq-card__input {
animation: sq-input 2s ease-out infinite;
}
@keyframes sq-panel {
0%, 8% {
background: rgb(94.2588235294%, 81.0823529412%, 78.4470588235%);
box-shadow: 0 0 0 3px rgba(194, 54, 26, 0.3), 0 0 34px 18px rgba(194, 54, 26, 0.3), 0 14px 24px rgba(23, 20, 26, 0.32);
}
23%, 77% {
background: #ffffff;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
92%, 100% {
background: rgb(94.2588235294%, 81.0823529412%, 78.4470588235%);
box-shadow: 0 0 0 3px rgba(194, 54, 26, 0.3), 0 0 34px 18px rgba(194, 54, 26, 0.3), 0 14px 24px rgba(23, 20, 26, 0.32);
}
}
@keyframes sq-input {
0%, 8% {
border-color: #c2361a;
box-shadow: 0 0 0 4px rgba(194, 54, 26, 0.22), 0 0 20px 10px rgba(194, 54, 26, 0.25);
}
23%, 77% {
border-color: #d8d2c4;
box-shadow: none;
}
92%, 100% {
border-color: #c2361a;
box-shadow: 0 0 0 4px rgba(194, 54, 26, 0.22), 0 0 20px 10px rgba(194, 54, 26, 0.25);
}
}
@media (prefers-reduced-motion: reduce) {
.sq-card.is-demo .sq-card__panel,
.sq-card.is-demo .sq-card__input {
animation: none;
}
}import { useState } from "react";
import styles from "./SecurityQuestionCard.module.scss";
export type SecurityQuestionCardProps = {
question: string;
onAnswer?: (value: string) => void;
};
/** OTP 를 못 받을 때 쓰는 대체 인증 — 등록해둔 질문에 답을 입력한다 */
export default function SecurityQuestionCard({ question, onAnswer }: SecurityQuestionCardProps) {
const [value, setValue] = useState("");
return (
<div className={styles.panel}>
<span className={styles.icon} aria-hidden="true">
<svg viewBox="0 0 256 256">
<g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2" /><path d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88" /></g>
</svg>
</span>
<p className={styles.question}>{question}</p>
<input
className={styles.input}
type="text"
placeholder="Your answer"
aria-label="Security question answer"
value={value}
onChange={(e: any) => {
setValue(e.target.value);
onAnswer?.(e.target.value);
}}
/>
</div>
);
}04 Security question card — Measured area 26.226% · moving frames 7/23Original by GODRICH · SCSS and React ship in the zip · 9 ID Verification Screen UI Patterns, Paste-Ready
The post this demo lives in
9 ID Verification Screen UI Patterns, Paste-ReadyAn id verification screen ui is a screen — a permission prompt, an OTP grid, a security question card — that proves a pe See all demos in this post

