
『縁の下のUIデザイン』という本を読んだ。UIの細部だけを集めた実践TIPS集で、その中に「待ち時間のデザイン」を扱った章がある。終わり時間を示すか示さないか、示さないならスケルトンスクリーンやtipsの演出で「待っている間に何を見せるか」を設計する、という内容だ。
読みながら、これは自分のAWS学習サイトの別の問題と同じ構造だと気づいた。サイト内には、まだ用意していないページへのリンクがいくつか残っていて、踏むと出てくるのは Next.js の素の404ページ、真っ白い画面に "404" とだけ書いてある、あの冷たい画面だった。待ち時間が「待っている間に何を見せるか」の設計なら、404ページは「行き止まりに着いた瞬間に何を見せるか」の設計だ。時間軸を「待つ」から「詰まる」に置き換えただけで、同じ問題だった。
本の感想は別に書いた(末尾にリンクを置いている)。この記事では、その気づきを受けて実際に404ページを作り直した過程を書く。実装のハウツーではなく、Claude Code と一緒に 1 枚の empty state をどう仕上げたか という意思決定の記録だ。
なぜ404ページを「ちゃんと」デザインすることにしたか
結論から言うと、404ページを「サイトの一部」として扱うかどうかで、そのサイトの手触りが変わると思ったからだ。
改修前の404ページは、こういう画面だった。
![[before-404.png]]
Next.js が標準で出す枠組みに、リンクカードだけ自分で足した状態。情報としては足りているが、サイトの一部というより「エラーが起きたので機械的に出しました」という顔をしている。
本で読んだ「待ち時間」の章は、待たせている事実を隠すのではなく、待っている間に何を見せるかを設計するという発想だった。同じことが404ページにも言える。学習サイトのアイデンティティは「学習ルートを案内すること」にある。だとすれば、存在しないページに迷い込んだ人にも、それらしい体験を返すべきだと考えた。素の404ページのままだと、そこだけサイトの外側に放り出されたような断絶が生まれる。
Next.js のデフォルト not-found.tsx を自分で書き換えれば済む話ではあるが、「何を書き換えるか」を決める前に、まず「このページに来た人にどう感じてほしいか」を言葉にする必要があった。ここから Claude Code とのやりとりが始まる。
最初に決めたのは色じゃなく「何に感じてほしいか」だった
Tailwind のクラス名を並べる前に決めたのは、コンセプトの一文だった。「404 の冷たさ」を「ロードマップの上の小休止」に変える、という言い換えだ。
存在しないページに来たユーザーを「道に迷った」と扱うのではなく、「まだ整備されていない区画に来ただけ」と感じさせたかった。この違いは小さく見えて、後続のすべての判断に効いてくる。マスコットが「ごめんなさい、迷子にさせました」と謝る役なのか、「こっちはまだ工事中だよ、こっちで学べるよ」と案内する役なのかで、表情もセリフも配色の温度も変わってくるからだ。
Claude Code に投げた最初の指示は、この一文の言い換えを軸にしたコンセプト定義だった。色やレイアウトの話は、その後にようやく出てくる。
参考にしたのは2つのパターン、真似したのは「関係性」だけ
結論として、既存の UI から借りたのは見た目の細部ではなく、要素同士の「関係性」だった。
参考にしたパターンは 2 つある。ひとつは Notion や Linear に見られる、正方形と余白を主役にしたクリーンな empty state。イラスト・見出し・CTA が上から順に並ぶ 3 ゾーン構成で、カードの内側に完結していて、外にはみ出さない。もうひとつは Duolingo 型の「マスコットが状態を説明する」パターンだ。マスコットは単なる飾りではなく、45度で手を上げて「こっちだよ」とポーズを取り、その姿勢自体が「今どういう状態か」を語る役割を持っている。
ここで大事だったのは、この 2 つのパターンからスクリーンショットや配色そのものをコピーするのではなく、「マスコットは装飾ではなく状態説明役として機能させる」という 関係性の設計だけ を抜き出したことだ。カラートークンは既存サイトの slate / sky 系をそのまま継承し、新規定義はゼロにした。プロダクト内の既存画面として自然に馴染ませるための判断だった。
やらないと決めたことのほうが記憶に残っている
やったことより、やらなかったことのほうが今も記憶に残っている。
まず不採用にしたのが「迷子の人物イラスト + 虫眼鏡」という、404ページの定番テンプレだ。検索して出てくる大半の404デザイン記事がこの構図を使っている。学習サイトのコンセプトとは噛み合わないので、最初の案出しの段階で除外した。
改めて振り返ると、本当にこの構図ばかりだったと思う。ただ、今回採用したロードマップとマスコットの組み合わせが最初から学習サイトのコンセプトにしっくりきていたので、他の不採用候補をあれこれ比較する必要すらなかった。一つの案が突出していると、消去法で残りを検討する手間自体が省ける、というのも今回学んだことだ。
次に不採用にしたのが「エラーコードを画面いっぱいの巨大数字で表示する」構成だ。これも404ページの定番だが、"404" を主役にすると「壊れた感」が強調されてしまう。今回はコンセプトを"小休止"に置いたので、404 の表示は eyebrow としてごく小さく添えるだけにとどめた。
もう一つ、地味だが効いたのが「ロードマップを棒グラフや階層ツリーで表現する」案の不採用だ。学習ルートという抽象概念を可視化しようとすると、つい業務系ダッシュボードの図表に寄せたくなる。だが今回欲しかったのは「歩いていく道」という体感で、それを数値化する棒グラフとは相性が悪い。結局、道は横に流れる曲線として描くことにした。
これらの引き算は、Claude Code が最初に出してきた案には実は一部含まれていた。テンプレ的な発想は AI のほうが先に手を出しやすい。それを一つずつ「これは違う」と言葉で削っていく作業が、コンセプトを研ぎ澄ませる工程そのものだったと思う。
アクセシビリティは「後付け」にしないと決めていた
見た目が決まった後回しではなく、色を決める段階からアクセシビリティの基準を組み込んだ。
WCAG というのは「Web Content Accessibility Guidelines」の略で、色弱の人や視力が弱い人でも文字がちゃんと読めるように定められた国際的なガイドラインだ。その中の「AA」という達成基準のひとつに、文字色と背景色の明暗差についての決まりがある。「コントラスト比 4.5:1 以上」というのは、背景色と文字色の明るさの差を数値化したもので、数値が低いほど文字と背景の区別がつきにくく、薄くて読みづらい表示になる。パレットを決める時点で、使う配色の組み合わせすべてに対してこの数値を確認した。
実装が進んだ後になって、eyebrow ラベル(画面上部の小さな補足テキスト)のコントラスト比が 4.6:1 とぎりぎりの数値だったため、微調整でコントラストを引き上げる修正を入れている。基準の 4.5:1 は超えていたが、ぎりぎりの数値のまま残すと今後の微調整で簡単に基準を割ってしまうと考え、余裕を持たせる方向に倒した。
アニメーションについても、色と同じ段階で作り込みではなく仕組みから決めた。CSS には「ユーザーが OS 側でアニメーションを抑制する設定にしているかどうか」を判定できる prefers-reduced-motion というメディアクエリがある。マスコットの手振りアニメーションはこの中だけに閉じ込め、ユーザーがアニメーション抑制を設定していれば静止画として表示されるようにした。
/* prefers-reduced-motion: no-preference の中だけにアニメーションを閉じ込める。
ユーザーが「動きを減らす」設定にしている場合は、このブロックごと適用されず静止画になる */
@media (prefers-reduced-motion: no-preference) {
.mascot-arm {
transform-origin: 426px 115px;
animation: wave 2.4s ease-in-out infinite;
}
}
@keyframes wave {
0%, 100% { transform: rotate(0deg); }
30% { transform: rotate(-12deg); }
60% { transform: rotate(8deg); }
}
こう書く理由は単純で、「動きが苦手な人には最初から動きを見せない」を、CSS の条件分岐だけで保証できるからだ。JavaScript でユーザー設定を判定してから出し分けるより、ブラウザ標準のメディアクエリに任せたほうが判定漏れが起きにくい。
リンクカードには focus-visible のリング表示を全件に適用し、キーボード操作でもどこにフォーカスがあるか分かるようにしてある。マウスを使わずキーボードの Tab キーだけでページ内を移動する人にとって、「今どのリンクを選択しているか」が見えないのは致命的なので、ここは最初から全リンクに一律で入れた。
// すべてのリンクカードに共通で付与するクラス(Tailwind)
// focus-visible: キーボード操作でフォーカスが当たったときだけリングを表示する
// (マウスクリック時には表示されないため、見た目のノイズにならない)
<Link
href={href}
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 focus-visible:ring-offset-2"
>
{label}
</Link>
focus-visible を使っているのは、単なる focus だとマウスでクリックしたときにもリングが出てしまい、見た目のノイズになるからだ。キーボード操作の人にだけ必要な情報を、キーボード操作の人にだけ出す。
今の404ページに対して正直な感想
作り直してしばらく経った今、正直な感想を書いておく。今の404ページはこういう画面になった。
![[after-404.png]]
ロードマップの道の上に旗を立て、雲のマスコットが手を振って「下のリンクから学べるよ!」と案内する構図にした。エラー表示ではなく、学習ルートの途中に立ち寄った小休止のような見た目を狙った。
キャラクターの動きは、まだ硬いと感じている。手を振るアニメーションも、吹き出しの現れ方も、狙った通りの柔らかさには届いていない。ここは今の実装の限界として認めておきたい。
ただし、最初の目的だった「ユーザーが迷い込んだ時の離脱対策」は一旦達成できたと思っている。素の404ページで放り出すのではなく、次にどこへ進めるかを示す代替導線を用意できたことで、行き止まりで終わらせない画面にはなった。
具体的には、手を振るアニメーションのタイミングや動き自体が、まだ機械的で不自然だと感じている。マスコットを2Dの図形で動かす今のやり方には限界があって、例えば3Dオブジェクトとして別に作り、それをページに組み込むようなアプローチを取れば、もっと自然な動きに近づけられるのではないかとも思う。今はその技術も知識もないので一旦保留にしているが、いずれ試してみたい。
デザイン性については、まだ伸びしろが大きい画面だと思っている。今回のプロセスを通じて、コンセプトの言語化やパターンの抽出はできるようになったが、実際の造形やモーションの質を上げる部分はこれからの課題だ。もっとUIのデザイン性を上げていけるように、引き続き学習を続けていきたい。
まとめ
『縁の下のUIデザイン』を読んで、404ページという小さな画面が「ただのエラー表示」ではなく「行き止まりで何を見せるかを設計する画面」だと気づいた。その気づきをそのままにせず、実際にコンセプトの言語化・パターンの抽出・引き算の判断・アクセシビリティの組み込みという意思決定を積み重ねて、1画面を作り直してみた。
本を読んで終わりにせず、手を動かしてみて初めて分かることがあった。次に新しい画面を作るときも、まず本の中の似た課題を探すところから始めてみたい。
読む前は、404ページのような小さな画面はエラーを吐かなければそれでいいという扱いだった。読んだあとは、小さな画面だからといって手を抜いていい理由にはならない、と考えるようになった。
書評『縁の下のUIデザイン』では、この本の学びについて詳しく書いている。今回の404ページの作り直しは、その学びを自分の手元で実際に試した記録になっている。
note書評『縁の下のUIデザイン』: https://note.com/fumi_ai_202507/n/n7064b8fa4f70?app_launch=false
よくある質問
Q: 本で読んだ内容をUI改善にどう活かせばいい?
A: 本の中の抽象的な原則を、自分の手元にある具体的な画面の課題に接続してみるとよい。今回は「待ち時間のデザイン=待っている間に何を見せるか」という学びを、「404ページ=行き止まりで何を見せるか」という別の課題に置き換えて考えた。
Q: 404ページのデザインで気をつけることは?
A: 「迷子イラスト+虫眼鏡」のような定番テンプレをそのまま使うと個性が消える。既存サイトのトークン(配色・タイポ)を踏襲しつつ、マスコットを装飾ではなく状態説明役として機能させる、という判断軸を持つとよい。
Q: AIが作ったUIデザインの初期案をどう改善する?
A: フィードバックの言葉をそのまま反映するのではなく、その言葉が指している構造上の問題を都度確認して直す。テンプレ的な発想はAIのほうが先に手を出しやすいので、一つずつ「これは違う」と言葉で削っていく作業がコンセプトを研ぎ澄ませる。
Q: アクセシビリティ対応で最低限やるべきことは?
A: 文字と背景の明暗差を示すコントラスト比4.5:1以上(WCAG AA基準)、prefers-reduced-motionによるアニメーション制御、focus-visibleによるキーボードフォーカス表示の3点セットを、色やレイアウトを決める初期段階から組み込むとよい(WCAG 2.1 Understanding SC 1.4.3 / MDN prefers-reduced-motion)。