空状態 · 空状態

親しみのあるイラスト付き空状態(受信箱が空)

保存した一覧を見る 0 →

このブラウザー内に保存します。別の端末との同期はなく、サイトデータを消すと保存も消えます。

印刷時は見本を静止画にし、プロンプトと解説を出力します。PDF保存はブラウザーの印刷画面から選べます。

Vibehaus · デザイン資料
https://vibe.itlibra.com/design/playful-empty-inbox/
完全無料・著作権フリー(CC0)· 料金¥0 · 商用利用・改変自由

プレビュー

画面に合わせる

指定幅で実際に組み替えて表示します。表示領域より広い場合は、この枠の中で横へスクロールできます。

vibe.itlibra.com/preview/playful-empty-inbox/

AIへの指示文

SaaS の受信箱が空のときの空状態画面を作ってください。

要件:
- 配色: ベース #fdf9f0 + 主色 #2a221a + アクセント #6c8aff(クール)+ 補助色 #ffb84a。
- 中央配置、上下に十分な余白。
- イラストはインライン SVG で大きめ(300px幅程度)。封筒・ハート・葉などの簡潔なシンボル。Stroke のみで線画。
- イラスト下に大きな見出し「受信箱は空です」。
- 短いコピー(30字程度)「お知らせは届いたらここに表示されます」。
- プライマリ CTA「お知らせ設定を確認する」。
- 補助リンク「詳しくは ヘルプ」。
- 上部に小さな階層案内「受信箱 / すべて」と、すべて・未読・自分宛てのタブ。選択とパネルの関連、左右キー・Home・Endの操作を実装する。
- フォント: 見出しは丸ゴ系、本文は Inter + Noto Sans JP。
- フルレスポンシブ。スマホでは余白を縮めてコンパクトに。

バリエーション

ダーク版

背景を暗色(#0e0e10)、テキストはオフホワイトに。
イラストの線色をアクセントブルー、補助色を発光イエローに。

検索結果ゼロ版

コピーを「『○○』に一致するメッセージはありません」に変更し、
検索キーワードのクリアと、フィルター解除の2つのリンクを下部に。

このプレビューのコード

HTML・CSS・JavaScriptの1ファイルです。外部依存・広告・解析コードはありません。3つの空状態と、お知らせ設定の選択確認を試せる架空の受信箱です。メール受信・既読変更・削除・通知配信・OS設定の変更・送信・保存は行いません。フォルダーのリンクは説明へ移動します。

CC0 · 改変・商用利用自由 · revision cc3989e7a4d1

コードを表示
<!doctype html>
<!-- Vibehaus · CC0 · 架空の受信箱。CSS・埋込みSVGのみ。外部依存・受信・送信・永続保存なし。 -->
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="robots" content="noindex" />
    <title>親しみのあるイラスト付き空状態(受信箱が空) — Vibehaus プレビュー</title>
    <style>
      :root {
        --bg: #fdf9f0;
        --bg-deep: #f5edd8;
        --ink: #2a221a;
        --muted: #6e5e47;
        --quiet: #a89a7e;
        --accent: #6c8aff;
        --accent-deep: #4a68d9;
        --warm: #ffb84a;
        --soft: #ffe9b8;
      }
      * { box-sizing: border-box; }
      html, body { margin: 0; padding: 0; }
      body {
        font-family: "Inter", "Noto Sans JP", "Hiragino Maru Gothic ProN",
          "Hiragino Kaku Gothic ProN", "Yu Gothic", sans-serif;
        background: var(--bg);
        color: var(--ink);
        line-height: 1.85;
        -webkit-font-smoothing: antialiased;
        min-height: 100dvh;
        position: relative;
      }

      .app {
        display: grid;
        grid-template-columns: 220px 1fr;
        min-height: 100dvh;
      }
      @media (max-width: 720px) {
        .app { grid-template-columns: 1fr; }
        aside.side { display: none; }
      }

      /* Side nav (just to give context that this is a real product UI) */
      aside.side {
        background: #fff;
        border-right: 1px solid var(--soft);
        padding: 1.5rem 1rem;
      }
      .side .brand {
        display: inline-flex;
        align-items: center;
        gap: 0.5rem;
        font-weight: 800;
        font-size: 1rem;
        letter-spacing: -0.01em;
        margin-bottom: 1.5rem;
      }
      .side .brand::before {
        content: "";
        width: 22px;
        height: 22px;
        border-radius: 7px;
        background: linear-gradient(135deg, var(--accent), var(--warm));
      }
      .side ul {
        list-style: none;
        margin: 0;
        padding: 0;
      }
      .side li a {
        display: flex;
        align-items: center;
        gap: 0.55rem;
        padding: 0.55rem 0.7rem;
        color: var(--muted);
        text-decoration: none;
        border-radius: 8px;
        font-size: 0.92rem;
      }
      .side li a:hover { background: var(--soft); color: var(--ink); }
      .side li a.active { background: var(--ink); color: var(--bg); }
      .side li a .ic {
        display: inline-grid;
        place-items: center;
        width: 18px; height: 18px;
      }
      .side li a .ic svg { width: 16px; height: 16px; }

      /* Main */
      main.main {
        padding: clamp(1.25rem, 4vw, 2.5rem);
        display: flex;
        flex-direction: column;
        min-height: 100dvh;
      }
      .crumbs {
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 0.7rem;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        color: var(--quiet);
        margin: 0 0 1rem;
      }
      .crumbs span { color: var(--ink); }
      .toolbar {
        display: flex;
        gap: 0.75rem;
        padding-bottom: 1rem;
        border-bottom: 1px solid var(--soft);
        margin-bottom: 1rem;
      }
      .toolbar .tab {
        padding: 0.45rem 0.85rem;
        border-radius: 999px;
        background: transparent;
        border: 0;
        color: var(--muted);
        font-family: inherit;
        font-size: 0.86rem;
        cursor: pointer;
      }
      .toolbar .tab.active { background: var(--ink); color: var(--bg); font-weight: 600; }

      /* Empty state */
      .empty {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        text-align: center;
        padding: 3rem 1rem;
        max-width: 520px;
        margin: 0 auto;
      }
      .illust {
        width: min(280px, 70%);
        margin-bottom: 1.75rem;
      }
      .empty h1 {
        margin: 0 0 0.6rem;
        font-size: clamp(1.6rem, 3vw, 2.1rem);
        font-weight: 800;
        line-height: 1.3;
        letter-spacing: 0;
      }
      .empty h1 :lang(ja) { letter-spacing: 0; line-height: 1.4; }
      .empty p {
        margin: 0 0 1.75rem;
        color: var(--muted);
        font-size: 1rem;
        line-height: 1.85;
        max-width: 38ch;
      }
      .ctas {
        display: inline-flex;
        flex-wrap: wrap;
        gap: 0.85rem;
        justify-content: center;
        align-items: center;
      }
      .btn {
        display: inline-flex;
        align-items: center;
        gap: 0.45rem;
        padding: 0.75rem 1.4rem;
        border-radius: 999px;
        font-size: 0.92rem;
        font-weight: 600;
        text-decoration: none;
        cursor: pointer;
        border: 0;
        font-family: inherit;
        transition: transform 0.15s, background 0.2s;
      }
      .btn.primary {
        background: var(--ink);
        color: var(--bg);
      }
      .btn.primary:hover { transform: translateY(-2px); background: var(--accent-deep); color: var(--bg); }
      .btn.primary .arr { transition: transform 0.2s; }
      .btn.primary:hover .arr { transform: translateX(3px); }
      .btn.text {
        background: transparent;
        color: var(--accent-deep);
        text-decoration: underline;
        text-decoration-thickness: 1.5px;
        text-underline-offset: 4px;
        padding: 0.75rem 0.5rem;
      }
      .btn.text:hover { color: var(--ink); }

[hidden]{display:none!important}button,input{font-family:inherit}button:disabled{cursor:default;opacity:.65}:focus-visible{outline:3px solid currentColor;outline-offset:3px}
button,a,summary{min-height:44px;min-width:44px}button{cursor:pointer}a,section{scroll-margin-top:1rem}.demo-caption,.demo-status{font-size:13px;line-height:1.8;letter-spacing:0;overflow-wrap:anywhere}.demo-status{border-left:3px solid currentColor;padding-left:.75rem}
.demo-note{font-size:13px;line-height:1.8;letter-spacing:0;margin-top:2rem;border-top:1px solid currentColor;padding-top:1rem;overflow-wrap:anywhere}.demo-note h2{font-size:18px;line-height:1.5;letter-spacing:0}.demo-note h3{font-size:15px;line-height:1.5;letter-spacing:0}.demo-note a{display:inline-flex;align-items:center;min-height:44px}.demo-note p{letter-spacing:0}.demo-tabs{flex-wrap:wrap;gap:.35rem}.demo-tabs button{font:inherit;letter-spacing:0;min-height:44px;min-width:44px}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}

:root{--quiet:#75664c;--accent-deep:#3d56b5}.toolbar .tab{min-height:44px;min-width:44px}.crumbs{letter-spacing:0;font-family:inherit}.main{min-width:0}.app{grid-template-columns:220px minmax(0,1fr)}.empty{width:100%;flex:none;min-height:450px;padding:2rem 1rem}.empty h1{overflow-wrap:anywhere}.ctas{max-width:100%}.btn{max-width:100%;overflow-wrap:anywhere}.folders>summary{font-size:13px;min-height:44px;padding:.4rem;cursor:pointer;color:var(--muted)}.side li a{min-height:44px}.side .brand{letter-spacing:0}.side .ic{flex:none}.setting{display:flex;align-items:center;gap:.6rem;min-height:44px;font-size:15px}.setting input{width:20px;height:20px;accent-color:var(--ink)}.setting-actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1rem}.demo-note .btn{font-size:13px}.demo-note .demo-status{margin-top:1rem}.demo-note{max-width:900px;width:100%}.btn:focus-visible,.tab:focus-visible{outline:3px solid var(--ink);outline-offset:3px}
@media(max-width:720px){.app{grid-template-columns:1fr}aside.side{display:block;padding:.75rem 1rem;border-right:0;border-bottom:1px solid var(--soft)}.side .brand{margin-bottom:.5rem}.empty{min-height:0;padding:1.5rem .5rem 2rem}.illust{width:min(240px,75%)}.side ul{display:flex;flex-wrap:wrap}.side li{flex:1 1 120px}}
</style>
  </head>
  <body>
    <div class="app">
      <aside class="side">
        <div class="brand">Postroom</div>
        <details class="folders" open><summary>フォルダー案内(見本)</summary><nav aria-label="フォルダーの説明"><ul>
          <li><a class="active" href="#demo-inbox">
            <span class="ic" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="6" width="16" height="12" rx="2"/><polyline points="4,7 12,13 20,7"/></svg></span>
            受信箱
          </a></li>
          <li><a href="#demo-done">
            <span class="ic" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><polyline points="7,12 11,16 17,9"/></svg></span>
            完了
          </a></li>
          <li><a href="#demo-snoozed">
            <span class="ic" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><polyline points="12,7 12,12 16,14"/></svg></span>
            あとで
          </a></li>
          <li><a href="#demo-saved">
            <span class="ic" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><polygon points="6,4 18,4 18,20 12,16 6,20"/></svg></span>
            保存済み
          </a></li>
          <li><a href="#demo-trash">
            <span class="ic" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="7" width="12" height="13" rx="1"/><path d="M4 7h16M9 4h6M10 10v7M14 10v7"/></svg></span>
            ゴミ箱
          </a></li>
        </ul></nav></details>
      </aside>

      <main class="main">
        <p class="crumbs">受信箱 / <span data-filter-label>すべて</span></p>

        <div class="toolbar demo-tabs" role="tablist" aria-label="受信箱の表示見本"><button class="tab" type="button" id="inbox-tab-0" role="tab" aria-selected="true" aria-controls="inbox-panel" tabindex="0" data-js-control disabled>すべて</button><button class="tab" type="button" id="inbox-tab-1" role="tab" aria-selected="false" aria-controls="inbox-panel" tabindex="-1" data-js-control disabled>未読</button><button class="tab" type="button" id="inbox-tab-2" role="tab" aria-selected="false" aria-controls="inbox-panel" tabindex="-1" data-js-control disabled>自分宛て</button></div>

        <div class="empty" id="inbox-panel" role="tabpanel" aria-labelledby="inbox-tab-0" tabindex="0">
          <svg class="illust" viewBox="0 0 280 200" fill="none" aria-hidden="true">
            <!-- Soft cloud-ish base shadow -->
            <ellipse cx="140" cy="178" rx="100" ry="10" fill="#fff" opacity="0.0"/>
            <ellipse cx="140" cy="178" rx="80" ry="6" fill="#0a0a0a" opacity="0.06"/>
            <!-- Envelope -->
            <rect x="60" y="58" width="160" height="100" rx="8" stroke="#2a221a" stroke-width="2.5" fill="#fff"/>
            <path d="M60 64 L 140 124 L 220 64" stroke="#2a221a" stroke-width="2.5" fill="none" stroke-linejoin="round"/>
            <path d="M60 158 L 124 110 M220 158 L 156 110" stroke="#2a221a" stroke-width="2.5" stroke-linecap="round"/>
            <!-- Sparkles -->
            <g stroke="#6c8aff" stroke-width="2" stroke-linecap="round">
              <path d="M28 38 V 50 M 22 44 H 34"/>
              <path d="M252 90 V 100 M 247 95 H 257"/>
              <path d="M44 130 V 140 M 39 135 H 49"/>
            </g>
            <!-- Heart with wing -->
            <g transform="translate(190 28)">
              <path d="M 0 8 C 0 2, 8 -2, 12 4 C 16 -2, 24 2, 24 8 C 24 16, 12 24, 12 24 S 0 16, 0 8 Z" fill="#ffb84a" stroke="#2a221a" stroke-width="2" stroke-linejoin="round"/>
            </g>
            <!-- Tiny moon -->
            <g transform="translate(38 78)">
              <circle cx="10" cy="10" r="10" fill="#ffe9b8" stroke="#2a221a" stroke-width="2"/>
              <circle cx="14" cy="8" r="8" fill="#fdf9f0" stroke="none"/>
            </g>
            <!-- Floor squiggle -->
            <path d="M 90 178 Q 100 172, 110 178 Q 120 184, 130 178 Q 140 172, 150 178 Q 160 184, 170 178 Q 180 172, 190 178" stroke="#2a221a" stroke-width="1.6" fill="none" stroke-linecap="round" opacity="0.4"/>
          </svg>

          <h1 data-empty-heading>受信箱は空です</h1>
          <p data-empty-description>お知らせや返信が届くと、ここに表示されます。設定の見本から、案内の続きも確認できます。</p>
          <div class="ctas">
            <a class="btn primary" href="#demo-settings">お知らせ設定を確認する <span class="arr">→</span></a>
            <a class="btn text" href="#demo-help">詳しくはヘルプ</a>
          </div>
        </div>
      <aside class="demo-note"><h2>この見本について</h2><p>架空の受信箱です。タブと説明への移動、お知らせ設定の選択を試せます。受信・既読の変更・招待・配信・外部送信・永続保存は行いません。配布HTMLはCC0・利用料¥0です。</p><section id="demo-settings" tabindex="-1"><h3>お知らせ設定の見本</h3><p>表示と選択だけを確認する見本です。端末の通知設定は変更しません。</p><form method="dialog"><label class="setting"><input id="setting-replies" type="checkbox" checked data-js-control disabled>返信のお知らせ</label><label class="setting"><input id="setting-mentions" type="checkbox" checked data-js-control disabled>自分宛てのお知らせ</label><div class="setting-actions"><button class="btn primary" type="button" data-settings-confirm data-js-control disabled>選択内容を確認(見本)</button><button class="btn text" type="button" data-settings-reset data-js-control disabled>初期値に戻す</button></div><p class="demo-status" data-settings-status role="status" hidden></p></form></section><section id="demo-help" tabindex="-1"><h3>ヘルプの見本</h3><p>空の状態から案内へ移動するリンク位置の見本です。サポートへの接続や問い合わせ送信は行いません。</p></section><section id="demo-inbox" tabindex="-1"><h3>受信箱の案内</h3><p>フォルダーへの入口を示す表示見本です。実際のメールの受信・移動・削除・保存は行いません。</p></section><section id="demo-done" tabindex="-1"><h3>完了の案内</h3><p>フォルダーへの入口を示す表示見本です。実際のメールの受信・移動・削除・保存は行いません。</p></section><section id="demo-snoozed" tabindex="-1"><h3>あとでの案内</h3><p>フォルダーへの入口を示す表示見本です。実際のメールの受信・移動・削除・保存は行いません。</p></section><section id="demo-saved" tabindex="-1"><h3>保存済みの案内</h3><p>フォルダーへの入口を示す表示見本です。実際のメールの受信・移動・削除・保存は行いません。</p></section><section id="demo-trash" tabindex="-1"><h3>ゴミ箱の案内</h3><p>フォルダーへの入口を示す表示見本です。実際のメールの受信・移動・削除・保存は行いません。</p></section><noscript><p>JavaScriptが無効なためタブと設定の操作はできません。初期画面と各説明へのリンクを表示します。</p></noscript></aside></main>
    </div>
  <script>
const tabs=[...document.querySelectorAll('[role="tab"]')];
function selectTab(index,focus=false){tabs.forEach((tab,i)=>{const selected=i===index;tab.setAttribute('aria-selected',String(selected));tab.tabIndex=selected?0:-1;tab.classList.toggle('on',selected);tab.classList.toggle('active',selected)});updatePanel(index);if(focus)tabs[index].focus()}
tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>selectTab(index));tab.addEventListener('keydown',event=>{let next;if(event.key==='ArrowRight')next=(index+1)%tabs.length;else if(event.key==='ArrowLeft')next=(index+tabs.length-1)%tabs.length;else if(event.key==='Home')next=0;else if(event.key==='End')next=tabs.length-1;else return;event.preventDefault();selectTab(next,true)})});

const states=[['すべて','受信箱は空です','お知らせや返信が届くと、ここに表示されます。設定の見本から、案内の続きも確認できます。'],['未読','未読のお知らせはありません','未読のお知らせは、ここにまとめて表示されます。'],['自分宛て','自分宛てのお知らせはありません','自分へのメンションを含むお知らせが届くと、ここに表示されます。']];
function updatePanel(index){document.querySelector('[data-filter-label]').textContent=states[index][0];document.querySelector('[data-empty-heading]').textContent=states[index][1];document.querySelector('[data-empty-description]').textContent=states[index][2];document.querySelector('#inbox-panel').setAttribute('aria-labelledby',tabs[index].id)}
const folders=document.querySelector('.folders'),wide=matchMedia('(min-width:721px)');function syncFolders(){if(!wide.matches&&folders.querySelector('nav').contains(document.activeElement))folders.querySelector('summary').focus();folders.open=wide.matches}wide.addEventListener('change',syncFolders);syncFolders();
const form=document.querySelector('form'),status=document.querySelector('[data-settings-status]');form.addEventListener('submit',event=>event.preventDefault());function clearStatus(){status.hidden=true;status.textContent=''}form.addEventListener('change',clearStatus);
document.querySelector('[data-settings-confirm]').addEventListener('click',()=>{const replies=document.querySelector('#setting-replies').checked,mentions=document.querySelector('#setting-mentions').checked;status.textContent='選択の見本:返信のお知らせは'+(replies?'オン':'オフ')+'、自分宛てのお知らせは'+(mentions?'オン':'オフ')+'。通知設定の変更・配信・送信・保存は行っていません。';status.hidden=false});document.querySelector('[data-settings-reset]').addEventListener('click',()=>{form.reset();clearStatus()});
document.querySelectorAll('[data-js-control]').forEach(el=>el.disabled=false);selectTab(0);
</script></body>
</html>

2026年10月14日、Chromiumで320・390・768・1280pxの描画とキーボードのフォーカス表示を確認しました。他のブラウザは未確認です。利用先のブラウザと実データでも確認してください。

概要

空状態(empty state)は、データがまだ存在しない画面です。受信箱が空、通知がゼロ、検索結果なし——一見すると「何も設計することがない」画面に見えます。しかし実際は逆で、空状態こそ多くのユーザーが最初に出会う画面です。アカウントを作った直後、すべてはゼロから始まる。つまり空状態は、プロダクトの第一印象そのものなのです。

真っ白な画面が呼ぶ不安

何も表示されない画面を見たユーザーは、無意識に問いを抱きます。「壊れているのか」「使い方を間違えたのか」「ここで何をすればいいのか」。この小さな不安が積み重なると離脱に繋がる。温度のあるイラストと一行のコピーは、まずその不安を打ち消す役割を持ちます。これは正常な状態です、あなたは何も間違えていません。そう伝えてから、次の一歩を示します。

空状態は小さなランディングページ

優れた空状態は、状況を説明するだけで終わりません。「最初のメールを送ってみましょう」「チームを招待しましょう」といった、ただ一つの明確なCTAを提示する。これは実質的に、プロダクト内に置かれた小さなランディングページです。新規ユーザーにとっては自然なオンボーディングになり、既存ユーザーにとっては次の行動への合図になります。

崩れる形受信箱は空です

状況の説明で止まっています。読んだ人は次に何をすればいいかを自分で考えることになり、そのまま画面を閉じます。

効く形最初のメッセージを待っています

同じ事実でも次の行動を誘います。空状態は実質的に、プロダクト内に置かれた小さなランディングページとして設計します。

見本で試せる範囲

「すべて」「未読」「自分宛て」の3つのタブは、各状態の見出しと説明を切り替えます。左右キー・Home・Endでも選択できます。フォルダーのリンクは各項目の説明へ移動し、スマホでは「フォルダー案内(見本)」から開けます。メールの受信・既読変更・移動・削除は行いません。

主ボタンからは、返信と自分宛てのお知らせを選ぶ設定の見本へ進めます。「選択内容を確認(見本)」でオン・オフを表示し、初期化か再読み込みで両方オンへ戻ります。通知配信・OS設定の変更・外部送信・保存は行いません。イラストは埋め込みSVGなので、保存したHTML1ファイルで表示できます。

仕上げの判断軸

CTAは必ず1つに絞ってください。空っぽで心細い状態の人に選択肢を3つ並べると、かえって動けなくなります。

イラストは状況に合った穏やかなトーンで、ブランドのキャラクターと一致させる。コピーは短く、前向きに——「受信箱は空です」より「最初のメッセージを待っています」のほうが、同じ事実でも次の行動を誘います。

空状態の構成要素

空状態の構成要素 図・記号 何が無いのかを書く 次の一手 「データがありません」で終わらせない 空なのは失敗ではなく、まだ始めていないだけ。始め方をその場に置く
空状態は行き止まりにしないことがすべてです。何が無いのかを書き、次にできることを同じ画面に置いてください。

使いどころ

  • SaaS管理画面の受信箱・通知センター
  • タスクリスト・プロジェクト一覧の初期画面
  • 検索結果・フィルター結果が0件のとき
  • 履歴・アクティビティが無いダッシュボード

このデザインの分類

カテゴリ
空状態
スタイル
プレイフル / ソフト / ハンドクラフト / ミニマル

背景と狙いどころ

よくある質問

空状態に CTA は必要?
必要です。「何もない」だけで終わらせると離脱率が上がります。次に何をすべきか1つだけ提示するのが鉄則です。
イラストは外注必須?
シンプルな線画なら自前で十分。Tabler Icons や Lucide の大型版で代用も可能です。CSS で太く描画すれば手作り感も出ます。
重要度の低い空画面に時間をかける価値ある?
重要です。空状態はユーザーが何度も目にする画面なので、ここの体験がプロダクトへの愛着に直結します。

この画面を実装する

動くサンプルとコピペできるコードを、技術別にまとめています。