07連携アカウント一覧
GoogleやGitHubなどのログイン手段が行ごとに並び、接続スイッチを押すとトグルが滑るように切り替わり、行の背景がうっすら色づきます。
使いどころ設定画面でのソーシャルログイン連携管理、複数アカウントをまとめる統合ログインダッシュボード
checkbox 기반 토글transform: translateXfocus-visible 링

<main class="stage">
<div class="accounts-card">
<h2 class="accounts-card__title">Linked accounts</h2>
<div class="accounts is-demo">
<label class="accounts__row">
<span class="accounts__icon" aria-hidden="true">
<svg viewBox="0 0 256 256"><g fill="currentColor"><path d="m218.34 119.6l-34.74 34.74a46.58 46.58 0 0 1-44.31 12.26c-.31.34-.62.67-.95 1l-34.74 34.74a46.63 46.63 0 1 1-65.94-65.94l34.74-34.74a46.6 46.6 0 0 1 44.31-12.26c.31-.34.62-.67 1-1l34.69-34.74a46.63 46.63 0 0 1 65.94 65.94" opacity=".2"/><path d="M240 88.23a54.43 54.43 0 0 1-16 37L189.25 160a54.27 54.27 0 0 1-38.63 16h-.05A54.63 54.63 0 0 1 96 119.84a8 8 0 0 1 16 .45A38.62 38.62 0 0 0 150.58 160a38.4 38.4 0 0 0 27.31-11.31l34.75-34.75a38.63 38.63 0 0 0-54.63-54.63l-11 11A8 8 0 0 1 135.7 59l11-11a54.65 54.65 0 0 1 77.3 0a54.86 54.86 0 0 1 16 40.23m-131 97.43l-11 11A38.4 38.4 0 0 1 70.6 208a38.63 38.63 0 0 1-27.29-65.94L78 107.31a38.63 38.63 0 0 1 66 28.4a8 8 0 0 0 7.78 8.22h.22a8 8 0 0 0 8-7.78A54.86 54.86 0 0 0 144 96a54.65 54.65 0 0 0-77.27 0L32 130.75A54.62 54.62 0 0 0 70.56 224a54.28 54.28 0 0 0 38.64-16l11-11a8 8 0 0 0-11.2-11.34"/></g></svg>
</span>
<span class="accounts__label">Google</span>
<span class="accounts__switch">
<input class="accounts__input" type="checkbox">
<span class="accounts__track"><span class="accounts__knob"></span></span>
</span>
</label>
<label class="accounts__row">
<span class="accounts__icon" aria-hidden="true">
<svg viewBox="0 0 256 256"><g fill="currentColor"><path d="m218.34 119.6l-34.74 34.74a46.58 46.58 0 0 1-44.31 12.26c-.31.34-.62.67-.95 1l-34.74 34.74a46.63 46.63 0 1 1-65.94-65.94l34.74-34.74a46.6 46.6 0 0 1 44.31-12.26c.31-.34.62-.67 1-1l34.69-34.74a46.63 46.63 0 0 1 65.94 65.94" opacity=".2"/><path d="M240 88.23a54.43 54.43 0 0 1-16 37L189.25 160a54.27 54.27 0 0 1-38.63 16h-.05A54.63 54.63 0 0 1 96 119.84a8 8 0 0 1 16 .45A38.62 38.62 0 0 0 150.58 160a38.4 38.4 0 0 0 27.31-11.31l34.75-34.75a38.63 38.63 0 0 0-54.63-54.63l-11 11A8 8 0 0 1 135.7 59l11-11a54.65 54.65 0 0 1 77.3 0a54.86 54.86 0 0 1 16 40.23m-131 97.43l-11 11A38.4 38.4 0 0 1 70.6 208a38.63 38.63 0 0 1-27.29-65.94L78 107.31a38.63 38.63 0 0 1 66 28.4a8 8 0 0 0 7.78 8.22h.22a8 8 0 0 0 8-7.78A54.86 54.86 0 0 0 144 96a54.65 54.65 0 0 0-77.27 0L32 130.75A54.62 54.62 0 0 0 70.56 224a54.28 54.28 0 0 0 38.64-16l11-11a8 8 0 0 0-11.2-11.34"/></g></svg>
</span>
<span class="accounts__label">GitHub</span>
<span class="accounts__switch">
<input class="accounts__input" type="checkbox">
<span class="accounts__track"><span class="accounts__knob"></span></span>
</span>
</label>
<label class="accounts__row">
<span class="accounts__icon" aria-hidden="true">
<svg viewBox="0 0 256 256"><g fill="currentColor"><path d="m218.34 119.6l-34.74 34.74a46.58 46.58 0 0 1-44.31 12.26c-.31.34-.62.67-.95 1l-34.74 34.74a46.63 46.63 0 1 1-65.94-65.94l34.74-34.74a46.6 46.6 0 0 1 44.31-12.26c.31-.34.62-.67 1-1l34.69-34.74a46.63 46.63 0 0 1 65.94 65.94" opacity=".2"/><path d="M240 88.23a54.43 54.43 0 0 1-16 37L189.25 160a54.27 54.27 0 0 1-38.63 16h-.05A54.63 54.63 0 0 1 96 119.84a8 8 0 0 1 16 .45A38.62 38.62 0 0 0 150.58 160a38.4 38.4 0 0 0 27.31-11.31l34.75-34.75a38.63 38.63 0 0 0-54.63-54.63l-11 11A8 8 0 0 1 135.7 59l11-11a54.65 54.65 0 0 1 77.3 0a54.86 54.86 0 0 1 16 40.23m-131 97.43l-11 11A38.4 38.4 0 0 1 70.6 208a38.63 38.63 0 0 1-27.29-65.94L78 107.31a38.63 38.63 0 0 1 66 28.4a8 8 0 0 0 7.78 8.22h.22a8 8 0 0 0 8-7.78A54.86 54.86 0 0 0 144 96a54.65 54.65 0 0 0-77.27 0L32 130.75A54.62 54.62 0 0 0 70.56 224a54.28 54.28 0 0 0 38.64-16l11-11a8 8 0 0 0-11.2-11.34"/></g></svg>
</span>
<span class="accounts__label">Apple</span>
<span class="accounts__switch">
<input class="accounts__input" type="checkbox">
<span class="accounts__track"><span class="accounts__knob"></span></span>
</span>
</label>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 스위치가 순서대로 켜지는 한 바퀴
$easing: $ease-out;
$color: $subject-sky; // 연결 상태 강조 색 (밝고 상큼한 축)
@include stage($stage-paper);
.accounts-card {
width: min(240px, 86vw);
padding: $sp-3;
border-radius: $r-card;
background: $stage-paper;
border: 1.5px solid #e6e2d8;
box-shadow: $shadow-press;
}
.accounts-card__title {
margin: 0 0 $sp-3;
font-size: 13px;
font-weight: 700;
color: $stage-ink;
font-family: inherit;
}
.accounts {
display: flex;
flex-direction: column;
gap: $sp-2;
}
.accounts__row {
display: flex;
align-items: center;
gap: $sp-2;
padding: $sp-1 $sp-2;
border-radius: $r-sm;
cursor: pointer;
background: transparent;
}
.accounts__icon { width: 16px; height: 16px; flex: 0 0 auto; color: #9a927e; }
.accounts__label { flex: 1 1 auto; font-size: 12px; font-weight: 600; color: $stage-ink; }
.accounts__switch { position: relative; flex: 0 0 auto; width: 32px; height: 18px; }
.accounts__input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.accounts__track {
position: absolute;
inset: 0;
border-radius: $r-pill;
background: #d8d2c4;
}
.accounts__knob {
position: absolute;
top: 2px; left: 2px;
width: 14px; height: 14px;
border-radius: 50%;
background: $stage-paper;
box-shadow: $shadow-press;
}
// 실제 사용 — 클릭해서 켜면 즉시 반영 (is-demo 없이 열었을 때)
.accounts__input:checked ~ .accounts__track { background: $color; }
.accounts__input:checked ~ .accounts__track .accounts__knob { transform: translateX(14px); }
.accounts__input:focus-visible ~ .accounts__track { outline: 2px solid $color; outline-offset: 2px; }
.accounts__row:has(.accounts__input:checked) { background: rgba($subject-mint, .18); }
// 자동 재생 — 행마다 순서대로 연결이 켜졌다 꺼진다 (같은 keyframe, delay 로 어긋나게)
.accounts.is-demo .accounts__row { animation: acct-row-cycle $duration $easing infinite; }
.accounts.is-demo .accounts__track { animation: acct-track-cycle $duration $easing infinite; }
.accounts.is-demo .accounts__knob { animation: acct-knob-cycle $duration $easing infinite; }
.accounts.is-demo .accounts__row:nth-child(1),
.accounts.is-demo .accounts__row:nth-child(1) .accounts__track,
.accounts.is-demo .accounts__row:nth-child(1) .accounts__knob { animation-delay: 0ms; }
.accounts.is-demo .accounts__row:nth-child(2),
.accounts.is-demo .accounts__row:nth-child(2) .accounts__track,
.accounts.is-demo .accounts__row:nth-child(2) .accounts__knob { animation-delay: 500ms; }
.accounts.is-demo .accounts__row:nth-child(3),
.accounts.is-demo .accounts__row:nth-child(3) .accounts__track,
.accounts.is-demo .accounts__row:nth-child(3) .accounts__knob { animation-delay: 1000ms; }
@keyframes acct-row-cycle {
0%, 8% { background: transparent; }
14%, 36% { background: rgba($subject-mint, .22); }
42%, 100% { background: transparent; }
}
@keyframes acct-track-cycle {
0%, 8% { background: #d8d2c4; }
14%, 36% { background: $color; }
42%, 100% { background: #d8d2c4; }
}
@keyframes acct-knob-cycle {
0%, 8% { transform: translateX(0); }
14%, 36% { transform: translateX(14px); }
42%, 100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
.accounts.is-demo .accounts__row,
.accounts.is-demo .accounts__track,
.accounts.is-demo .accounts__knob { animation: 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);
}
.accounts-card {
width: min(240px, 86vw);
padding: 12px;
border-radius: 16px;
background: #ffffff;
border: 1.5px solid #e6e2d8;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.accounts-card__title {
margin: 0 0 12px;
font-size: 13px;
font-weight: 700;
color: #17141a;
font-family: inherit;
}
.accounts {
display: flex;
flex-direction: column;
gap: 8px;
}
.accounts__row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 8px;
cursor: pointer;
background: transparent;
}
.accounts__icon {
width: 16px;
height: 16px;
flex: 0 0 auto;
color: #9a927e;
}
.accounts__label {
flex: 1 1 auto;
font-size: 12px;
font-weight: 600;
color: #17141a;
}
.accounts__switch {
position: relative;
flex: 0 0 auto;
width: 32px;
height: 18px;
}
.accounts__input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
}
.accounts__track {
position: absolute;
inset: 0;
border-radius: 999px;
background: #d8d2c4;
}
.accounts__knob {
position: absolute;
top: 2px;
left: 2px;
width: 14px;
height: 14px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.accounts__input:checked ~ .accounts__track {
background: #6ec8ff;
}
.accounts__input:checked ~ .accounts__track .accounts__knob {
transform: translateX(14px);
}
.accounts__input:focus-visible ~ .accounts__track {
outline: 2px solid #6ec8ff;
outline-offset: 2px;
}
.accounts__row:has(.accounts__input:checked) {
background: rgba(76, 212, 166, 0.18);
}
.accounts.is-demo .accounts__row {
animation: acct-row-cycle 2s ease-out infinite;
}
.accounts.is-demo .accounts__track {
animation: acct-track-cycle 2s ease-out infinite;
}
.accounts.is-demo .accounts__knob {
animation: acct-knob-cycle 2s ease-out infinite;
}
.accounts.is-demo .accounts__row:nth-child(1),
.accounts.is-demo .accounts__row:nth-child(1) .accounts__track,
.accounts.is-demo .accounts__row:nth-child(1) .accounts__knob {
animation-delay: 0ms;
}
.accounts.is-demo .accounts__row:nth-child(2),
.accounts.is-demo .accounts__row:nth-child(2) .accounts__track,
.accounts.is-demo .accounts__row:nth-child(2) .accounts__knob {
animation-delay: 500ms;
}
.accounts.is-demo .accounts__row:nth-child(3),
.accounts.is-demo .accounts__row:nth-child(3) .accounts__track,
.accounts.is-demo .accounts__row:nth-child(3) .accounts__knob {
animation-delay: 1000ms;
}
@keyframes acct-row-cycle {
0%, 8% {
background: transparent;
}
14%, 36% {
background: rgba(76, 212, 166, 0.22);
}
42%, 100% {
background: transparent;
}
}
@keyframes acct-track-cycle {
0%, 8% {
background: #d8d2c4;
}
14%, 36% {
background: #6ec8ff;
}
42%, 100% {
background: #d8d2c4;
}
}
@keyframes acct-knob-cycle {
0%, 8% {
transform: translateX(0);
}
14%, 36% {
transform: translateX(14px);
}
42%, 100% {
transform: translateX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.accounts.is-demo .accounts__row,
.accounts.is-demo .accounts__track,
.accounts.is-demo .accounts__knob {
animation: none;
}
}import styles from "./LinkedAccountsList.module.scss";
export type LinkedAccount = {
id: string;
label: string;
connected: boolean;
};
export type LinkedAccountsListProps = {
accounts: LinkedAccount[];
onToggle?: (id: string, connected: boolean) => void;
};
const linkIcon = (
<svg viewBox="0 0 256 256" aria-hidden="true">
<g fill="currentColor">
<path d="m218.34 119.6l-34.74 34.74a46.58 46.58 0 0 1-44.31 12.26c-.31.34-.62.67-.95 1l-34.74 34.74a46.63 46.63 0 1 1-65.94-65.94l34.74-34.74a46.6 46.6 0 0 1 44.31-12.26c.31-.34.62-.67 1-1l34.69-34.74a46.63 46.63 0 0 1 65.94 65.94" opacity=".2" />
<path d="M240 88.23a54.43 54.43 0 0 1-16 37L189.25 160a54.27 54.27 0 0 1-38.63 16h-.05A54.63 54.63 0 0 1 96 119.84a8 8 0 0 1 16 .45A38.62 38.62 0 0 0 150.58 160a38.4 38.4 0 0 0 27.31-11.31l34.75-34.75a38.63 38.63 0 0 0-54.63-54.63l-11 11A8 8 0 0 1 135.7 59l11-11a54.65 54.65 0 0 1 77.3 0a54.86 54.86 0 0 1 16 40.23m-131 97.43l-11 11A38.4 38.4 0 0 1 70.6 208a38.63 38.63 0 0 1-27.29-65.94L78 107.31a38.63 38.63 0 0 1 66 28.4a8 8 0 0 0 7.78 8.22h.22a8 8 0 0 0 8-7.78A54.86 54.86 0 0 0 144 96a54.65 54.65 0 0 0-77.27 0L32 130.75A54.62 54.62 0 0 0 70.56 224a54.28 54.28 0 0 0 38.64-16l11-11a8 8 0 0 0-11.2-11.34" />
</g>
</svg>
);
/** 소셜 로그인 연결 목록 — 스위치를 켜고 끄면 부모가 실제 연결 상태를 바꾼다 */
export default function LinkedAccountsList({ accounts, onToggle }: LinkedAccountsListProps) {
return (
<div className={styles.card}>
<h2 className={styles.title}>Linked accounts</h2>
<div className={styles.list}>
{accounts.map((a) => (
<label key={a.id} className={a.connected ? `${styles.row} ${styles.rowOn}` : styles.row}>
<span className={styles.icon}>{linkIcon}</span>
<span className={styles.label}>{a.label}</span>
<span className={styles.switch}>
<input
type="checkbox"
className={styles.input}
checked={a.connected}
onChange={(e: any) => onToggle?.(a.id, e.target.checked)}
/>
<span className={styles.track}>
<span className={styles.knob} />
</span>
</span>
</label>
))}
</div>
</div>
);
}07 連携アカウント一覧 — 計測 累積面積 12.77% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · 本人確認 画面UI 9選 — そのまま使える認証カード
このデモが属する記事
本人確認 画面UI 9選 — そのまま使える認証カード本人確認 画面とは、カメラへのアクセス許可や認証コードの入力、本人確認直後のプロフィール設定まで、ユーザーが本人であることを証明する一連の流れで使われる一時的な画面のことです。ここでは権限リクエストから認証コード入力、ログアウト確認まで、そ この記事のデモをすべて見る


