1 分

Web とモバイルで一貫した読み込み・エラー・空状態を実現する方法

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

Web とモバイルで一貫した読み込み・エラー・空状態を実現する方法

なぜこれらの状態はすぐにバラバラになるのか

読み込み、エラー、空状態は、アプリが待機中のとき、何かが失敗したとき、または表示するものが何もないときにユーザーが見る画面(あるいは小さなUIブロック)です。ネットワークは遅くなるし、権限は拒否されるし、新しいアカウントはデータがゼロから始まります。これらは普通に起きることです。

乱雑になる理由は、たいてい後回しにして急いで追加されるからです。チームはまずハッピーパスを作り、残りの箇所にスピナー、赤いメッセージ、そして「項目がありません」プレースホルダを張り付けていきます。これを多数の画面で繰り返すと、使い回しのきかないワンオフの山になります。

高速な反復は状況をさらに悪化させます。UI が素早く作られると(AI生成のUI を含む)、メインレイアウトは数分で出せても、これらの状態は簡単に抜け落ちます。新しい画面ごとにスピナーのスタイルや文言(「Try again」 と「Retry」のような違い)、ボタンの配置がばらばらになります。最初に得た速度は、リリース直前の磨き作業に変わります。

状態の不一致はユーザーを混乱させ、チームの時間を奪います。空のリストが「結果なし」なのか「まだ読み込まれていない」なのか「アクセス権がない」のか判別できません。QA は細かなバリエーションの長い尻尾をテストしなければならず、Web とモバイルで挙動が違うためバグが混入します。

「乱雑」はしばしば次のように見えます:

  • ある画面では読み込みがアクションを隠すのに、別の画面では隠さない。
  • エラーがページをブロックする場合と、小さなテキストで出る場合が混在する。
  • 空状態で使うトーン、アイコン、次のステップが違う。
  • 同じ操作に対して Web とモバイルでラベルが異なる。

目標は単純です:Web とモバイルで共通のアプローチを作ること。もしチームが素早く機能を生成する(たとえば Koder.ai のようなプラットフォームを使う)なら、共有の状態パターンがさらに重要になります。新しい画面がデフォルトで一貫した状態を持って始まるからです。

最初に標準化すべき5つの状態タイプ

ほとんどのアプリは同じ問題箇所を繰り返します:リスト、詳細ページ、フォーム、ダッシュボード。ここでスピナーやバナー、 "何もない" メッセージが増殖します。

まずは5つの状態タイプに名前を付けて標準化しましょう:

  • 初回読み込み(Initial loading):画面を最初に開いたときで、まだデータがない状態。
  • 更新中 / リフレッシュ(Refreshing / updating):既にデータはあるが、新しいデータを取得している状態。
  • 空のベースライン(Empty baseline):本当に何もない状態(新しいアカウント、ワークスペース、新規項目がないなど)。
  • ゼロ結果(Zero results):データ自体は存在するが、フィルタや検索で結果がない状態。
  • エラー(Error):リクエストが失敗した、権限でアクセスできない、何かが壊れた場合。

ふたつの特殊ケースは別ルールに値します:

  • オフライン(Offline):可能な限りキャッシュされたコンテンツを表示し、「オフラインです」と明示する。
  • 遅いネットワーク(Slow network):すぐにエラーを出さない。短い遅延の後で「まだ読み込み中です…」に切り替えるなどの扱いにする。

画面やプラットフォームを越えて、構造を一貫させてください:状態が現れる場所、アイコンのスタイル、トーン、そしてデフォルトアクション(再試行、リフレッシュ、フィルタ解除、作成)。変えられるのは文脈だけです:画面名やユーザーの言葉を使った一文など。

例:Web とモバイル両方で「プロジェクト」の一覧を生成するなら、ゼロ結果のパターンを共有させます。アクションラベルはプラットフォームに合わせて変えてもよい(「フィルタをクリア」 vs 「リセット」など)。

ワンオフをやめて小さな StateKit を作る

画面ごとにスピナー、エラーカード、空メッセージを考えると、微妙に違うバージョンが山になります。最速の対策は、どの機能にも差し込める小さな "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つで充分です。

テンプレートを使って即興をやめさせる

いくつかの定型パターンで大半の状況をカバーできます。小さい画面にも収まるよう短く保ってください:

  • ネットワーク:「接続できません」+「インターネットを確認して再試行してください。」+ アクション:「再試行」
  • タイムアウト:「予想より時間がかかっています」+「リクエストがタイムアウトしました。」+ アクション:「もう一度試す」
  • 権限:「権限が必要です」+「続行するにはアクセスを許可してください。」+ アクション:「設定を開く」
  • 未検出:「何もありません」+「この項目は削除された可能性があります。」+ アクション:「戻る」
  • 検証:「1つ修正してください」+「有効なメールアドレスを追加してください。」+ アクション:「保存」

「Something went wrong」のような曖昧な文言は避けてください。本当に原因が不明な場合は、分かっていることとユーザーが今できることを示します。例えば「プロジェクトを読み込めませんでした」は単に「エラー」と表示するより親切です。

次のアクションを予測可能にする

ルールを一つ設けてください:すべてのエラーと空状態には次の一手を提示すること。

  • ユーザーが回復できるなら「再試行」や「リフレッシュ」を提供する。
  • フィルタが原因で空になっているなら「フィルタをクリア」を提案する。
  • 権限でブロックされているなら、どこで変更するかを正確に伝える。

AI生成のUIでは、画面が素早く出る分テンプレートで文言を統一しておくことが、リリース前の数十件のワンオフ修正を防ぎます。

アクションを予測可能にする:再試行、リフレッシュ、次の手

Ship with fewer polish fixes
Draft a checklist for loading, error, empty, offline, and slow-network behavior and apply it app-wide.

状態画面がページごとに違うアクションを示すと、ユーザーは躊躇します。チームはリリース直前にボタンや文言をいじることになりがちです。

各状態にどのアクションがふさわしいかを決め、配置とラベルを統一してください。ほとんどの画面は主要アクションを1つ持つべきです。二つ目を置くなら主経路を支援するもので、競合しないようにします。

スケールする小さなアクションマップ

許容するアクションは絞りましょう:

  • 読み込み(Loading):通常アクションは不要(長時間のユーザー主導タスクなら「キャンセル」を任意で)。
  • 空(Empty):ユーザーが追加できるなら「作成」、フィルタが原因なら「フィルタを調整」。
  • エラー(Error):一時的な失敗は「再試行」、古いデータなら「リフレッシュ」、ブロックされたワークフローでは「サポートに連絡」を最後の手段に。
  • 変更のない成功:「戻る」や「完了」。

地味なボタンは機能です。UI を馴染ませ、生成された画面が一貫性を保つのに役立ちます。

再試行とエラー詳細のルール

再試行は現実的に成功する見込みがある場合にのみ表示します(タイムアウト、断続的なネットワーク、5xx など)。連打を防ぐために短いデバウンスを入れ、再試行中はボタンをローディング状態に切り替えます。

繰り返し失敗したら、主要ボタンはそのままにして二次的なヘルプ(「接続を確認」や「後でもう一度」など)を強化します。失敗が二回起きたからといってレイアウト全体を変えるのは避けてください。

エラーの詳細はユーザーが取れる行動を示す短い理由を出します(「セッションが切れました。再ログインしてください。」など)。技術的な詳細はデフォルトで隠し、必要なら一貫した「詳細」表示で見せます。

例:モバイルで「プロジェクト」一覧の読み込みに失敗した場合、両プラットフォームは同じ主要「再試行」アクションを表示し、再試行中は無効化し、2回失敗したら接続ヒントを小さく表示する、という挙動にします。レイアウトを変更しないことが重要です。

チームの速度を落とさずにシステムを導入する

状態の一貫性はリデザインではなく小さなプロダクト変更として扱ってください。段階的に進めて導入しやすくします。

まずは既存の状況を軽くスナップショットします。完璧を目指さず、よく見られるバリエーションを拾います:スピナーとスケルトン、フルページエラーとバナー、異なるトーンの "No results" など。

実用的なローアウト計画:

  • Web とモバイルで重要な15〜30画面を棚卸し、最もよく見るパターンをグループ化する。
  • まずサポートする正準的な配置を3〜4つ選ぶ(フルページ、セクション、インライン、トースト)。
  • Web とモバイルで同じ props と名前を持つマッチするコンポーネントを作る。
  • 新しい画面がキットから選ぶだけで済むよう短い利用ルールを書く。
  • 製品領域をひとつずつ(検索、受信箱、請求など)置き換えて、安全にリリースする。

コンポーネントができれば、時間を節約するのは論争を減らす短いルールセットです:ページ全体をブロックする状態とカードだけを覆う状態の違い、必須のアクションは何か、などを明確にします。

ルールは短く:

  • すべてのエラーは明確な次の一手を示す(再試行、リフレッシュ、サポートへの連絡)。
  • 空状態は主要なアクションを1つ提供する(作成、インポート、探索)。
  • 読み込み状態はデータ到着時にレイアウトが跳ねない。

Koder.ai のような AI UI ジェネレータを使う場合、これらのルールは早く成果を出します。StateKit を指定してプロンプトすると、React の Web と Flutter のモバイルで一致する画面が得られ、後処理が減ります。

遅い磨き作業を生む一般的なミス

後回しの磨き作業は、状態処理がワンオフで作られたときに起きます。画面は「動く」けれど、何かが遅い、失敗する、データがないときの体験が毎回違うと感じられます。

停滞しているように感じる読み込み

スケルトンは有効ですが、長時間そのままにしておくとアプリが固まった印象を与えます。よくある原因は、信号のない遅い呼び出しでフルスケルトンを表示し続け、進行中であることが分からないことです。

時間を区切ってください:短い遅延の後で軽めの「まだ読み込み中です…」メッセージに切り替えるか、進捗が出せるならそれを表示します。

画面ごとの文言のズレ

チームは同じ問題でも毎回新しいメッセージを書きがちです。「Something went wrong」「Unable to fetch」「Network error」などは同じケースを指していても不一致に見えますし、サポートが難しくなります。

エラータイプごとに1つのラベルを決め、Web とモバイルで同じトーンと詳細レベルで再利用してください。

空状態とエラーと未読み込みの混同

別の典型的なミスは、データが完全に読み込まれる前に空状態を表示したり、実際は失敗なのに「項目なし」を表示することです。ユーザーは間違った行動(コンテンツを追加するなど)を取ってしまいます。

決定順を明確にします:まず読み込み、次に失敗ならエラー、最後に成功してデータが空なら空状態を表示します。

アクションがない、あるいは過剰

回復手段のないエラーは行き詰まりを作ります。逆に、三つのボタンが並んで主張し合うこともよくあります。

絞ってください:

  • エラーには主要アクションを1つ(通常は「再試行」)にする。
  • 空状態には明確な次の一手を1つ提供する。
  • 二次アクションは本当に必要なときだけ。

プラットフォーム間の視覚的不一致

小さな差異(アイコンスタイル、余白、ボタンの形状)が積み重なると違和感になります。AI生成のUI はプロンプトが画面ごとに違うとここでズレが生じます。

状態コンポーネントの余白、アイコンセット、レイアウトを固定して、どの新しい画面も同じ構造を継承するようにします。

読み込み挙動とアクセシビリティに関する実践的ルール

Keep web and mobile aligned
Create matching React web and Flutter mobile screens from one chat, with the same state rules.

Web とモバイルで一貫した状態処理をしたければ、「地味な」ルールを明文化してください。多くの後回しの磨き作業は、画面ごとに読み込み挙動、タイムアウト、ラベルを勝手に決めたことが原因です。

速く感じさせる読み込みルール(実際は遅くても)

フルページ読み込みではデフォルトを一つ決めます:コンテンツが重い画面(リスト、カード、ダッシュボード)はスケルトン、レイアウトが不明な短い待ち時間はスピナーにします。

UI が黙ったまま吊るされないようにタイムアウト閾値を設けます。読み込みが約8〜10秒を越えたら、明確なメッセージと「再試行」のような可視アクションに切り替えます。

部分的な読み込みでは画面を真っ白にしないでください。既にあるコンテンツは表示したまま、更新中のセクション付近に小さな進捗表示(ヘッダの細いバーやインラインスピナーなど)を出します。

キャッシュされたデータは「古いが使える」扱いにします。キャッシュを即表示して、さりげない「更新中…」インジケータを出し、データが変わる可能性を示します。

オフラインは別状態です。はっきりと書き、何が利用可能かを示します。例:「オフラインです。保存済みプロジェクトは表示できますが、同期は保留中です。」など。主要な次の一手は「再試行」や「保存済み項目を開く」など1つに絞ります。

どこでも適用できるアクセシビリティの基本

プラットフォーム横断で次を統一してください:

  • フォーカス順:状態が出たらメッセージと主要アクションにフォーカスを移す。
  • スクリーンリーダーラベル:読み込みとエラーを短く明確にアナウンスする。
  • タップターゲット:モバイルでボタンは押しやすく、インラインリンクは小さくしない。
  • 動き:スピナーは控えめにし、アニメーションだけで情報を伝えない。
  • 色:色だけで状態を示さず、テキストやアイコンも併用する。

Koder.ai のようなツールで UI を生成する場合、これらのルールを StateKit に焼き込んでおけば、新しい画面はデフォルトで一貫性を保ちます。

例:1つの機能、3つの状態、2つのプラットフォーム

単純な CRM を想像してください。Contacts(連絡先)リスト画面と Contact(連絡先)詳細画面があります。状態をワンオフで扱うと、Web とモバイルはすぐにズレます。小さなシステムがあれば、UI が素早く作られても整合性を保てます。

初回の空状態(Contacts リスト): ユーザーが Contacts を開いて何もない状態。Web とモバイルでタイトルは同じ(「連絡先」)、空メッセージは理由を説明(「まだ連絡先がありません」)、次の一手は一つ(「最初の連絡先を追加」)。セットアップが必要なら(メールボックス接続や CSV インポートなど)、空状態はその具体的な手順を示します。

遅いネットワークでの読み込み: ユーザーが Contact 詳細ページを開くとき、両プラットフォームは最終ページ構造に一致するスケルトン(ヘッダ、主要フィールド、メモ)を表示します。戻るボタンは機能し、ページタイトルは見えるようにし、ランダムに別の場所にスピナーが出るのは避けます。

サーバーエラー: 詳細取得が失敗した場合、Web とモバイルで同じパターン:短い見出し、一文の説明、主要アクション(「再試行」)。再試行が続けて失敗したら「Contacts に戻る」といった二次オプションを提示して、ユーザーを行き詰まらせないようにします。

一貫している点はシンプルです:

  • 配置:メッセージはコンテンツ領域、アクションは一目でわかる場所。
  • コピー:トーンとボタンラベル(再試行、連絡先を追加など)は同じ。
  • 挙動:スケルトンのトリガーや再試行ルールは同じ。
  • 安全性:読み込み中は重複アクションを無効化。

出荷前の簡単チェックリスト

Make consistency a team habit
Bring your team into one workspace so state naming, triggers, and actions stay consistent.

遅い接続、新規アカウント、または不安定な API に遭遇するまではビルドが「完了」に見えることがよくあります。このチェックリストで最後の詰めを見つけ、QA を宝探しにしないでください。

UI の一貫性チェック

リスト画面から始めてください。リストは増殖しやすいからです。代表的なリストを3つ(検索結果、保存済み項目、最近のアクティビティ)選び、すべて同じ空状態構造を使っているか確認します:明確なタイトル、一つの説明文、一つの主要アクション。

データがまだ読み込まれている間に空状態が表示されないことを確認してください。"何もない" が一瞬表示されてからコンテンツに差し替わるとユーザーの信頼が落ちます。

読み込みインジケータの一貫性もチェック:サイズ、配置、最小表示時間が妥当で、ちらつかないこと。Web がトップバーのスピナーで、モバイルが同じ画面でフルスクリーンスケルトンを出すのは別物に感じられます。

エラーと QA チェック

エラーは常に「今どうする?」に答えるべきです。すべてのエラーに次の一手(再試行、リフレッシュ、フィルタ変更、再ログイン、サポートへの連絡)を用意してください。

リリース直前の短い確認事項:

  • 空状態:リスト画面で同じレイアウト、アイコンスタイル、トーンを使っているか。
  • 読み込み:空状態に早すぎに置き換わっていないか。インジケータは Web とモバイルで一致しているか。
  • エラー:どのエラー画面やトーストにも明確な次のアクションがあるか。
  • コピー規則:プラットフォームで文言パターンが一致しているか(タイトル、句読点、ボタン動詞)。
  • QA スクリプト:読み込み、空、エラー状態を確実にトリガーする短く再現可能な手順があるか。

Koder.ai のような AI ビルダーを使うなら、画面は早く生成できますが一貫性は共有キットとコピー規則に依存します。

アプリが成長しても一貫性を保つための次のステップ

一貫性は日常業務の一部にすると楽です。一度の掃除ではなく、新しい画面が "合っているか" を忘れずにチェックする文化を作ります。新しい画面は読み込み状態、空状態(該当する場合)、そして明確なアクションを伴うエラー状態がなければ完成ではないと定義しましょう。

ルールは軽く、でも書き残してください。短いドキュメントに数枚のスクリーンショットと正確な文言パターンを書く程度で十分です。新しいバリアントは例外扱いにして、誰かが新しい状態デザインを提案したら本当に新ケースか、それとも既存キットに当てはまるかを問う習慣をつけます。

多くの画面をリファクタするなら、リスクを下げるため段階的にやります:1つのフローずつ更新し、Web とモバイルで検証してから続ける。Koder.ai ではスナップショットとロールバックが大きな変更を安全にするのに役立ち、Planning Mode は共有 StateKit を定義して新しく生成される画面が最初からデフォルトに従うようにできます。

進め方の実践例

今週、状態の問題で遅れが出ている領域を1つ選んでください(多くは検索、オンボーディング、アクティビティフィードです)。その後:

  • その領域の受け入れチェックリストに状態要件を追加する。
  • ワンオフの状態を共有コンポーネントで置き換える。
  • 良い例と悪い例をそれぞれ2〜3例ドキュメントに記録する。
  • クロスプラットフォームで短いレビューを行う(意味が同じで、アクションが同じ、レイアウトが似ていること)。
  • 次のスプリントで発生する UI 磨きチケットの数を追跡して比較する。

これが機能している明確なサインは、"再試行を追加"、"空状態が変"、"読み込みスピナーがページをブロックしている" といった小さなチケットが減ることです。

所有権を明確にする

状態標準の責任者を一人決めます(デザイナーかテックリード、あるいは両方)。すべてを承認する必要はありませんが、キットが徐々に似て非なるバリアントに分裂し、見た目が似て挙動が違うものになって時間を食う事態を防ぐ役割は果たしてください。

よくある質問

What state types should we standardize first?

最初に名前を決めて共通で使う少数の状態をそろえます:初回読み込み、更新中(リフレッシュ)、空のベースライン(新しいアカウントなど)、ゼロ結果(フィルタや検索の結果がない)、エラー。オフラインと遅いネットワークには明確なルールを追加して、ランダムなエラー扱いにしないでください。チームが名前とトリガーに合意すれば、画面やプラットフォーム間でUIの振る舞いが予測可能になります。

How do we avoid dozens of one-off spinners and empty screens?

小さな StateKit を作って、Loading、Error、Empty の3つの再利用可能なパーツを用意します。各コンポーネントは共通の入力(タイトル、短いメッセージ、主要アクション1つ、任意の詳細)で動くようにして、画面ごとに新しいUIを作らなくて済むようにします。既定バリアントを最も使いやすくして、ワンオフが生まれにくくします。

How do we stop empty states from showing before data is actually loaded?

単純な判断順を使ってください:リクエストが終わるまでは読み込みを表示し、失敗したらエラーを表示し、成功してデータが空の場合にのみ空状態を表示します。これで "No items" がデータ到着前に一瞬表示される、といったバグを防げます。QA にとっても挙動が一貫するので確認しやすくなります。

What should the primary action be on loading, error, and empty states?

状態ごとにデフォルトのアクションを1つ決め、それを画面全体で同じラベルと配置で再利用します。エラーには通常「再試行」、空のベースラインには「作成(または次のセットアップ手順)」、ゼロ結果には「フィルタをクリア」を割り当てます。主要なアクションが予測可能だと、ユーザーの操作が速くなり、チームの議論も減ります。

How do we keep loading/error/empty copy consistent across the app?

共有テンプレートを作ります:状況を名前で示す短いタイトル、平易な言葉での一文説明、そして明確な次の一手を示すアクション。例えば「We couldn’t load your projects」よりも「プロジェクトを読み込めませんでした」のように具体的に。トーンは落ち着いて統一してください。これで Web とモバイルで一貫した印象になります。

What’s the right way to handle offline mode?

オフラインは汎用のエラー扱いにしないでください。キャッシュ済みの内容があれば表示し、「オフラインです」と明示し、何が今できるかを伝えます。次の一手は「再試行」のように一つに絞るとユーザーが迷いません。

How should we handle slow networks without making the app feel broken?

短い待ち時間のあとにすぐエラーを表示しないでください。一定の閾値を設け、境界を越えたら「まだ読み込み中です…」などの明示的なメッセージに切り替え、目に見えるアクション(「再試行」など)を出します。これでネットワークが遅くてもアプリが壊れている印象を与えにくくなります。

Where should state UI appear: inline, section, or full-page?

3つのサイズバリアントを用意します:カードやセクション内の小さい inline、標準のセクション、ページ全体を覆うフルページ。どの場面でどれを使うかを事前に定義しておけば、画面ごとに勝手に作られることを防げます。間隔、アイコンスタイル、ボタンスタイルは全バリアントで揃えておくのが肝心です。

What are the must-have accessibility rules for these states?

いくつかの基本ルールを組み込みます:状態が出たらメッセージと主要アクションにフォーカスを移す、読み込みやエラーは短く明確な文で通知する、モバイルでボタンは押しやすくする、色だけで状態を伝えない(テキストやアイコンも併用)。これらが StateKit に含まれていれば、新しい画面も自動的に同じアクセシビリティ基準を満たします。

How can we roll this out without slowing down feature delivery?

ハイライト領域から始める高優先エリア(よく使われるリストや詳細画面)を1つずつ置き換えていきます。既存の状態を棚卸しして、主要な配置をいくつか決め、画面を触るたびにワンオフを共有コンポーネントに置き換える運用にします。Koder.ai で生成しているなら、StateKit を使う指示をデフォルトにするだけで逸脱を防げます。

Related posts