09ログアウト確認カード
ログアウトボタンを押すとカードが小さく揺れてから確認カードに切り替わり、最終の退出ボタンにカーソルを乗せると背景が濃い赤に変わります。
使いどころ誤って押したログアウトをもう一度確認するカード、全端末ログアウトを警告するアカウント設定画面
@keyframes shake커스텀 프로퍼티 --dangerfocus-visible 링

<main class="stage">
<div class="signout-card is-demo">
<span class="signout-card__icon" aria-hidden="true">
<svg viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 56v144a16 16 0 0 1-16 16H48V40h160a16 16 0 0 1 16 16" opacity=".2"/><path d="M120 216a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V40a8 8 0 0 1 8-8h64a8 8 0 0 1 0 16H56v160h56a8 8 0 0 1 8 8m109.66-93.66l-40-40a8 8 0 0 0-11.32 11.32L204.69 120H112a8 8 0 0 0 0 16h92.69l-26.35 26.34a8 8 0 0 0 11.32 11.32l40-40a8 8 0 0 0 0-11.32"/></g></svg>
</span>
<h2 class="signout-card__title">Sign out of this device?</h2>
<p class="signout-card__desc">You'll need to sign in again to continue.</p>
<div class="signout-card__actions">
<button class="signout-card__btn signout-card__btn--cancel" type="button">Cancel</button>
<button class="signout-card__btn signout-card__btn--danger" type="button">Sign out</button>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 흔들림 → 확인 상태 전환 한 바퀴
$easing: $ease-spring;
$color: $error; // 위험 강조 색
@include stage($stage-ink);
$card-bg: lighten($stage-ink, 9%);
$card-border: rgba($stage-paper, .14);
.signout-card {
width: min(230px, 86vw);
padding: $sp-4;
border-radius: $r-card;
background: $card-bg;
border: 1.5px solid $card-border;
text-align: center;
box-shadow: $shadow-raised;
}
.signout-card__icon { display: inline-flex; width: 26px; height: 26px; margin-bottom: $sp-2; color: $color; }
.signout-card__title { margin: 0 0 $sp-1; font-size: 14px; font-weight: 700; color: $stage-paper; font-family: inherit; }
.signout-card__desc { margin: 0 0 $sp-4; font-size: 11px; line-height: 1.5; color: rgba($stage-paper, .6); font-family: inherit; }
.signout-card__actions { display: flex; gap: $sp-2; justify-content: center; }
.signout-card__btn {
padding: $sp-2 $sp-4;
border-radius: $r-control;
font-size: 12px;
font-weight: 700;
font-family: inherit;
cursor: pointer;
border: 1.5px solid transparent;
}
.signout-card__btn--cancel {
background: transparent;
border-color: rgba($stage-paper, .3);
color: $stage-paper;
}
.signout-card__btn--cancel:hover, .signout-card__btn--cancel:focus-visible { background: rgba($stage-paper, .08); }
.signout-card__btn--danger { background: $color; color: $stage-paper; }
.signout-card__btn--danger:hover, .signout-card__btn--danger:focus-visible { background: darken($color, 12%); }
// 실제 사용 — 확인을 누르면 카드가 위험 상태로 고정된다 (is-demo 없이 열었을 때)
.signout-card.is-confirmed { background: mix($color, $card-bg, 30%); border-color: $color; }
// 자동 재생 — 흔들림(초반) → 카드 전체 배경이 위험 색으로 물들었다 되돌아온다
.signout-card.is-demo { animation: signout-shake $duration $easing infinite, signout-tint $duration $easing infinite; }
@keyframes signout-shake {
0%, 8%, 20%, 100% { transform: translateX(0); }
10% { transform: translateX(-6px); }
12% { transform: translateX(6px); }
15% { transform: translateX(-4px); }
18% { transform: translateX(3px); }
}
@keyframes signout-tint {
0%, 18% { background: $card-bg; border-color: $card-border; }
28%, 74% { background: mix($color, $card-bg, 30%); border-color: $color; }
86%, 100% { background: $card-bg; border-color: $card-border; }
}
@media (prefers-reduced-motion: reduce) {
.signout-card.is-demo { animation: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.signout-card {
width: min(230px, 86vw);
padding: 16px;
border-radius: 16px;
background: rgb(18.0196078431%, 15.6692242114%, 20.3699914749%);
border: 1.5px solid rgba(255, 255, 255, 0.14);
text-align: center;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.signout-card__icon {
display: inline-flex;
width: 26px;
height: 26px;
margin-bottom: 8px;
color: #c2361a;
}
.signout-card__title {
margin: 0 0 4px;
font-size: 14px;
font-weight: 700;
color: #ffffff;
font-family: inherit;
}
.signout-card__desc {
margin: 0 0 16px;
font-size: 11px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.6);
font-family: inherit;
}
.signout-card__actions {
display: flex;
gap: 8px;
justify-content: center;
}
.signout-card__btn {
padding: 8px 16px;
border-radius: 12px;
font-size: 12px;
font-weight: 700;
font-family: inherit;
cursor: pointer;
border: 1.5px solid transparent;
}
.signout-card__btn--cancel {
background: transparent;
border-color: rgba(255, 255, 255, 0.3);
color: #ffffff;
}
.signout-card__btn--cancel:hover, .signout-card__btn--cancel:focus-visible {
background: rgba(255, 255, 255, 0.08);
}
.signout-card__btn--danger {
background: #c2361a;
color: #ffffff;
}
.signout-card__btn--danger:hover, .signout-card__btn--danger:focus-visible {
background: rgb(54.9147950089%, 15.2855614973%, 7.359714795%);
}
.signout-card.is-confirmed {
background: rgb(35.437254902%, 17.3213981245%, 17.3178175618%);
border-color: #c2361a;
}
.signout-card.is-demo {
animation: signout-shake 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite, signout-tint 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes signout-shake {
0%, 8%, 20%, 100% {
transform: translateX(0);
}
10% {
transform: translateX(-6px);
}
12% {
transform: translateX(6px);
}
15% {
transform: translateX(-4px);
}
18% {
transform: translateX(3px);
}
}
@keyframes signout-tint {
0%, 18% {
background: rgb(18.0196078431%, 15.6692242114%, 20.3699914749%);
border-color: rgba(255, 255, 255, 0.14);
}
28%, 74% {
background: rgb(35.437254902%, 17.3213981245%, 17.3178175618%);
border-color: #c2361a;
}
86%, 100% {
background: rgb(18.0196078431%, 15.6692242114%, 20.3699914749%);
border-color: rgba(255, 255, 255, 0.14);
}
}
@media (prefers-reduced-motion: reduce) {
.signout-card.is-demo {
animation: none;
}
}(function(){
var card = document.querySelector('.signout-card'),
cancel = card.querySelector('.signout-card__btn--cancel'),
danger = card.querySelector('.signout-card__btn--danger');
cancel.addEventListener('click', function(){ card.classList.remove('is-confirmed'); });
danger.addEventListener('click', function(){ card.classList.add('is-confirmed'); });
}());import { useState } from "react";
import styles from "./SignOutConfirmCard.module.scss";
export type SignOutConfirmCardProps = {
onCancel?: () => void;
onConfirm?: () => void;
};
/** 사인아웃 전 마지막 확인 카드 — Sign out 을 누르면 위험 상태로 고정되고, Cancel 로 되돌린다 */
export default function SignOutConfirmCard({ onCancel, onConfirm }: SignOutConfirmCardProps) {
const [confirmed, setConfirmed] = useState(false);
const cancel = () => { setConfirmed(false); onCancel?.(); };
const confirm = () => { setConfirmed(true); onConfirm?.(); };
return (
<div className={confirmed ? `${styles.card} ${styles.confirmed}` : styles.card}>
<span className={styles.icon} aria-hidden="true">
<svg viewBox="0 0 256 256">
<g fill="currentColor"><path d="M224 56v144a16 16 0 0 1-16 16H48V40h160a16 16 0 0 1 16 16" opacity=".2" /><path d="M120 216a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V40a8 8 0 0 1 8-8h64a8 8 0 0 1 0 16H56v160h56a8 8 0 0 1 8 8m109.66-93.66l-40-40a8 8 0 0 0-11.32 11.32L204.69 120H112a8 8 0 0 0 0 16h92.69l-26.35 26.34a8 8 0 0 0 11.32 11.32l40-40a8 8 0 0 0 0-11.32" /></g>
</svg>
</span>
<h2 className={styles.title}>Sign out of this device?</h2>
<p className={styles.desc}>You'll need to sign in again to continue.</p>
<div className={styles.actions}>
<button className={styles.cancel} type="button" onClick={cancel}>Cancel</button>
<button className={styles.danger} type="button" onClick={confirm}>Sign out</button>
</div>
</div>
);
}09 ログアウト確認カード — 計測 累積面積 28.216% · 動いたフレーム 7/23GODRICH オリジナル · SCSS と React は zip に同梱 · 本人確認 画面UI 9選 — そのまま使える認証カード
このデモが属する記事
本人確認 画面UI 9選 — そのまま使える認証カード本人確認 画面とは、カメラへのアクセス許可や認証コードの入力、本人確認直後のプロフィール設定まで、ユーザーが本人であることを証明する一連の流れで使われる一時的な画面のことです。ここでは権限リクエストから認証コード入力、ログアウト確認まで、そ この記事のデモをすべて見る


