ダッシュボード · ダッシュボード

数字から読ませるSaaS管理画面

保存した一覧を見る 0 →

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

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

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

プレビュー

画面に合わせる

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

vibe.itlibra.com/preview/saas-admin-overview/

AIへの指示文

請求書クラウドのようなB2B SaaSの管理画面の「概要」ページを作ってください。Linear や Vercel のような、装飾を抑えた実務向けの見た目です。

要件:
- 配色: 地 #fafafa、面 #ffffff、サイドバー #f4f4f5、罫線 #e4e4e7、文字 #18181b、補助 #52525b、アクセントは藍 #5b5bd6。影はほぼ使わず罫線で区切る。
- 左に幅232pxのサイドバー。上にワークスペース切替、メニューは「概要/請求書/取引先/入金の照合」と、小見出し付きの「レポート」「ワークスペース」のグループ。各項目の右に件数を薄く出す。選択中は白い面に1pxの枠。
- サイドバーの一番下に、契約プランと利用量のメーター(72 / 100件)。
- 上部は固定のバー。パンくず、⌘K 付きの検索ボタン、アカウントのアイコン。
- ページの見出しの右に、集計期間の切替(7日/30日/90日、aria-pressed で状態を持つ)と主要な操作ボタン1つ。
- 指標カードを4枚横並び。ラベル、大きな数値、前期比、小さな推移線。
- 前期比は「増えたか減ったか」ではなく「良くなったか悪くなったか」で色を決める。未回収が増えたら赤、回収日数が減ったら緑。矢印の向きは増減のまま。
- 中段は2対1の2列。左に請求額と入金額の推移グラフ(入金額は破線にして、色だけに頼らず区別する)、右に最近の動き。
- 下段に直近の請求書の表。金額は右揃え、状態は「✓ 入金済み」「… 支払待ち」「! 期限超過」のように記号と文字を併記したバッジ。
- 数字は font-variant-numeric: tabular-nums で桁を揃える。
- 期間を切り替えたらグラフと見出しの期間表記を描き直す。グラフには role="img" と、期間に合わせて変わる title を付ける。
- 幅1080px未満で指標を2列・中段を1列に、760px未満でサイドバーを隠してボタンで出す。
- フォント: Inter と日本語の system フォント。

バリエーション

ダーク版

地を #0b0b0d、面を #141417、罫線を #26262b、文字を #ededef に。
アクセントは #8b8bf5 に上げ、グラフの塗りは透明度12%にして暗い地で浮かないようにする。

異常を先に出す版

指標カードの上に、対応が必要な項目だけを集めた帯を置く(期限超過3件・照合できない入金1件など)。
帯の各項目は該当する一覧へ絞り込んだ状態で飛ぶリンクにする。

このプレビューのコード

HTML・CSS・JavaScriptの1ファイルです。外部依存・広告・解析コードはありません。架空の請求サービスです。期間別のグラフとスマホメニューを試せます。指標と請求書は固定の見本で、送付・照合・検索・認証・保存・決済は行いません。

CC0 · 改変・商用利用自由 · revision 33d5341e1649

コードを表示
<!doctype html>
<!-- Vibehaus · CC0 · 架空のデータを使う1ファイルの表示見本。外部依存・送信・永続保存なし。 -->
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="robots" content="noindex" />
    <title>数字から読ませるSaaS管理画面 — Vibehaus プレビュー</title>
    <style>
      :root {
        --bg: #fafafa;
        --panel: #ffffff;
        --side: #f4f4f5;
        --line: #e4e4e7;
        --line-2: #d4d4d8;
        --ink: #18181b;
        --muted: #52525b;
        --quiet: #62626c;
        --accent: #5b5bd6;
        --accent-soft: #eef0ff;
        --up: #15803d;
        --down: #b91c1c;
        --font: "Inter", system-ui, -apple-system, "Hiragino Sans", "Hiragino Kaku Gothic ProN",
          "Yu Gothic Medium", "Yu Gothic", "Meiryo", sans-serif;
        --mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, "Meiryo", monospace;
      }
      * { box-sizing: border-box; }
      html, body { height: 100%; }
      body {
        margin: 0;
        font-family: var(--font);
        font-size: 14px;
        line-height: 1.7;
        letter-spacing: 0;
        color: var(--ink);
        background: var(--bg);
        /* 数字の桁を揃える。表とカードの数値が縦に並んだとき比べやすい */
        font-variant-numeric: tabular-nums;
      }
      button { font: inherit; color: inherit; cursor: pointer; }
      a { color: inherit; text-decoration: none; }
      .app { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 100%; }

      /* ---------- サイドバー ---------- */
      .side { display: flex; flex-direction: column; gap: 1.2rem; padding: 0.9rem 0.7rem; background: var(--side); border-right: 1px solid var(--line); }
      .ws {
        display: flex; align-items: center; gap: 0.55rem; width: 100%;
        padding: 0.45rem 0.55rem; text-align: left;
        background: none; border: 1px solid transparent; border-radius: 8px;
      }
      .ws:hover { background: #ebebed; }
      .ws-mark { display: grid; place-items: center; width: 24px; height: 24px; font-size: 12px; font-weight: 700; color: #fff; background: var(--ink); border-radius: 6px; }
      .ws b { font-size: 14px; }
      .ws small { margin-left: auto; color: var(--quiet); }
      .navgroup p { margin: 0 0 0.25rem 0.55rem; font-size: 12px; font-weight: 600; color: var(--quiet); }
      .navgroup ul { margin: 0; padding: 0; list-style: none; }
      .navgroup a {
        display: flex; align-items: center; gap: 0.55rem;
        padding: 0.35rem 0.55rem; color: var(--muted); border-radius: 7px;
      }
      .navgroup a:hover { color: var(--ink); background: #ebebed; }
      .navgroup a[aria-current="location"] { color: var(--ink); font-weight: 600; background: var(--panel); box-shadow: 0 0 0 1px var(--line); }
      .ico { width: 16px; text-align: center; color: var(--quiet); }
      .count { margin-left: auto; font-size: 12px; color: var(--quiet); }
      .plan { margin-top: auto; padding: 0.8rem; font-size: 12px; color: var(--muted); background: var(--panel); border: 1px solid var(--line); border-radius: 10px; }
      .plan b { display: block; color: var(--ink); font-size: 13px; }
      .meter { height: 5px; margin: 0.5rem 0 0.35rem; overflow: hidden; background: var(--line); border-radius: 99px; }
      .meter i { display: block; width: 72%; height: 100%; background: var(--accent); }

      /* ---------- 上部 ---------- */
      .main { min-width: 0; }
      .top {
        position: sticky; top: 0; z-index: 5;
        display: flex; align-items: center; gap: 0.8rem;
        padding: 0.65rem 1.5rem;
        background: color-mix(in srgb, var(--bg) 90%, transparent);
        backdrop-filter: blur(8px);
        border-bottom: 1px solid var(--line);
      }
      .crumbs { display: flex; gap: 0.4rem; font-size: 13px; color: var(--quiet); }
      .crumbs b { color: var(--ink); font-weight: 600; }
      .search {
        display: flex; align-items: center; gap: 0.5rem; margin-left: auto;
        min-width: 240px; padding: 0.35rem 0.6rem;
        color: var(--quiet); background: var(--panel);
        border: 1px solid var(--line); border-radius: 8px;
      }
      kbd { margin-left: auto; padding: 0 0.35rem; font-family: var(--mono); font-size: 11px; color: var(--quiet); border: 1px solid var(--line); border-radius: 4px; }
      .avatar { width: 28px; height: 28px; display: grid; place-items: center; font-size: 12px; font-weight: 700; color: #fff; background: #e07a5f; border-radius: 50%; }
      .menu { display: none; padding: 0.2rem 0.5rem; background: none; border: 1px solid var(--line); border-radius: 6px; }

      .page { max-width: 1120px; margin: 0 auto; padding: 1.6rem 1.5rem 3rem; }
      .head { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.2rem; }
      .head h1 { margin: 0; font-size: 22px; line-height: 1.4; }
      .head p { margin: 0.15rem 0 0; color: var(--muted); }
      .range { display: inline-flex; margin-left: auto; padding: 3px; background: var(--side); border: 1px solid var(--line); border-radius: 9px; }
      .range button { padding: 0.25rem 0.75rem; font-size: 13px; color: var(--muted); background: none; border: 0; border-radius: 6px; }
      .range button[aria-pressed="true"] { color: var(--ink); font-weight: 600; background: var(--panel); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
      .btn { padding: 0.4rem 0.8rem; font-size: 13px; font-weight: 600; color: #fff; background: var(--ink); border: 0; border-radius: 8px; }

      /* ---------- 指標 ---------- */
      .kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.8rem; margin-bottom: 0.8rem; }
      .kpi { padding: 0.9rem 1rem; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; }
      .kpi p { margin: 0; font-size: 12px; color: var(--muted); }
      .kpi .v { margin: 0.2rem 0 0.1rem; font-size: 24px; font-weight: 700; line-height: 1.35; }
      .delta { font-size: 12px; font-weight: 600; }
      .delta.up { color: var(--up); }
      .delta.down { color: var(--down); }
      .delta small { font-weight: 400; color: var(--quiet); }
      .spark { display: block; width: 100%; height: 28px; margin-top: 0.4rem; }

      /* ---------- グラフと一覧 ---------- */
      .grid2 { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 0.8rem; margin-bottom: 0.8rem; }
      .card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; }
      .card-h { display: flex; align-items: center; gap: 0.6rem; padding: 0.8rem 1rem; border-bottom: 1px solid var(--line); }
      .card-h h2 { margin: 0; font-size: 14px; line-height: 1.4; }
      .card-h span { margin-left: auto; font-size: 12px; color: var(--quiet); }
      .chart { padding: 0.8rem 1rem 1rem; }
      .chart svg { display: block; width: 100%; height: 220px; }
      .legend { display: flex; gap: 1rem; margin-top: 0.5rem; font-size: 12px; color: var(--muted); }
      .legend i { display: inline-block; width: 10px; height: 3px; margin-right: 0.35rem; vertical-align: middle; border-radius: 2px; }

      .feed { margin: 0; padding: 0.4rem 1rem 0.8rem; list-style: none; }
      .feed li { display: grid; grid-template-columns: 8px minmax(0, 1fr); gap: 0.7rem; padding: 0.55rem 0; border-bottom: 1px solid var(--line); }
      .feed li:last-child { border-bottom: 0; }
      .feed .dot { width: 8px; height: 8px; margin-top: 0.5rem; border-radius: 50%; background: var(--accent); }
      .feed p { margin: 0; line-height: 1.6; }
      .feed small { color: var(--quiet); }

      table { width: 100%; border-collapse: collapse; }
      th, td { padding: 0.6rem 1rem; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
      th { font-size: 12px; font-weight: 600; color: var(--quiet); background: #fcfcfc; }
      td.num, th.num { text-align: right; }
      tbody tr:hover { background: #fafafb; }
      tbody tr:last-child td { border-bottom: 0; }
      .who { display: flex; align-items: center; gap: 0.55rem; }
      .who i { display: grid; place-items: center; width: 24px; height: 24px; font-size: 11px; font-style: normal; font-weight: 700; color: var(--accent); background: var(--accent-soft); border-radius: 6px; }
      /* 状態は色だけで示さない。文字と記号を必ず併記する */
      .badge { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.05rem 0.5rem; font-size: 12px; font-weight: 600; border-radius: 99px; border: 1px solid; }
      .badge.ok { color: var(--up); background: #f0fdf4; border-color: #bbf7d0; }
      .badge.wait { color: #a16207; background: #fefce8; border-color: #fde68a; }
      .badge.ng { color: var(--down); background: #fef2f2; border-color: #fecaca; }
      .tablewrap { overflow-x: auto; }

      :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

      @media (max-width: 1080px) {
        .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
        .grid2 { grid-template-columns: minmax(0, 1fr); }
      }
      @media (max-width: 760px) {
        .app { grid-template-columns: minmax(0, 1fr); }
        .side { position: fixed; inset: 0 auto 0 0; z-index: 10; width: 240px; transform: translateX(-100%); transition: transform 0.2s; }
        .side.open { transform: none; box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.3); }
        .menu { display: block; }
        .search { min-width: 0; }
        .search span { display: none; }
        .page { padding: 1.2rem 1rem 2.5rem; }
      }
      @media (prefers-reduced-motion: reduce) { .side { transition: none; } }

/* 配布見本の操作・説明。配色と主構成は維持する。 */
[hidden]{display:none!important} :focus-visible{outline:3px solid currentColor;outline-offset:3px}
.demo-note{margin:2rem 0 0;padding:1rem;border-top:1px solid currentColor;font:14px/1.8 system-ui,"Hiragino Sans","Meiryo",sans-serif;letter-spacing:0;overflow-wrap:anywhere}
.demo-note h2{font:700 16px/1.5 system-ui,"Hiragino Sans","Meiryo",sans-serif;letter-spacing:0}
.demo-note p{letter-spacing:0;scroll-margin-top:7rem}.demo-note a{display:inline-flex;align-items:center;min-height:44px}
.demo-banner{font:13px/1.7 system-ui,"Hiragino Sans","Meiryo",sans-serif;letter-spacing:0;margin:0 0 1rem}
[data-dashboard-scroll]{max-width:100%;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-gutter:stable}
[data-motion-toggle]{font:inherit;color:inherit;background:transparent;border:1px solid currentColor;border-radius:4px;min-height:44px;padding:.5rem .8rem;margin:0 0 1rem;cursor:pointer;letter-spacing:0}
html:not(.demo-js) *,html[data-motion-paused] *{animation-play-state:paused!important}
html:not(.demo-js) *::before,html:not(.demo-js) *::after,html[data-motion-paused] *::before,html[data-motion-paused] *::after{animation:none!important}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}

.app{grid-template-rows:auto 1fr}.main{display:contents}.side{grid-column:1;grid-row:1/span 2;min-width:0}.top{grid-column:2;grid-row:1;flex-wrap:wrap}.page{grid-column:2;grid-row:2;width:100%;min-width:0}.side a,.top a,.range button,.btn,.menu{min-height:44px;display:inline-flex;align-items:center}.side .ws{display:flex}.navgroup a{display:flex}.menu{min-width:44px}.avatar{background:#a8432b}.search{min-width:0}.range button{min-width:44px}.kpi .v{overflow-wrap:anywhere}.card-h{flex-wrap:wrap}.tablewrap table{min-width:660px}.side,.side.open{position:static;inset:auto;transform:none;box-shadow:none;transition:none;width:auto}.crumbs{flex-wrap:wrap}.btn{justify-content:center}.range button:disabled{cursor:default}
@media(max-width:760px){.app{grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto 1fr}.top{grid-column:1;grid-row:1;padding:.65rem 1rem;gap:.5rem}.side{grid-column:1;grid-row:2;border-right:0;border-bottom:1px solid var(--line)}.page{grid-column:1;grid-row:3}.side .navgroup ul{display:flex;flex-wrap:wrap;gap:.4rem}.navgroup a{border:1px solid var(--line)}.plan{margin-top:0}.menu{display:none}.app.is-mobile .menu{display:inline-flex}}@media(max-width:400px){.kpis{grid-template-columns:minmax(0,1fr)}.crumbs{font-size:12px}.avatar{flex-shrink:0}.top .search{margin-left:0}.head .range{margin-left:0}}
.menu{display:none}.app.is-mobile .menu:not([hidden]){display:inline-flex}
</style>
  </head>
  <body>
    <div class="app">
      <nav class="side" id="side" aria-label="メインメニュー">
        <a class="ws" href="#demo-workspace"><span class="ws-mark">K</span><b>Kakeru 請求</b><small>⌄</small></a>
        <div class="navgroup">
          <ul>
            <li><a href="#demo-overview" aria-current="page"><span class="ico">◧</span>概要</a></li>
            <li><a href="#demo-invoices"><span class="ico">▤</span>請求書<span class="count">128</span></a></li>
            <li><a href="#demo-customers"><span class="ico">◎</span>取引先<span class="count">46</span></a></li>
            <li><a href="#demo-matching"><span class="ico">⇄</span>入金の照合<span class="count">3</span></a></li>
          </ul>
        </div>
        <div class="navgroup">
          <p>レポート</p>
          <ul>
            <li><a href="#demo-sales"><span class="ico">▦</span>売上の推移</a></li>
            <li><a href="#demo-unpaid"><span class="ico">◔</span>未回収</a></li>
          </ul>
        </div>
        <div class="navgroup">
          <p>ワークスペース</p>
          <ul>
            <li><a href="#demo-settings"><span class="ico">⚙</span>設定</a></li>
            <li><a href="#demo-members"><span class="ico">☺</span>メンバー</a></li>
          </ul>
        </div>
        <div class="plan"><b>スタンダードプラン</b>今月の請求書 72 / 100 件<div class="meter" aria-hidden="true"><i></i></div>更新まで 9日</div>
      </nav>

      <div class="main">
        <header class="top">
          <button class="menu" hidden type="button" aria-controls="side" aria-expanded="false">☰<span style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">メニュー</span></button>
          <div class="crumbs"><span>Kakeru 請求</span><span>/</span><b>概要</b></div>
          <a class="search" href="#demo-search" aria-label="検索の表示見本について"><span>検索・移動</span><kbd>見本</kbd></a>
          <span class="avatar" aria-label="佐藤 さん">佐</span>
        </header>

        <main class="page"><p class="demo-banner">架空の請求サービスです。期間切替はグラフの固定データだけを変更し、指標・請求書は同じ見本を表示します。</p>
          <div class="head">
            <div>
              <h1>概要</h1>
              <p>固定データによる過去30日の表示見本です</p>
            </div>
            <div class="range" role="group" aria-label="集計期間">
              <button type="button" data-range="7" disabled aria-pressed="false">7日</button>
              <button type="button" data-range="30" disabled aria-pressed="true">30日</button>
              <button type="button" data-range="90" disabled aria-pressed="false">90日</button>
            </div>
            <a class="btn" href="#demo-create">+ 請求書を作成</a>
          </div>

          <section class="kpis" aria-label="主要な指標">
            <div class="kpi"><p>請求額</p><p class="v" id="k1">¥4,820,400</p><span class="delta up">▲ 12.4% <small>前期比</small></span>
              <svg class="spark" viewBox="0 0 100 28" preserveAspectRatio="none" aria-hidden="true"><polyline fill="none" stroke="#5b5bd6" stroke-width="1.6" points="0,22 12,20 24,21 36,16 48,17 60,12 72,13 84,8 100,6"/></svg></div>
            <div class="kpi"><p>入金済み</p><p class="v">¥3,912,000</p><span class="delta up">▲ 8.1% <small>前期比</small></span>
              <svg class="spark" viewBox="0 0 100 28" preserveAspectRatio="none" aria-hidden="true"><polyline fill="none" stroke="#5b5bd6" stroke-width="1.6" points="0,20 12,21 24,18 36,18 48,14 60,15 72,11 84,12 100,9"/></svg></div>
            <div class="kpi"><p>未回収</p><p class="v">¥908,400</p><span class="delta down">▲ 3.2% <small>前期比</small></span>
              <svg class="spark" viewBox="0 0 100 28" preserveAspectRatio="none" aria-hidden="true"><polyline fill="none" stroke="#b91c1c" stroke-width="1.6" points="0,18 12,17 24,19 36,15 48,16 60,13 72,14 84,12 100,11"/></svg></div>
            <div class="kpi"><p>平均回収日数</p><p class="v">23.6日</p><span class="delta up">▼ 1.8日 <small>前期比</small></span>
              <svg class="spark" viewBox="0 0 100 28" preserveAspectRatio="none" aria-hidden="true"><polyline fill="none" stroke="#5b5bd6" stroke-width="1.6" points="0,8 12,9 24,11 36,10 48,14 60,15 72,17 84,18 100,20"/></svg></div>
          </section>

          <div class="grid2">
            <section class="card" aria-labelledby="c-trend">
              <div class="card-h"><h2 id="c-trend">請求額と入金額の推移</h2><span id="rangeLabel" aria-live="polite">過去30日</span></div>
              <div class="chart">
                <svg id="chart" viewBox="0 0 600 220" preserveAspectRatio="none" role="img" aria-labelledby="chart-title">
                  <title id="chart-title">過去30日の請求額と入金額。どちらも右肩上がりで、差は月末に広がる</title>
                  <g stroke="#e4e4e7" stroke-width="1">
                    <line x1="0" y1="40" x2="600" y2="40"/><line x1="0" y1="100" x2="600" y2="100"/><line x1="0" y1="160" x2="600" y2="160"/><line x1="0" y1="219" x2="600" y2="219"/>
                  </g>
                  <path id="area" fill="#eef0ff" d="M0.0 149.2 L42.9 141.4 L85.7 143.3 L128.6 129.6 L171.4 121.8 L214.3 125.7 L257.1 110.0 L300.0 100.2 L342.9 104.1 L385.7 90.4 L428.6 78.6 L471.4 70.8 L514.3 74.7 L557.1 59.0 L600.0 43.4 L600 220 L0 220 Z"/>
                  <path id="lineA" fill="none" stroke="#5b5bd6" stroke-width="2.2" d="M0.0 149.2 L42.9 141.4 L85.7 143.3 L128.6 129.6 L171.4 121.8 L214.3 125.7 L257.1 110.0 L300.0 100.2 L342.9 104.1 L385.7 90.4 L428.6 78.6 L471.4 70.8 L514.3 74.7 L557.1 59.0 L600.0 43.4"/>
                  <path id="lineB" fill="none" stroke="#a1a1aa" stroke-width="2" stroke-dasharray="5 4" d="M0.0 164.9 L42.9 157.0 L85.7 153.1 L128.6 149.2 L171.4 141.4 L214.3 137.4 L257.1 129.6 L300.0 123.7 L342.9 119.8 L385.7 110.0 L428.6 106.1 L471.4 98.2 L514.3 94.3 L557.1 86.5 L600.0 78.6"/>
                </svg>
                <div class="legend"><span><i style="background:#5b5bd6"></i>請求額</span><span><i style="background:#a1a1aa"></i>入金額(破線)</span></div>
              </div>
            </section>

            <section class="card" aria-labelledby="c-feed">
              <div class="card-h"><h2 id="c-feed">最近の動き</h2><span>すべて見る</span></div>
              <ul class="feed">
                <li><span class="dot"></span><p>株式会社ミナト から <b>¥320,000</b> の入金を照合しました<br /><small>12分前</small></p></li>
                <li><span class="dot" style="background:#a16207"></span><p>請求書 INV-0412 の支払期限が3日後です<br /><small>1時間前</small></p></li>
                <li><span class="dot" style="background:#b91c1c"></span><p>入金額が請求額と一致しない取引が <b>1件</b> あります<br /><small>今日 09:12</small></p></li>
                <li><span class="dot"></span><p>林 さんが請求書を5件まとめて送付しました<br /><small>昨日</small></p></li>
              </ul>
            </section>
          </div>

          <section class="card" aria-labelledby="c-inv">
            <div class="card-h"><h2 id="c-inv">直近の請求書</h2><span>128件中 5件</span></div>
            <p class="demo-banner" style="padding:.6rem 1rem;margin:0">表は横にスクロールできます。Tabで表に入り、左右キーで移動できます。</p><div class="tablewrap" data-dashboard-scroll tabindex="0" role="region" aria-label="直近の請求書(横スクロール可)">
              <table>
                <thead><tr><th>取引先</th><th>請求書番号</th><th>期限</th><th>状態</th><th class="num">金額</th></tr></thead>
                <tbody>
                  <tr><td><span class="who"><i>ミ</i>株式会社ミナト</span></td><td>INV-0416</td><td>9月30日</td><td><span class="badge ok">✓ 入金済み</span></td><td class="num">¥320,000</td></tr>
                  <tr><td><span class="who"><i>ア</i>アオイ設計事務所</span></td><td>INV-0415</td><td>10月5日</td><td><span class="badge wait">… 支払待ち</span></td><td class="num">¥148,500</td></tr>
                  <tr><td><span class="who"><i>ほ</i>ほしぞら商店</span></td><td>INV-0414</td><td>9月20日</td><td><span class="badge ng">! 期限超過</span></td><td class="num">¥62,700</td></tr>
                  <tr><td><span class="who"><i>カ</i>カワセミ食品</span></td><td>INV-0413</td><td>10月10日</td><td><span class="badge wait">… 支払待ち</span></td><td class="num">¥1,204,000</td></tr>
                  <tr><td><span class="who"><i>ニ</i>ニシキ運輸</span></td><td>INV-0412</td><td>9月27日</td><td><span class="badge wait">… 支払待ち</span></td><td class="num">¥87,200</td></tr>
                </tbody>
              </table>
            </div>
          </section>
        <aside class="demo-note"><h2>この見本について</h2><p>架空のサービス・数値・人物を使った表示見本です。外部通信・永続保存は行いません。配布HTMLはCC0・利用料¥0です。</p><p id="demo-overview" tabindex="-1"><b>概要</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-invoices" tabindex="-1"><b>請求書</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-customers" tabindex="-1"><b>取引先</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-matching" tabindex="-1"><b>入金の照合</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-sales" tabindex="-1"><b>売上の推移</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-unpaid" tabindex="-1"><b>未回収</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-settings" tabindex="-1"><b>設定</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-members" tabindex="-1"><b>メンバー</b> — 請求サービスの構成を示すページ内の説明です。請求書の送付・入金照合・顧客管理・設定変更は行いません。</p><p id="demo-workspace" tabindex="-1"><b>ワークスペース</b> — 切替・契約・認証は未実装です。</p><p id="demo-search" tabindex="-1"><b>検索</b> — 検索欄の配置を示す見本です。検索とショートカットは未実装です。</p><p id="demo-create" tabindex="-1"><b>請求書の作成</b> — ボタンの配置を示す見本です。請求書の作成・保存・送信・決済は行いません。</p></aside></main>
      </div>
    </div>

    <script>
      // 集計期間に応じてグラフを描き直す。値は見本用の固定データ
      const SERIES = {
        7:  { a: [62, 70, 66, 78, 84, 80, 92], b: [50, 58, 60, 64, 70, 72, 76], label: "過去7日" },
        30: { a: [30, 34, 33, 40, 44, 42, 50, 55, 53, 60, 66, 70, 68, 76, 84], b: [22, 26, 28, 30, 34, 36, 40, 43, 45, 50, 52, 56, 58, 62, 66], label: "過去30日" },
        90: { a: [20, 26, 24, 30, 36, 34, 42, 40, 48, 54, 52, 60, 66, 72, 80], b: [16, 20, 22, 24, 28, 30, 32, 36, 38, 42, 46, 48, 52, 58, 62], label: "過去90日" },
      };
      const W = 600, H = 220, PAD = 12;
      const toPts = (arr) => arr.map((v, i) => [(i / (arr.length - 1)) * W, H - PAD - (v / 100) * (H - PAD * 2)]);
      const pathOf = (pts) => pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");

      function draw(range) {
        const s = SERIES[range];
        const a = toPts(s.a), b = toPts(s.b);
        document.getElementById("lineA").setAttribute("d", pathOf(a));
        document.getElementById("lineB").setAttribute("d", pathOf(b));
        document.getElementById("area").setAttribute("d", pathOf(a) + ` L${W} ${H} L0 ${H} Z`);
        document.getElementById("rangeLabel").textContent = s.label;
        document.getElementById("chart-title").textContent = `${s.label}の請求額と入金額。どちらも右肩上がり`;
      }

      document.querySelector(".range").addEventListener("click", (e) => {
        const btn = e.target.closest("button[data-range]");
        if (!btn) return;
        document.querySelectorAll(".range button").forEach((b) => b.setAttribute("aria-pressed", String(b === btn)));
        draw(btn.dataset.range);
        document.querySelector(".head>div>p").textContent = "固定データによる" + SERIES[btn.dataset.range].label + "の表示見本です";
      });
      draw(30);

      document.querySelectorAll('.range button').forEach(button=>button.disabled=false);
      document.querySelector('.head>div>p').textContent='固定データによる過去30日の表示見本です';
      const side = document.getElementById("side");
      const menu = document.querySelector(".menu");
      const app=document.querySelector('.app'),mobile=matchMedia('(max-width:760px)');
      function setOpen(open,restore=false){if(!open&&restore)menu.focus();side.hidden=mobile.matches&&!open;menu.setAttribute('aria-expanded',String(open));}
      function syncMenu(){const focus=document.activeElement;if(mobile.matches){app.classList.add('is-mobile');if(side.contains(focus))menu.focus()}else{side.hidden=false;if(focus===menu)side.querySelector('a').focus();app.classList.remove('is-mobile')}setOpen(false);}
      menu.hidden=false;menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';setOpen(open);if(open)side.querySelector('a').focus()});
      document.addEventListener('keydown',e=>{if(e.key==='Escape'&&mobile.matches&&!side.hidden){e.preventDefault();setOpen(false,true)}});
      document.addEventListener('pointerdown',e=>{if(mobile.matches&&!side.hidden&&!side.contains(e.target)&&!menu.contains(e.target))setOpen(false,side.contains(document.activeElement))});
      document.addEventListener('focusin',e=>{if(mobile.matches&&!side.hidden&&!side.contains(e.target)&&e.target!==menu)setOpen(false)});
      side.addEventListener('click',e=>{if(e.target.closest('a')&&mobile.matches)setOpen(false,true)});
      mobile.addEventListener('change',syncMenu);syncMenu();
      function syncCurrent(){const target=location.hash||'#demo-overview';document.querySelectorAll('.navgroup a').forEach(a=>{if(a.hash===target)a.setAttribute('aria-current','location');else a.removeAttribute('aria-current')})}
      addEventListener('hashchange',syncCurrent);syncCurrent();
    </script>
  </body>
</html>

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

概要

B2B SaaSの管理画面は、2020年代に入って見た目の方向が1つにまとまりました。Linear、Vercel、Stripe の管理画面に代表される、白い面と1pxの罫線、控えめな1色のアクセント、影をほとんど使わない組み方です。派手さを捨てたのは、この画面が毎日開かれる仕事の道具だからです。

開いて最初の数秒で何が分かるか

概要ページの仕事は、ログインした人に「いま何か問題が起きていないか」を数秒で伝えることです。そのために、上から下へ情報の粒度を細かくしていきます。

崩れる形グラフを最上段に大きく置く

見栄えは良くなりますが、読み取りに時間がかかります。いくらなのかという答えが、線の傾きを見て推測するものになってしまいます。

効く形数字、推移、明細の順に置く

最上段で数字を答え、中段で流れを見せ、下段で個別の明細へ降ります。知りたいことの粒度の順に並べると、多くの人は上の数行だけで用が済みます。

色は増減ではなく良し悪しに付ける

前期比の色でよくある失敗が、「増えたら緑、減ったら赤」で統一してしまうことです。未回収の金額やエラーの件数は、増えるほど悪い数字です。回収にかかる日数は、減るほど良い数字です。

矢印の向きは増減、色は良くなったか悪くなったか。役割を分けておけば、減ってほしい指標の良い知らせが警告に見えることはありません。

仕上げの判断軸

状態のバッジは、色だけで示さず記号と文字を併記します。緑と赤の区別が付きにくい人は珍しくありません。グラフの2本の線も同じで、片方を破線にして色以外でも見分けられるようにします。

4枚指標カードの数。狭い画面では2列2段に崩せる
2 : 1中段の推移と最近の動きの幅。主役はグラフ
tabular-nums数字の桁を揃える指定。縦に並んだ金額が比べやすい

装飾を抑えた画面は、細部の粗が目立ちます。サイドバーの件数、表の金額の右揃え、罫線の太さを揃えるといった小さな一貫性が、そのまま製品への信頼になります。

ダッシュボードの構成要素

ダッシュボードの構成要素 ナビ 1,284 96.2% ¥48万 12件 要点の数値 ── 最初に目に入る場所へ 推移 明細 数値は等幅数字で。桁が揃わないと比較にならない 全部を同じ大きさで並べると、どれが重要か分からなくなる
管理画面は何を最初に見せるかで価値が決まります。要点の数値を上に置き、詳細は下へ。並べる順序が優先順位そのものです。

使いどころ

  • 請求・会計・在庫などB2B SaaSのログイン直後の画面
  • 社内向け管理ツールのトップページ
  • 事業の数字を毎朝確認する経営者向けの画面
  • SaaSのLPに載せる製品画面のモック

このデザインの分類

カテゴリ
ダッシュボード
スタイル
SaaS / ミニマル / クール

背景と狙いどころ

よくある質問

指標カードは何枚が適切?
3〜5枚です。4枚なら横1列に収まり、幅が狭いときは2列2段に素直に崩せます。6枚を超えると、どれが本当に見るべき数字か分からなくなります。残りはレポートの画面へ回してください。
前期比が下がって緑になるのは分かりにくくない?
矢印は増減、色は良し悪し、と役割を分けていれば混乱しません。逆に「減ったら赤」で統一すると、回収日数やエラー件数のように減ってほしい指標で、良い知らせが警告に見えてしまいます。
グラフは何のライブラリで描くべき?
折れ線と面だけなら SVG の path で十分です。凡例の操作や拡大が必要になった時点でライブラリを検討してください。最初から入れると、概要ページの読み込みがいちばん重くなります。

この画面を実装する

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