GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

アカウント セキュリティ 設定 UI 9選 — ログイン後の画面

アカウント セキュリティ 設定とは、ログインを済ませた人が次に巡り会う画面のことです。今つながっている端末、二段階認証、パスワードそのものを、本人の手で確かめて変えていく場所です。9つすべて実際に動きます。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

並び順は、セキュリティの画面を初めて開いた人が疑問を持つ順です。まず誰が入っているかを見ます(01)。次に二段階認証を一歩ずつ進め(02)、バックアップコードを受け取り(03)、パスワードなしで入れるようこの端末を登録します(04)。ここからは変える側です。パスワードを替え(05)、見知らぬ端末の通知をどこで受けるか決め(06)、メールアプリに渡すアプリパスワードを発行します(07)。最後の2つは見張る側です。過去のログインを眺めて怪しい接続を探し(08)、取り消せない削除の前に一度だけ言葉を置きます(09)。apple アカウント セキュリティ 設定 のような画面をそのまま真似るのではなく、どの製品の設定画面にも共通して現れる部品だけを取り出しています。入る扉の前の画面は本人確認 画面UIが担い、ログイン後の顔を示す役はプロフィール UI デザインが引き継ぎます。セッション一覧は今つないでいる端末を一番上に置き、残りは最終アクティブ時刻で並べました。二段階認証の三歩は一歩が終わるまで次が開かないよう束ね、バックアップコードは使った行の数字を二度表示せず打ち消し線だけを残します。パスワードの規則は入力が終わる瞬間ではなく打っているあいだじゅう更新され、ログアウトした端末は消さずに灰色で残しています。ほかの端末で何を消したか後から見られるためです。画面が狭いときは二段組のカードを一段に畳み、確認のボタンだけは幅を保って指が届く位置に留めています。

01いまログイン中の端末一覧

何が見えるか

端末アイコンと接続した都市、最後に使った時刻が一行に並び、いま見ている端末には別のバッジが付きます。

いまログイン中の端末一覧
新しいタブで開く

01 · いまログイン中の端末一覧 — 計測 累積面積 11.214% · 動いたフレーム 9/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translateXmax-heightaria-live

コードのどこを見るか

行の右のボタンを押すとその行だけ横へ滑り出し、高さも一緒に畳まれるので下の行が跳ねません。

.sd.is-demo .sd__row[data-row="3"] {
  animation-name: sd-out;
  animation-duration: $dur-loop;
  animation-timing-function: $easing;
  animation-iteration-count: infinite;
}
@keyframes sd-out {
  0%   { transform: translateX(0); max-height: var(--row-h); background-color: transparent; }
  28%  { transform: translateX(0); max-height: var(--row-h); background-color: transparent; }
  31%  { transform: translateX(0); max-height: var(--row-h); background-color: $error-soft; }
  48%  { transform: translateX(112%); max-height: var(--row-h); background-color: $error-soft; }
  58%  { transform: translateX(112%); max-height: 0; background-color: $error-soft; }
  68%  { transform: translateX(112%); max-height: var(--row-h); background-color: $error-soft; }
  84%  { transform: translateX(0); max-height: var(--row-h); background-color: $error-soft; }
  87%  { transform: translateX(0); max-height: var(--row-h); background-color: transparent; }
  100% { transform: translateX(0); max-height: var(--row-h); background-color: transparent; }
}

02二段階認証を入れる三つの手順

二段階認証 画面 の三手順はどう進むか

アプリでコードを読む場面、六桁を入れ直す場面、バックアップコードを受け取る場面が一枚のカードの中で順番に開きます。

二段階認証を入れる三つの手順
新しいタブで開く

02 · 二段階認証を入れる三つの手順 — 計測 累積面積 18.638% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aria-currentsteps(1, end)scaleX

コードの読み方

手順ごとに上の目盛りが一つ埋まり、通り過ぎた手順は開き直しません。

.tf.is-demo .tf__panel {
  animation-duration: $dur-loop, $dur-loop;
  animation-timing-function: steps(1, end), $easing;
  animation-iteration-count: infinite, infinite;
}
.tf.is-demo .tf__panel[data-panel="1"] { animation-name: tf-cut1, tf-rise1; }
.tf.is-demo .tf__panel[data-panel="2"] { animation-name: tf-cut2, tf-rise2; }
.tf.is-demo .tf__panel[data-panel="3"] { animation-name: tf-cut3, tf-rise3; }

@keyframes tf-cut1 { 0% { opacity: 1; } 30% { opacity: 0; } 86% { opacity: 1; } 100% { opacity: 1; } }
@keyframes tf-cut2 { 0% { opacity: 0; } 30% { opacity: 1; } 60% { opacity: 0; } 100% { opacity: 0; } }
@keyframes tf-cut3 { 0% { opacity: 0; } 60% { opacity: 1; } 86% { opacity: 0; } 100% { opacity: 0; } }

03一度使うと線が引かれるバックアップコード

どんな動きか

九文字のコードが六つ、カードの中に二列のグリッドで並び、すでに使ったコードには真ん中に線が引かれて薄く残ります。

一度使うと線が引かれるバックアップコード
新しいタブで開く

03 · 一度使うと線が引かれるバックアップコード — 計測 累積面積 8.319% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

line-throughnavigator.clipboardaria-live

どこを変えるか

下のボタンは一覧全体をクリップボードへ渡し、その瞬間を読み上げ領域にも一度流します。

btn.addEventListener('click', function () {
  root.classList.remove('is-demo');        // 人が触ったので自動ループを切る
  clearTimeout(t);
  root.setAttribute('data-copied', '1');
  var txt = fullCode();
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(txt);
  }
  live.textContent = 'バックアップコード6個をクリップボードにコピーしました';
  t = setTimeout(function () { root.setAttribute('data-copied', '0'); }, 1600);
});

04指紋から盾へ切り替わるパスキー登録

何が見えるか

中央の指紋が膨らんでは戻るのを繰り返し、端末が指を待っていることを示します。

指紋から盾へ切り替わるパスキー登録
新しいタブで開く

04 · 指紋から盾へ切り替わるパスキー登録 — 計測 累積面積 10.955% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

steps(1, end)aria-busyscale

コードのどこを見るか

登録が終わると盾の印へ一フレームで切り替わり、待っている間ボタンは固まり、その事実は属性にも書かれます。

// 弧は待機の区間だけ見える — 回転とカットを一つの要素に重ねて掛ける
.pk.is-demo .pk__arc {
  animation-name: pk-spin, pk-busy;
  animation-duration: 900ms, $dur-loop;
  animation-timing-function: linear, steps(1, end);
  animation-iteration-count: infinite, infinite;
}

// 指紋は待っている間、膨らんでは縮むを繰り返す
.pk.is-demo .pk__ic[data-ic="print"] {
  animation-name: pk-print, pk-pulse;
  animation-duration: $dur-loop, $dur-loop;
  animation-timing-function: steps(1, end), $ease-spring;
  animation-iteration-count: infinite, infinite;
}

05三つの欄がそろって開くパスワード変更

画面で起きること

いまのパスワード、新しいパスワード、確認欄がすべて条件を満たすまで保存は開きません。

三つの欄がそろって開くパスワード変更
新しいタブで開く

05 · 三つの欄がそろって開くパスワード変更 — 計測 累積面積 18.794% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-columnsaria-describedbyaria-disabled

コードの読み方

新しい欄の下の四つの目盛りはグリッドで切ってあるので幅がずれず、確認欄が違うとその行だけ赤く残り、理由が添えられます。

// ── 四つの強度目盛り — 目盛りの幅は grid が分ける (scaleX は目盛りの内側の塗りだけに) ──
.pw__meter {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: $sp-1;
  margin-top: $sp-1;
}
.pw__cell {
  display: block;
  overflow: hidden;
  height: 6px;
  border-radius: $r-pill;
  background-color: rgba($subject-ink, .14);
}

06新しい端末のログイン通知スイッチ

どんな動きか

メール、プッシュ、SMSの三行がスイッチを一つずつ持ち、つまみが左右に滑ります。

新しい端末のログイン通知スイッチ
新しいタブで開く

06 · 新しい端末のログイン通知スイッチ — 計測 累積面積 12.649% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role="switch"aria-checkedtranslateX

どこを変えるか

一つ変えると下に保存済みの行が灯り、その行は消えずに次の変更まで残るので、代表画像が空の画面になりません。

// 実際の動作: role="switch" の <button> なので Space·Enter がそのまま click で来る — 別途横取りしない
var root = document.querySelector('.al');
var switches = root.querySelectorAll('.al__sw');
Array.prototype.forEach.call(switches, function (sw) {
  sw.addEventListener('click', function () {
    root.classList.remove('is-demo');   // 人が触ったので自動ループを切る
    var on = sw.getAttribute('aria-checked') !== 'true';
    sw.setAttribute('aria-checked', String(on));
    root.setAttribute('data-saved', 'true');   // 保存の行は消えず、次の変更まで残る
  });
});

07一度だけ見せるアプリパスワード

何が見えるか

発行を押すと隠されていた場所が左から一コマずつ剥がれて文字が現れ、コピーが済むと同じ場所が点で覆われて二度と開きません。

一度だけ見せるアプリパスワード
新しいタブで開く

07 · 一度だけ見せるアプリパスワード — 計測 累積面積 8.803% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

navigator.clipboardsteps(16, end)aria-live

コードのどこを見るか

値が一度しか見えないという注意は、現れる前からカードに書かれています。

// ── 自動ループ: 覆いが16コマに刻まれながらめくれ、現れた場所はそのまま残す (退場は再現しない) ──
.tk.is-demo .tk__cover {
  animation-name: tk-strip;
  animation-duration: $dur-loop;
  animation-timing-function: steps(16, end);
  animation-iteration-count: infinite;
}
@keyframes tk-strip {
  0%   { width: 100%; }
  10%  { width: 100%; }
  56%  { width: 0%; }
  100% { width: 0%; }
}

08成功と失敗が一緒に並ぶログイン履歴

画面で起きること

縦の線に沿って時刻と場所が積み上がり、点の色で成功と失敗が分かれます。

成功と失敗が一緒に並ぶログイン履歴
新しいタブで開く

08 · 成功と失敗が一緒に並ぶログイン履歴 — 計測 累積面積 36.643% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

::beforebox-shadowtranslateY

コードの読み方

いつもと違う国から入った行には枠がもう一重付き、その行だけゆっくり呼吸するので最初に目に留まります。

// ── 自動ループ: 行が translateY の時差で一つずつ浮かび上がる。ショートハンド禁止 · 遅延には backwards 必須 ──
// 浮き上がり(transform)と点灯(opacity)を分けて掛ける — opacity を一緒に補間すると半透明のゴーストフレームが出る
.lh.is-demo .lh__item {
  animation-name: lh-rise, lh-show;
  animation-duration: $dur-loop, $dur-loop;
  animation-timing-function: $easing, steps(1, end);
  animation-iteration-count: infinite, infinite;
  animation-fill-mode: backwards, backwards;
}
.lh.is-demo .lh__item[data-i="1"] { animation-delay: 40ms, 40ms; }
.lh.is-demo .lh__item[data-i="2"] { animation-delay: 200ms, 200ms; }
.lh.is-demo .lh__item[data-i="4"] { animation-delay: 360ms, 360ms; }
.lh.is-demo .lh__item[data-i="3"] { animation-delay: 520ms, 520ms; }

// 一度上がった行はそのまま留まる (退場は再現しない)。22% × 2s = 440ms で止まる
@keyframes lh-rise {
  0%   { transform: translateY(16px); }
  22%  { transform: translateY(0); }
  100% { transform: translateY(0); }
}

09文字を打ち込んで初めて開く退会

どんな動きか

赤い区画の入力欄に決められた語をそのまま打ち込むまで削除は固まったままで、文字が合った瞬間にだけボタンが開きます。

文字を打ち込んで初めて開く退会
新しいタブで開く

09 · 文字を打ち込んで初めて開く退会 — 計測 累積面積 38.426% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role="alertdialog"aria-disabledoutline

どこを変えるか

押して出る確認ダイアログは警告として開くので、フォーカスが先にその中へ入ります。

dialog.addEventListener('keydown', function (e) {
  if (e.key === 'Escape') { openDialog(false); return; }
  if (e.key !== 'Tab') return;
  e.preventDefault();                            // フォーカスが確認ダイアログの外へ出られない
  (document.activeElement === noBtn ? yesBtn : noBtn).focus({ preventScroll: true });
});

どこで壊れるか — 落とし穴

状態をopacityでなめらかにつなぐと、24フレームのプレビューは二つの画面が半透明で重なった幽霊を一枚撮ります。02の三つの手順パネルは最初フェードで書きましたが、いちばん大きく変わるフレームがQRコードと六桁の欄と完了の盾を三枚重ねで写した一枚だったのに気づいて切り替えました。画面の交換は steps(1, end) で一フレームに断ち、その横に translateY のポップと scaleX の目盛り埋めを並べて、動くフレーム数を確保しました。この形の02は24枠のうち22枠に動きを記録しています。08の行の浮上も同じ切り分けで、移動はスプリングのイージングに、点灯は一フレームのカットに乗せています。

狭い画面には規則を決めてあります。幅320pxではステージの高さが200pxで、9つの部品の高さを測ると116px(07)から172px(05)の間でした。三つの欄に目盛りと注意文が全部入る05がいちばん高く、余白は28pxでした。そこで狭い画面では値を増やさず減らします。06はスイッチのトラックが40×22pxから32×18pxへ縮みますが、キーフレーム内のつまみの移動距離18pxはメディアクエリの外にある値なので、そのままだと2pxはみ出します。そこで狭い幅のブロックの中で al-knob-on・al-knob-push・al-knob-sms の三つのキーフレームを、移動距離14pxで丸ごと定義し直しました。

測ってみると、9つは性格で分かれます。09は面積38.4%に強度160.7で、いちばん広く、いちばんくっきり動きます。確認ダイアログがスクリーンごと一フレームで開く場面が、それだけの画面を一度に塗り替えるからです。逆に03は面積8.3%しかないのに、強度は152.6で二番目に高い。打ち消し線は細い棒なので面積は小さいのですが、明るいカードの上の濃い赤い線は差が大きいのです。08は四つの行が時差で浮上し、50%の地点で三つの行が同時に暗くなる場面が最大の変化となって36.6%を記録しました。一行が浮くより三行が一度に変わる方が面積が大きくなる、いつもの規則をこの記事でもそのまま使っています。07の覆いは16段のstepsで剥がれます。段の間隔は、タイミング関数が当たる「幅が落ちる区間」10%から56%の920msを16等分した57.5msです。その間に発行ボタンが押される反発を横に添えて、動くフレーム17枠を埋めました。

08を作りながら、遅延の扱いについて規則を一つまとめました。四つの行は40ms・200ms・360ms・520msと時差で浮上しますが、この遅延は animation-delay で与えつつ、必ず animation-fill-mode: backwards と組にします。fill-modeがないと遅延の間、要素は書いたままの姿で描かれるので、0フレームに完成した一覧が一瞬現れて消える瞬きが撮れます。順番もDOMの順とわざと変えました。見慣れない国の行はDOMでは三番目ですが、遅延は520msでいちばん遅く浮上します。最後に届く行こそ、目の止まる行だからです。さらに05の四つの目盛りは animation-delay を使わず、目盛りごとに自分の時間割を持つキーフレーム四つで組みました。共通の規則に animation: の一括指定を置くとdelayが0に戻り、240番で五つの枠が同時に動いた落とし穴を踏み直すことになります。

04は回る弧一本で測定を稼ぎました。状態の交換を steps(1, end) のカットだけで組むとフレームが二枚しか残らず、レンダーの測定を通れません。そこで待っている区間だけ900ms周期で回る弧を同じ要素に重ね、カットは指紋から盾へ切り替わる瞬間だけに使いました。アニメーション名をカンマで二つ並べ、タイミング関数は弧が linear, steps(1, end)、指紋が steps(1, end), $ease-spring と、それぞれの対で指定する形です。02の三つのパネルも同じ配線で steps(1, end), $easing となり、目盛りバーは84%から86%へ三つ同時に戻します。通過した痕跡が一度に消える方が残像が少ないためです。01の戻る道は同じスプリングのイージングで押し戻し、瞬間移動に見えないようにして、三行目が再び広がった後の87%で背景だけを単独で消します。ログアウトを押したときの通知文も46%から48%の二ポイントの間でカットによって灯し、84%まで保ちます。半透明の中間フレームを作らないためです。02の六桁の欄には inputmode="numeric" と maxlength="6" を併せて置き、モバイルのキーボードが数字パッドで先に開くようにしました。フォーカスを移すときは focus({ preventScroll: true }) で親ページが引きずられないように防いでいます。デモはホームの一覧にiframeで埋め込まれているので、中でフォーカスが動くたびに上のページが勝手にスクロールされては困るのです。09のタイピング表示は等幅の ch 単位で幅を切り、六文字が正確に一コマずつ現れます。キャレットの点滅は400ms周期で、2秒のループの中にちょうど五周収まるので継ぎ目で点滅が途切れません。覆いの下の値も同じ等幅で組んであるので、幅の揃った十九文字が一コマごとに顔を出します。一覧から行を払い出す動作が必要になればスワイプ削除リストUIがその手をそのまま使います。9つ分の規則を一つのフォルダにまとめた圧縮ファイルのパスワードは eeu22zhr で、中にはいま見ている素の版とReact版が同じ値で入っています。

アクセシビリティ

prefers-reduced-motion: reduce をオンにした画面では、9つすべてのループが止まり、結果の状態だけが残ります。止めるだけで終わりにせず、到着点を固定します。05は目盛りの四つが全部埋まり保存ボタンが活性色の終わった状態で、08は四つの行がすべてくっきり残り危ない行にだけ赤い枠が残る状態で、09は確認ダイアログが開いた画面で止まります。reduced-motionのブロック内の宣言にはすべて !important を付けました。付けないと animation: none は (0,3,0)の詳細度に負けて、260番で実測されたようにアニメーションが回り続けます。

スイッチは飾った <div> ではなく、role="switch" を持つ本物の <button> なので、SpaceとEnterがそのままclickとして届きます。施錠は本物の disabled ではなく aria-disabled を使いました(05・07・09)。ボタンが消えると、キーボードの利用者は急にTabが飛ぶ理由を知れません。フォーカスは残して、押したときの理由の文は05では aria-describedby の指す行に、09ではボタンの隣に現れます。削除の確認ダイアログは role="alertdialog" で、名前も説明も画面にすでにある文字を aria-labelledby と aria-describedby で指します。開けばフォーカスはその中へ移り、Tabは二つのボタンの間だけを巡ります。

部品 目で見ると 読み上げでは 操作
01 接続端末の一覧 行が滑り出て畳まる role="status" がログアウトを読む ボタンを押す
02 認証の三手順 目盛りが一つずつ埋まる aria-current="step" が手順ごとに移動 次へ · Enter
03 バックアップコード 使ったコードに線 role="status" がコピーの瞬間を読む すべてコピー
04 パスキー登録 指紋が膨らみ盾へカット aria-busy が待ち時間を知らせる 登録を押す
05 パスワード変更 目盛り四つ · 赤い不一致行 aria-describedby の行が理由を答える 一文字ごと
06 通知スイッチ つまみが滑る role="switch" + aria-checked 押す · Space
07 アプリパスワード 覆いが16コマで剥がれる role="status" が発行とコピーを読む 発行 → コピー
08 ログイン履歴 危ない行に枠と呼吸 行が aria-pressed で確認を示す 行を押す
09 危険な操作 DELETEを打たないと開かない alertdialog とフォーカスの閉じ込め 入力 · Escape

スイッチの役割の付け方は MDNのswitchロールに、ダイアログを警告として開く方法は alertdialogロールにまとまっています。ログイン画面のカードは ログイン画面デザイン9選に、本人確認のカードは 本人確認画面UI 9選に集まっています。

FAQ

二段階認証の手順はCSSだけでは進めませんか?

目盛りまでは進みます。scaleX で一つずつ埋めていくのは純粋なCSSです。ただし、手順を正しく通過したかの判定はCSSにはできません。六桁のコードが本物と合っているか比べ、その結果を次の手順の開放に反映する仕事はJavaScriptが担います。02が aria-current="step" をスクリプトで動かす理由です。目盛りは見た目で、手順は状態です。絶えず変わる値の板を浮かべたいならリアルタイム株価表示が同じ席の埋め方を見せます。

バックアップコードのコピーが失敗するブラウザはありますか?

あります。navigator.clipboard はセキュアコンテキストでしか存在しません。なので03は呼ぶ前に navigator.clipboard && navigator.clipboard.writeText を確認し、なければクリップボードには触らず、ラベルと読み上げの文だけ切り替えます。利用者にエラーを投げる代わりに、画面自身が状態を知らせる道を選びました。実際のサービスなら、ここに手動で選んでコピーする案内を一行足す方が安全です。

google アカウント セキュリティ 設定 のように端末の行を消すとき、なぜ高さまで畳むのですか?

行だけを横に滑らせて空間を残すと、一覧に穴が開いたままになります。行の height は内容に合わせて自動的に決まる値なので、目標値なしには動かせません。そこで max-height を行の高さから0まで縮めて、下の行が滑らかに持ち上がるようにしています。01は滑り出す区間と畳まる区間を一つのキーフレームの中に並べて置き、戻る道も同じイージングで押し戻して瞬間移動に見えないようにしました。一覧全体の高さは固定してあるので、行が畳まれてもカードの他の部分は動きません。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。