空状態表示(Empty State)— 検索結果なし

表示・インジケーター 初級

このコンポーネントについて

空状態表示(Empty State)は、リストや検索結果にデータが存在しないときに表示するUIパターンです。何も表示しないよりも「なぜ空なのか」を伝えることで、ユーザーの迷いを減らします。

検索キーワードの見直しや次のアクションへの誘導に使われます。Webアプリの検索フォームや一覧画面に広く活用できます。

  • リアルタイムフィルタリング — テキスト入力のたびにリストを即時絞り込む
  • 空状態への切り替え — 一致するアイテムがゼロ件になった瞬間に空状態UIを表示する
  • 検索ワードの表示 — 「"〇〇" に一致する結果はありません」と入力値を反映して原因を明示する
  • クリア時の復帰 — 入力内容を消すと元のリストに自動で戻る
  • SVGアイコン — 外部ライブラリ不要のインラインSVGで虫眼鏡アイコンを表示する

実装のポイント・注意点

空状態の表示切り替えには hidden 属性を使います。hiddendisplay: none と同じ効果を持つHTML標準の属性で、CSSを書かずにJavaScriptから setAttribute('hidden', '') / removeAttribute('hidden') で制御できます。

入力値を空状態メッセージに埋め込む際は必ず textContent を使います。innerHTML に変数を直接結合するとXSS脆弱性につながるため注意してください。

検索ボックスに虫眼鏡アイコンを左内側に配置するには、position: relative の親要素の中で position: absolute のSVGを置き、インプットに padding-left を広めに設定してアイコンと入力文字が重ならないようにします。

空状態の虫眼鏡アイコンに「+」の線を加えることで「見つからない」ニュアンスを視覚的に補強しています。色を薄いグレー(#D0D7E0)にすることで主張を抑えつつ、「何もない状態」を自然に表現しています。

HTML・CSS・バニラJavaScriptのみで実装しており、フレームワーク不要でコピペすぐに動きます。

デモ

    サンプルソース

    3つのファイルを同じフォルダに保存し、index.html をブラウザで開くとすぐに動作確認できます。
    ファイル名:index.html / style.css / script.js — 保存時の文字コードは UTF-8 を指定してください(Shift-JISだと日本語が文字化けします)。

    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>空状態表示(Empty State)サンプル</title>
      <link rel="stylesheet" href="./style.css">
    </head>
    <body>
    
    <div class="es-wrapper">
      <!-- 検索ボックス -->
      <div class="es-search-box">
        <svg class="es-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
          <circle cx="11" cy="11" r="8" stroke="#9AA5B4" stroke-width="2"/>
          <path d="m21 21-4.35-4.35" stroke="#9AA5B4" stroke-width="2" stroke-linecap="round"/>
        </svg>
        <input
          type="text"
          id="esInput"
          class="es-input"
          placeholder="フルーツを検索..."
          autocomplete="off"
        >
      </div>
    
      <!-- リスト -->
      <ul class="es-list" id="esList"></ul>
    
      <!-- 空状態 -->
      <div class="es-empty" id="esEmpty" hidden>
        <svg width="48" height="48" viewBox="0 0 24 24" fill="none" aria-hidden="true">
          <circle cx="11" cy="11" r="8" stroke="#D0D7E0" stroke-width="1.5"/>
          <path d="m21 21-4.35-4.35" stroke="#D0D7E0" stroke-width="1.5" stroke-linecap="round"/>
          <path d="M8 11h6M11 8v6" stroke="#D0D7E0" stroke-width="1.5" stroke-linecap="round"/>
        </svg>
        <p class="es-empty-title" id="esEmptyTitle">検索結果がありません</p>
        <p class="es-empty-desc">別のキーワードで試してみてください。</p>
      </div>
    </div>
    
    <script src="./script.js"></script>
    </body>
    </html>
    /* ===== CSS変数(色の調整はここで)===== */
    :root {
      --es-border:      #E2E8F0;
      --es-focus:       #2B7FE8;
      --es-text:        #1A2332;
      --es-text-muted:  #5A6A7A;
      --es-icon:        #9AA5B4;
      --es-hover-bg:    #F8FAFC;
      --es-empty-title: #9AA5B4;
      --es-empty-desc:  #B0BAC9;
    }
    
    *, *::before, *::after { box-sizing: border-box; }
    body { font-family: sans-serif; padding: 24px; background: #F4F6F9; }
    
    /* ===== ラッパー ===== */
    .es-wrapper {
      background: #fff;
      border: 1px solid var(--es-border);
      border-radius: 12px;
      overflow: hidden;
      max-width: 360px;
    }
    
    /* ===== 検索ボックス ===== */
    .es-search-box {
      position: relative;
      padding: 12px 16px;
      border-bottom: 1px solid var(--es-border);
    }
    .es-search-icon {
      position: absolute;
      top: 50%;
      left: 28px;
      transform: translateY(-50%);
      pointer-events: none;
    }
    .es-input {
      width: 100%;
      padding: 8px 12px 8px 36px;
      font-size: 14px;
      color: var(--es-text);
      background: #F8FAFC;
      border: 1.5px solid var(--es-border);
      border-radius: 8px;
      outline: none;
      font-family: sans-serif;
      transition: border-color 0.15s;
    }
    .es-input:focus {
      border-color: var(--es-focus);
      background: #fff;
    }
    
    /* ===== リスト ===== */
    .es-list {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    .es-list li {
      padding: 12px 20px;
      font-size: 14px;
      color: var(--es-text);
      border-bottom: 1px solid var(--es-border);
      transition: background 0.1s;
    }
    .es-list li:last-child { border-bottom: none; }
    .es-list li:hover { background: var(--es-hover-bg); }
    
    /* ===== 空状態 ===== */
    .es-empty {
      padding: 40px 20px;
      text-align: center;
    }
    .es-empty-title {
      margin: 16px 0 8px;
      font-size: 14px;
      font-weight: 600;
      color: var(--es-empty-title);
    }
    .es-empty-desc {
      margin: 0;
      font-size: 13px;
      color: var(--es-empty-desc);
    }
    // フルーツリストのデータ
    var fruits = [
      'りんご', 'バナナ', 'いちご', 'みかん', 'ぶどう',
      'メロン', 'スイカ', 'もも', 'キウイ', 'マンゴー'
    ];
    
    var esInput      = document.getElementById('esInput');
    var esList       = document.getElementById('esList');
    var esEmpty      = document.getElementById('esEmpty');
    var esEmptyTitle = document.getElementById('esEmptyTitle');
    
    // 初期描画
    renderList(fruits);
    
    // 入力のたびにフィルタリング
    esInput.addEventListener('input', function() {
      var keyword = esInput.value.trim();
      var matched = fruits.filter(function(fruit) {
        return fruit.includes(keyword);
      });
    
      if (keyword === '' || matched.length > 0) {
        // キーワードなし or 一致あり → リスト表示
        renderList(keyword === '' ? fruits : matched);
        esList.removeAttribute('hidden');
        esEmpty.setAttribute('hidden', '');
      } else {
        // 一致なし → 空状態表示
        esList.setAttribute('hidden', '');
        // textContent を使って入力値を安全に埋め込む
        esEmptyTitle.textContent = '"' + keyword + '" に一致する結果はありません';
        esEmpty.removeAttribute('hidden');
      }
    });
    
    // リストアイテムを生成する
    function renderList(items) {
      esList.innerHTML = '';
      for (var i = 0; i < items.length; i++) {
        var li = document.createElement('li');
        li.textContent = items[i];
        esList.appendChild(li);
      }
    }

    AI用プロンプト

    以下のプロンプトをコピーしてAIに渡すと、同様のコンポーネントを生成できます。

    ChatGPTやClaudeにこのプロンプトを渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。ライブラリ指定や列数変更など、要件を追記して使うのがおすすめです。

    ※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。

    💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。

    # 空状態表示(Empty State)作成依頼
    
    ## 概要
    検索ボックスとリストを持つUIで、検索結果がゼロ件のときに空状態(Empty State)を表示するコンポーネントを作成してください。
    
    ## 要件
    - テキスト入力のたびにリストをリアルタイムで絞り込む
    - 一致するアイテムがゼロ件になったら空状態UIに切り替える
    - 空状態にはアイコン・「検索結果がありません」のメッセージ・サブテキストを表示する
    - 入力をクリアすると全件リストに戻る
    
    ## 技術仕様
    - HTML / CSS / バニラJavaScript で実装
    - 外部ライブラリ:なし(アイコンはインラインSVGを使用)
    - レスポンシブ対応:不要
    
    ## 動作詳細
    - リストのサンプルデータはフルーツ名10件(りんご・バナナ・いちご・みかん・ぶどう・メロン・スイカ・もも・キウイ・マンゴー)
    - 検索は部分一致
    - 空状態の文言は「"(入力値)" に一致する結果はありません」の形式
    - アイコンは虫眼鏡のインラインSVGを使用
    - 入力値を表示する際は innerHTML ではなく textContent を使用する(XSS対策)
    
    ## 出力形式
    HTML・CSS・JavaScriptを分けて出力してください。
    各ファイルは単独でコピー&ペーストして使えるよう記述してください。