Web とモバイルで一貫した読み込み・エラー・空状態を保つためのシンプルな仕組みを学び、AI生成された UI でも初めから整った状態で実装できるようにします。

読み込み、エラー、空状態は、アプリが待機中のとき、何かが失敗したとき、または表示するものが何もないときにユーザーが見る画面(あるいは小さなUIブロック)です。ネットワークは遅くなるし、権限は拒否されるし、新しいアカウントはデータがゼロから始まります。これらは普通に起きることです。
乱雑になる理由は、たいてい後回しにして急いで追加されるからです。チームはまずハッピーパスを作り、残りの箇所にスピナー、赤いメッセージ、そして「項目がありません」プレースホルダを張り付けていきます。これを多数の画面で繰り返すと、使い回しのきかないワンオフの山になります。
高速な反復は状況をさらに悪化させます。UI が素早く作られると(AI生成のUI を含む)、メインレイアウトは数分で出せても、これらの状態は簡単に抜け落ちます。新しい画面ごとにスピナーのスタイルや文言(「Try again」 と「Retry」のような違い)、ボタンの配置がばらばらになります。最初に得た速度は、リリース直前の磨き作業に変わります。
状態の不一致はユーザーを混乱させ、チームの時間を奪います。空のリストが「結果なし」なのか「まだ読み込まれていない」なのか「アクセス権がない」のか判別できません。QA は細かなバリエーションの長い尻尾をテストしなければならず、Web とモバイルで挙動が違うためバグが混入します。
「乱雑」はしばしば次のように見えます:
目標は単純です:Web とモバイルで共通のアプローチを作ること。もしチームが素早く機能を生成する(たとえば Koder.ai のようなプラットフォームを使う)なら、共有の状態パターンがさらに重要になります。新しい画面がデフォルトで一貫した状態を持って始まるからです。
ほとんどのアプリは同じ問題箇所を繰り返します:リスト、詳細ページ、フォーム、ダッシュボード。ここでスピナーやバナー、 "何もない" メッセージが増殖します。
まずは5つの状態タイプに名前を付けて標準化しましょう:
ふたつの特殊ケースは別ルールに値します:
画面やプラットフォームを越えて、構造を一貫させてください:状態が現れる場所、アイコンのスタイル、トーン、そしてデフォルトアクション(再試行、リフレッシュ、フィルタ解除、作成)。変えられるのは文脈だけです:画面名やユーザーの言葉を使った一文など。
例:Web とモバイル両方で「プロジェクト」の一覧を生成するなら、ゼロ結果のパターンを共有させます。アクションラベルはプラットフォームに合わせて変えてもよい(「フィルタをクリア」 vs 「リセット」など)。
画面ごとにスピナー、エラーカード、空メッセージを考えると、微妙に違うバージョンが山になります。最速の対策は、どの機能にも差し込める小さな "StateKit" を作ることです。
どこでも使える再利用コンポーネントをまずは3つ用意しましょう:Loading、Error、Empty。あえて地味に保ちます。目立ちすぎず、メインUIと競合しないことが大事です。
コンポーネントを予測可能にするために、入力項目を少数に絞ります:
次に見た目を決め切ります。一度だけ余白、タイポ、アイコンサイズ、ボタンスタイルを決めてルールにしてください。アイコンサイズやボタンタイプが揃っていれば、ユーザーは状態UIを気にしなくなり、そのUIを信頼するようになります。
バリアントは限定的に保ち、StateKit が別のデザインシステムにならないよう注意します。通常は3つのサイズで足ります:小(インライン)、標準(セクション)、フルページ(ブロッキング)。
もし Koder.ai で画面を生成しているなら、"use the app StateKit for loading/error/empty with default variant" のような簡単な指示でブレを防げます。React の Web と Flutter のモバイルにまたがる後処理も減ります。
コピー(文言)もシステムの一部であり、装飾ではありません。レイアウトが揃っていても、都度作るフレーズでは画面の印象が違ってしまいます。
共通の声(ボイス)を選んでください:短く、具体的で、落ち着いた口調。何が起きたかを平易に伝え、その後にユーザーが次にすべきことを示します。ほとんどの画面は、明確なタイトル1つ、短い説明1つ、目立つアクション1つで充分です。
いくつかの定型パターンで大半の状況をカバーできます。小さい画面にも収まるよう短く保ってください:
「Something went wrong」のような曖昧な文言は避けてください。本当に原因が不明な場合は、分かっていることとユーザーが今できることを示します。例えば「プロジェクトを読み込めませんでした」は単に「エラー」と表示するより親切です。
ルールを一つ設けてください:すべてのエラーと空状態には次の一手を提示すること。
AI生成のUIでは、画面が素早く出る分テンプレートで文言を統一しておくことが、リリース前の数十件のワンオフ修正を防ぎます。
状態画面がページごとに違うアクションを示すと、ユーザーは躊躇します。チームはリリース直前にボタンや文言をいじることになりがちです。
各状態にどのアクションがふさわしいかを決め、配置とラベルを統一してください。ほとんどの画面は主要アクションを1つ持つべきです。二つ目を置くなら主経路を支援するもので、競合しないようにします。
許容するアクションは絞りましょう:
地味なボタンは機能です。UI を馴染ませ、生成された画面が一貫性を保つのに役立ちます。
再試行は現実的に成功する見込みがある場合にのみ表示します(タイムアウト、断続的なネットワーク、5xx など)。連打を防ぐために短いデバウンスを入れ、再試行中はボタンをローディング状態に切り替えます。
繰り返し失敗したら、主要ボタンはそのままにして二次的なヘルプ(「接続を確認」や「後でもう一度」など)を強化します。失敗が二回起きたからといってレイアウト全体を変えるのは避けてください。
エラーの詳細はユーザーが取れる行動を示す短い理由を出します(「セッションが切れました。再ログインしてください。」など)。技術的な詳細はデフォルトで隠し、必要なら一貫した「詳細」表示で見せます。
例:モバイルで「プロジェクト」一覧の読み込みに失敗した場合、両プラットフォームは同じ主要「再試行」アクションを表示し、再試行中は無効化し、2回失敗したら接続ヒントを小さく表示する、という挙動にします。レイアウトを変更しないことが重要です。
状態の一貫性はリデザインではなく小さなプロダクト変更として扱ってください。段階的に進めて導入しやすくします。
まずは既存の状況を軽くスナップショットします。完璧を目指さず、よく見られるバリエーションを拾います:スピナーとスケルトン、フルページエラーとバナー、異なるトーンの "No results" など。
実用的なローアウト計画:
コンポーネントができれば、時間を節約するのは論争を減らす短いルールセットです:ページ全体をブロックする状態とカードだけを覆う状態の違い、必須のアクションは何か、などを明確にします。
ルールは短く:
Koder.ai のような AI UI ジェネレータを使う場合、これらのルールは早く成果を出します。StateKit を指定してプロンプトすると、React の Web と Flutter のモバイルで一致する画面が得られ、後処理が減ります。
後回しの磨き作業は、状態処理がワンオフで作られたときに起きます。画面は「動く」けれど、何かが遅い、失敗する、データがないときの体験が毎回違うと感じられます。
スケルトンは有効ですが、長時間そのままにしておくとアプリが固まった印象を与えます。よくある原因は、信号のない遅い呼び出しでフルスケルトンを表示し続け、進行中であることが分からないことです。
時間を区切ってください:短い遅延の後で軽めの「まだ読み込み中です…」メッセージに切り替えるか、進捗が出せるならそれを表示します。
チームは同じ問題でも毎回新しいメッセージを書きがちです。「Something went wrong」「Unable to fetch」「Network error」などは同じケースを指していても不一致に見えますし、サポートが難しくなります。
エラータイプごとに1つのラベルを決め、Web とモバイルで同じトーンと詳細レベルで再利用してください。
別の典型的なミスは、データが完全に読み込まれる前に空状態を表示したり、実際は失敗なのに「項目なし」を表示することです。ユーザーは間違った行動(コンテンツを追加するなど)を取ってしまいます。
決定順を明確にします:まず読み込み、次に失敗ならエラー、最後に成功してデータが空なら空状態を表示します。
回復手段のないエラーは行き詰まりを作ります。逆に、三つのボタンが並んで主張し合うこともよくあります。
絞ってください:
小さな差異(アイコンスタイル、余白、ボタンの形状)が積み重なると違和感になります。AI生成のUI はプロンプトが画面ごとに違うとここでズレが生じます。
状態コンポーネントの余白、アイコンセット、レイアウトを固定して、どの新しい画面も同じ構造を継承するようにします。
Web とモバイルで一貫した状態処理をしたければ、「地味な」ルールを明文化してください。多くの後回しの磨き作業は、画面ごとに読み込み挙動、タイムアウト、ラベルを勝手に決めたことが原因です。
フルページ読み込みではデフォルトを一つ決めます:コンテンツが重い画面(リスト、カード、ダッシュボード)はスケルトン、レイアウトが不明な短い待ち時間はスピナーにします。
UI が黙ったまま吊るされないようにタイムアウト閾値を設けます。読み込みが約8〜10秒を越えたら、明確なメッセージと「再試行」のような可視アクションに切り替えます。
部分的な読み込みでは画面を真っ白にしないでください。既にあるコンテンツは表示したまま、更新中のセクション付近に小さな進捗表示(ヘッダの細いバーやインラインスピナーなど)を出します。
キャッシュされたデータは「古いが使える」扱いにします。キャッシュを即表示して、さりげない「更新中…」インジケータを出し、データが変わる可能性を示します。
オフラインは別状態です。はっきりと書き、何が利用可能かを示します。例:「オフラインです。保存済みプロジェクトは表示できますが、同期は保留中です。」など。主要な次の一手は「再試行」や「保存済み項目を開く」など1つに絞ります。
プラットフォーム横断で次を統一してください:
Koder.ai のようなツールで UI を生成する場合、これらのルールを StateKit に焼き込んでおけば、新しい画面はデフォルトで一貫性を保ちます。
単純な CRM を想像してください。Contacts(連絡先)リスト画面と Contact(連絡先)詳細画面があります。状態をワンオフで扱うと、Web とモバイルはすぐにズレます。小さなシステムがあれば、UI が素早く作られても整合性を保てます。
初回の空状態(Contacts リスト): ユーザーが Contacts を開いて何もない状態。Web とモバイルでタイトルは同じ(「連絡先」)、空メッセージは理由を説明(「まだ連絡先がありません」)、次の一手は一つ(「最初の連絡先を追加」)。セットアップが必要なら(メールボックス接続や CSV インポートなど)、空状態はその具体的な手順を示します。
遅いネットワークでの読み込み: ユーザーが Contact 詳細ページを開くとき、両プラットフォームは最終ページ構造に一致するスケルトン(ヘッダ、主要フィールド、メモ)を表示します。戻るボタンは機能し、ページタイトルは見えるようにし、ランダムに別の場所にスピナーが出るのは避けます。
サーバーエラー: 詳細取得が失敗した場合、Web とモバイルで同じパターン:短い見出し、一文の説明、主要アクション(「再試行」)。再試行が続けて失敗したら「Contacts に戻る」といった二次オプションを提示して、ユーザーを行き詰まらせないようにします。
一貫している点はシンプルです:
遅い接続、新規アカウント、または不安定な API に遭遇するまではビルドが「完了」に見えることがよくあります。このチェックリストで最後の詰めを見つけ、QA を宝探しにしないでください。
リスト画面から始めてください。リストは増殖しやすいからです。代表的なリストを3つ(検索結果、保存済み項目、最近のアクティビティ)選び、すべて同じ空状態構造を使っているか確認します:明確なタイトル、一つの説明文、一つの主要アクション。
データがまだ読み込まれている間に空状態が表示されないことを確認してください。"何もない" が一瞬表示されてからコンテンツに差し替わるとユーザーの信頼が落ちます。
読み込みインジケータの一貫性もチェック:サイズ、配置、最小表示時間が妥当で、ちらつかないこと。Web がトップバーのスピナーで、モバイルが同じ画面でフルスクリーンスケルトンを出すのは別物に感じられます。
エラーは常に「今どうする?」に答えるべきです。すべてのエラーに次の一手(再試行、リフレッシュ、フィルタ変更、再ログイン、サポートへの連絡)を用意してください。
リリース直前の短い確認事項:
Koder.ai のような AI ビルダーを使うなら、画面は早く生成できますが一貫性は共有キットとコピー規則に依存します。
一貫性は日常業務の一部にすると楽です。一度の掃除ではなく、新しい画面が "合っているか" を忘れずにチェックする文化を作ります。新しい画面は読み込み状態、空状態(該当する場合)、そして明確なアクションを伴うエラー状態がなければ完成ではないと定義しましょう。
ルールは軽く、でも書き残してください。短いドキュメントに数枚のスクリーンショットと正確な文言パターンを書く程度で十分です。新しいバリアントは例外扱いにして、誰かが新しい状態デザインを提案したら本当に新ケースか、それとも既存キットに当てはまるかを問う習慣をつけます。
多くの画面をリファクタするなら、リスクを下げるため段階的にやります:1つのフローずつ更新し、Web とモバイルで検証してから続ける。Koder.ai ではスナップショットとロールバックが大きな変更を安全にするのに役立ち、Planning Mode は共有 StateKit を定義して新しく生成される画面が最初からデフォルトに従うようにできます。
今週、状態の問題で遅れが出ている領域を1つ選んでください(多くは検索、オンボーディング、アクティビティフィードです)。その後:
これが機能している明確なサインは、"再試行を追加"、"空状態が変"、"読み込みスピナーがページをブロックしている" といった小さなチケットが減ることです。
状態標準の責任者を一人決めます(デザイナーかテックリード、あるいは両方)。すべてを承認する必要はありませんが、キットが徐々に似て非なるバリアントに分裂し、見た目が似て挙動が違うものになって時間を食う事態を防ぐ役割は果たしてください。
最初に名前を決めて共通で使う少数の状態をそろえます:初回読み込み、更新中(リフレッシュ)、空のベースライン(新しいアカウントなど)、ゼロ結果(フィルタや検索の結果がない)、エラー。オフラインと遅いネットワークには明確なルールを追加して、ランダムなエラー扱いにしないでください。チームが名前とトリガーに合意すれば、画面やプラットフォーム間でUIの振る舞いが予測可能になります。
小さな StateKit を作って、Loading、Error、Empty の3つの再利用可能なパーツを用意します。各コンポーネントは共通の入力(タイトル、短いメッセージ、主要アクション1つ、任意の詳細)で動くようにして、画面ごとに新しいUIを作らなくて済むようにします。既定バリアントを最も使いやすくして、ワンオフが生まれにくくします。
単純な判断順を使ってください:リクエストが終わるまでは読み込みを表示し、失敗したらエラーを表示し、成功してデータが空の場合にのみ空状態を表示します。これで "No items" がデータ到着前に一瞬表示される、といったバグを防げます。QA にとっても挙動が一貫するので確認しやすくなります。
状態ごとにデフォルトのアクションを1つ決め、それを画面全体で同じラベルと配置で再利用します。エラーには通常「再試行」、空のベースラインには「作成(または次のセットアップ手順)」、ゼロ結果には「フィルタをクリア」を割り当てます。主要なアクションが予測可能だと、ユーザーの操作が速くなり、チームの議論も減ります。
共有テンプレートを作ります:状況を名前で示す短いタイトル、平易な言葉での一文説明、そして明確な次の一手を示すアクション。例えば「We couldn’t load your projects」よりも「プロジェクトを読み込めませんでした」のように具体的に。トーンは落ち着いて統一してください。これで Web とモバイルで一貫した印象になります。
オフラインは汎用のエラー扱いにしないでください。キャッシュ済みの内容があれば表示し、「オフラインです」と明示し、何が今できるかを伝えます。次の一手は「再試行」のように一つに絞るとユーザーが迷いません。
短い待ち時間のあとにすぐエラーを表示しないでください。一定の閾値を設け、境界を越えたら「まだ読み込み中です…」などの明示的なメッセージに切り替え、目に見えるアクション(「再試行」など)を出します。これでネットワークが遅くてもアプリが壊れている印象を与えにくくなります。
3つのサイズバリアントを用意します:カードやセクション内の小さい inline、標準のセクション、ページ全体を覆うフルページ。どの場面でどれを使うかを事前に定義しておけば、画面ごとに勝手に作られることを防げます。間隔、アイコンスタイル、ボタンスタイルは全バリアントで揃えておくのが肝心です。
いくつかの基本ルールを組み込みます:状態が出たらメッセージと主要アクションにフォーカスを移す、読み込みやエラーは短く明確な文で通知する、モバイルでボタンは押しやすくする、色だけで状態を伝えない(テキストやアイコンも併用)。これらが StateKit に含まれていれば、新しい画面も自動的に同じアクセシビリティ基準を満たします。
ハイライト領域から始める高優先エリア(よく使われるリストや詳細画面)を1つずつ置き換えていきます。既存の状態を棚卸しして、主要な配置をいくつか決め、画面を触るたびにワンオフを共有コンポーネントに置き換える運用にします。Koder.ai で生成しているなら、StateKit を使う指示をデフォルトにするだけで逸脱を防げます。