ウィジェット・ダッシュボード

grid-auto-flow: dense と可変スパンで大小のウィジェットを隙間なく敷き詰めるダッシュボード。並び替えやクリック拡大で密集アルゴリズムの動きを体感できる。

#css#grid#dashboard

ライブデモ

コード

HTML
<!-- ウィジェット・ダッシュボード:grid-auto-flow:dense と可変スパンで隙間なく敷き詰める -->
<div class="dw" id="dwRoot">
  <header class="dw__head">
    <div class="dw__head-text">
      <span class="dw__eyebrow">OVERVIEW</span>
      <h1>ダッシュボード</h1>
    </div>
    <div class="dw__tools">
      <button class="dw__btn" id="dwShuffle" type="button">並び替え</button>
      <span class="dw__hint">ウィジェットをクリックで拡大/縮小</span>
    </div>
  </header>

  <div class="dw__grid" id="dwGrid">
    <section class="dw-w dw-w--big" data-widget>
      <div class="dw-w__top"><span class="dw-w__k">売上推移</span><b class="dw-w__v">&yen;8.4M</b></div>
      <div class="dw-w__bars">
        <i style="--h:38%"></i><i style="--h:52%"></i><i style="--h:44%"></i><i style="--h:66%"></i>
        <i style="--h:58%"></i><i style="--h:74%"></i><i style="--h:69%"></i><i style="--h:88%"></i>
      </div>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">アクティブ</span><b class="dw-w__v">3,204</b><i class="dw-w__d dw-w__d--up">+4.2%</i>
    </section>

    <section class="dw-w dw-w--tall" data-widget>
      <span class="dw-w__k">満足度スコア</span>
      <div class="dw-w__gauge" style="--pct:82"><span>82</span></div>
      <p class="dw-w__note">先月比 +3pt</p>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">解約率</span><b class="dw-w__v">1.6%</b><i class="dw-w__d dw-w__d--down">-0.4%</i>
    </section>

    <section class="dw-w dw-w--wide" data-widget>
      <span class="dw-w__k">トラフィック内訳</span>
      <ul class="dw-w__list">
        <li><span>検索流入</span><b>48%</b></li>
        <li><span>直接流入</span><b>27%</b></li>
        <li><span>SNS経由</span><b>18%</b></li>
        <li><span>その他</span><b>7%</b></li>
      </ul>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">新規登録</span><b class="dw-w__v">142</b><i class="dw-w__d dw-w__d--up">+11%</i>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">サーバー負荷</span>
      <div class="dw-w__bars dw-w__bars--sm">
        <i style="--h:30%"></i><i style="--h:55%"></i><i style="--h:40%"></i><i style="--h:62%"></i><i style="--h:35%"></i>
      </div>
    </section>

    <section class="dw-w dw-w--wide" data-widget>
      <span class="dw-w__k">最近の注文</span>
      <ul class="dw-w__orders">
        <li><span class="dw-w__dot"></span>#10432 プレミアムプラン<b>&yen;12,000</b></li>
        <li><span class="dw-w__dot dw-w__dot--b"></span>#10431 スタンダードプラン<b>&yen;4,800</b></li>
        <li><span class="dw-w__dot"></span>#10430 プレミアムプラン<b>&yen;12,000</b></li>
      </ul>
    </section>
  </div>
</div>
CSS
/* 全体:明るいSaaS分析ダッシュボード。grid-auto-flow:dense で可変スパンのウィジェットを敷き詰める */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: "Hiragino Sans", system-ui, sans-serif;
  background: #eef1f8;
  color: #1b2033;
  min-height: 100vh;
  display: block; /* ビルダーの flex 中央寄せを解除し、フルブリードのダッシュボードを表示 */
}

/* ルート:width:100% が無いと親の flex 中央寄せで潰れるため必須 */
.dw { width: 100%; min-height: 100vh; padding: clamp(16px, 3vw, 32px); }

.dw__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: 12px; margin-bottom: 18px;
}
.dw__eyebrow { display: block; font-size: 11px; font-weight: 800; letter-spacing: .22em; color: #6366f1; margin-bottom: 4px; }
.dw__head-text h1 { font-size: clamp(20px, 3.4vw, 28px); font-weight: 800; }
.dw__tools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dw__btn {
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  color: #fff; background: #4338ca; border: none; border-radius: 10px;
  padding: 9px 16px; transition: transform .15s ease, box-shadow .15s ease;
}
.dw__btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -10px rgba(67,56,202,.5); }
.dw__hint { font-size: 12px; color: #6b7390; }

/* 土台:4列 x dense 詰め */
.dw__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 92px;
  grid-auto-flow: dense;
  gap: 14px;
}

.dw-w {
  position: relative; overflow: hidden;
  background: #fff; border: 1px solid #e3e7f2; border-radius: 16px;
  padding: 14px 16px; cursor: pointer;
  display: flex; flex-direction: column; justify-content: space-between; gap: 6px;
  box-shadow: 0 1px 2px rgba(20, 24, 50, .04);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.dw-w:hover { transform: translateY(-3px); box-shadow: 0 14px 28px -18px rgba(30, 41, 90, .35); border-color: #c7d0ea; }
.dw-w__k { font-size: 11px; font-weight: 800; letter-spacing: .04em; color: #6b7390; }
.dw-w__v { font-size: 24px; font-weight: 800; }
.dw-w__d { font-size: 11px; font-weight: 700; font-style: normal; width: fit-content; padding: 1px 7px; border-radius: 20px; }
.dw-w__d--up { color: #059669; background: #d1fae5; }
.dw-w__d--down { color: #dc2626; background: #fee2e2; }

/* スパン:可変サイズで dense が隙間を埋める */
.dw-w--big { grid-column: span 2; grid-row: span 2; }
.dw-w--wide { grid-column: span 2; }
.dw-w--tall { grid-row: span 2; }

/* クリックで拡大(is-expanded は JS が付け外し。dense が周囲を自動で再配置) */
.dw-w.is-expanded { grid-column: span 2; grid-row: span 2; z-index: 2; border-color: #a5b4fc; }

/* 大きいタイル内のバーチャート */
.dw-w--big .dw-w__top { display: flex; align-items: baseline; justify-content: space-between; }
.dw-w__bars { flex: 1; display: flex; align-items: flex-end; gap: 6px; min-height: 0; }
.dw-w__bars i {
  flex: 1; height: var(--h, 40%); border-radius: 4px 4px 0 0; min-height: 4px;
  background: linear-gradient(180deg, #818cf8, #4338ca);
}
.dw-w__bars--sm { height: 46px; }
.dw-w__bars--sm i { background: linear-gradient(180deg, #34d399, #059669); }

/* ゲージ(満足度スコア) */
.dw-w__gauge {
  --pct: 70;
  width: 74px; height: 74px; border-radius: 50%; margin: 4px auto;
  background: conic-gradient(#4338ca calc(var(--pct) * 1%), #e7e9f5 0);
  display: grid; place-items: center;
}
.dw-w__gauge span {
  width: 56px; height: 56px; border-radius: 50%; background: #fff;
  display: grid; place-items: center; font-size: 17px; font-weight: 800; color: #1b2033;
}
.dw-w__note { font-size: 11px; color: #6b7390; text-align: center; }

/* リスト系ウィジェット */
.dw-w__list, .dw-w__orders { list-style: none; display: flex; flex-direction: column; gap: 6px; flex: 1; justify-content: center; }
.dw-w__list li { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; }
.dw-w__list li b { color: #4338ca; }
.dw-w__orders li { display: flex; align-items: center; gap: 8px; font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dw-w__orders li b { margin-left: auto; color: #1b2033; flex-shrink: 0; }
.dw-w__dot { width: 7px; height: 7px; border-radius: 50%; background: #34d399; flex-shrink: 0; }
.dw-w__dot--b { background: #38bdf8; }

@media (max-width: 760px) {
  .dw__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 420px) {
  .dw__grid { grid-template-columns: 1fr; }
  .dw-w--big, .dw-w--wide, .dw-w.is-expanded { grid-column: span 1; }
}

@media (prefers-reduced-motion: reduce) {
  .dw-w, .dw__btn { transition: none; }
}
JavaScript
// ウィジェットのクリックで拡大/縮小トグル、並び替えボタンでDOM順をシャッフル
// (grid-auto-flow:dense が毎回すき間なく再配置する様子を実演/null安全)
(() => {
  const grid = document.getElementById('dwGrid');
  const shuffleBtn = document.getElementById('dwShuffle');
  if (!grid) return;

  const widgets = Array.from(grid.querySelectorAll('[data-widget]'));
  if (!widgets.length) return;

  widgets.forEach((w) => {
    w.addEventListener('click', () => {
      w.classList.toggle('is-expanded');
    });
  });

  if (shuffleBtn) {
    shuffleBtn.addEventListener('click', () => {
      const items = widgets.slice();
      for (let i = items.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [items[i], items[j]] = [items[j], items[i]];
      }
      items.forEach((el) => grid.appendChild(el));
    });
  }
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ウィジェット・ダッシュボード」の効果を追加してください。

# 追加してほしい効果
ウィジェット・ダッシュボード(レイアウト & グリッド)
grid-auto-flow: dense と可変スパンで大小のウィジェットを隙間なく敷き詰めるダッシュボード。並び替えやクリック拡大で密集アルゴリズムの動きを体感できる。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ウィジェット・ダッシュボード:grid-auto-flow:dense と可変スパンで隙間なく敷き詰める -->
<div class="dw" id="dwRoot">
  <header class="dw__head">
    <div class="dw__head-text">
      <span class="dw__eyebrow">OVERVIEW</span>
      <h1>ダッシュボード</h1>
    </div>
    <div class="dw__tools">
      <button class="dw__btn" id="dwShuffle" type="button">並び替え</button>
      <span class="dw__hint">ウィジェットをクリックで拡大/縮小</span>
    </div>
  </header>

  <div class="dw__grid" id="dwGrid">
    <section class="dw-w dw-w--big" data-widget>
      <div class="dw-w__top"><span class="dw-w__k">売上推移</span><b class="dw-w__v">&yen;8.4M</b></div>
      <div class="dw-w__bars">
        <i style="--h:38%"></i><i style="--h:52%"></i><i style="--h:44%"></i><i style="--h:66%"></i>
        <i style="--h:58%"></i><i style="--h:74%"></i><i style="--h:69%"></i><i style="--h:88%"></i>
      </div>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">アクティブ</span><b class="dw-w__v">3,204</b><i class="dw-w__d dw-w__d--up">+4.2%</i>
    </section>

    <section class="dw-w dw-w--tall" data-widget>
      <span class="dw-w__k">満足度スコア</span>
      <div class="dw-w__gauge" style="--pct:82"><span>82</span></div>
      <p class="dw-w__note">先月比 +3pt</p>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">解約率</span><b class="dw-w__v">1.6%</b><i class="dw-w__d dw-w__d--down">-0.4%</i>
    </section>

    <section class="dw-w dw-w--wide" data-widget>
      <span class="dw-w__k">トラフィック内訳</span>
      <ul class="dw-w__list">
        <li><span>検索流入</span><b>48%</b></li>
        <li><span>直接流入</span><b>27%</b></li>
        <li><span>SNS経由</span><b>18%</b></li>
        <li><span>その他</span><b>7%</b></li>
      </ul>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">新規登録</span><b class="dw-w__v">142</b><i class="dw-w__d dw-w__d--up">+11%</i>
    </section>

    <section class="dw-w" data-widget>
      <span class="dw-w__k">サーバー負荷</span>
      <div class="dw-w__bars dw-w__bars--sm">
        <i style="--h:30%"></i><i style="--h:55%"></i><i style="--h:40%"></i><i style="--h:62%"></i><i style="--h:35%"></i>
      </div>
    </section>

    <section class="dw-w dw-w--wide" data-widget>
      <span class="dw-w__k">最近の注文</span>
      <ul class="dw-w__orders">
        <li><span class="dw-w__dot"></span>#10432 プレミアムプラン<b>&yen;12,000</b></li>
        <li><span class="dw-w__dot dw-w__dot--b"></span>#10431 スタンダードプラン<b>&yen;4,800</b></li>
        <li><span class="dw-w__dot"></span>#10430 プレミアムプラン<b>&yen;12,000</b></li>
      </ul>
    </section>
  </div>
</div>

【CSS】
/* 全体:明るいSaaS分析ダッシュボード。grid-auto-flow:dense で可変スパンのウィジェットを敷き詰める */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: "Hiragino Sans", system-ui, sans-serif;
  background: #eef1f8;
  color: #1b2033;
  min-height: 100vh;
  display: block; /* ビルダーの flex 中央寄せを解除し、フルブリードのダッシュボードを表示 */
}

/* ルート:width:100% が無いと親の flex 中央寄せで潰れるため必須 */
.dw { width: 100%; min-height: 100vh; padding: clamp(16px, 3vw, 32px); }

.dw__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: 12px; margin-bottom: 18px;
}
.dw__eyebrow { display: block; font-size: 11px; font-weight: 800; letter-spacing: .22em; color: #6366f1; margin-bottom: 4px; }
.dw__head-text h1 { font-size: clamp(20px, 3.4vw, 28px); font-weight: 800; }
.dw__tools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dw__btn {
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  color: #fff; background: #4338ca; border: none; border-radius: 10px;
  padding: 9px 16px; transition: transform .15s ease, box-shadow .15s ease;
}
.dw__btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -10px rgba(67,56,202,.5); }
.dw__hint { font-size: 12px; color: #6b7390; }

/* 土台:4列 x dense 詰め */
.dw__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 92px;
  grid-auto-flow: dense;
  gap: 14px;
}

.dw-w {
  position: relative; overflow: hidden;
  background: #fff; border: 1px solid #e3e7f2; border-radius: 16px;
  padding: 14px 16px; cursor: pointer;
  display: flex; flex-direction: column; justify-content: space-between; gap: 6px;
  box-shadow: 0 1px 2px rgba(20, 24, 50, .04);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.dw-w:hover { transform: translateY(-3px); box-shadow: 0 14px 28px -18px rgba(30, 41, 90, .35); border-color: #c7d0ea; }
.dw-w__k { font-size: 11px; font-weight: 800; letter-spacing: .04em; color: #6b7390; }
.dw-w__v { font-size: 24px; font-weight: 800; }
.dw-w__d { font-size: 11px; font-weight: 700; font-style: normal; width: fit-content; padding: 1px 7px; border-radius: 20px; }
.dw-w__d--up { color: #059669; background: #d1fae5; }
.dw-w__d--down { color: #dc2626; background: #fee2e2; }

/* スパン:可変サイズで dense が隙間を埋める */
.dw-w--big { grid-column: span 2; grid-row: span 2; }
.dw-w--wide { grid-column: span 2; }
.dw-w--tall { grid-row: span 2; }

/* クリックで拡大(is-expanded は JS が付け外し。dense が周囲を自動で再配置) */
.dw-w.is-expanded { grid-column: span 2; grid-row: span 2; z-index: 2; border-color: #a5b4fc; }

/* 大きいタイル内のバーチャート */
.dw-w--big .dw-w__top { display: flex; align-items: baseline; justify-content: space-between; }
.dw-w__bars { flex: 1; display: flex; align-items: flex-end; gap: 6px; min-height: 0; }
.dw-w__bars i {
  flex: 1; height: var(--h, 40%); border-radius: 4px 4px 0 0; min-height: 4px;
  background: linear-gradient(180deg, #818cf8, #4338ca);
}
.dw-w__bars--sm { height: 46px; }
.dw-w__bars--sm i { background: linear-gradient(180deg, #34d399, #059669); }

/* ゲージ(満足度スコア) */
.dw-w__gauge {
  --pct: 70;
  width: 74px; height: 74px; border-radius: 50%; margin: 4px auto;
  background: conic-gradient(#4338ca calc(var(--pct) * 1%), #e7e9f5 0);
  display: grid; place-items: center;
}
.dw-w__gauge span {
  width: 56px; height: 56px; border-radius: 50%; background: #fff;
  display: grid; place-items: center; font-size: 17px; font-weight: 800; color: #1b2033;
}
.dw-w__note { font-size: 11px; color: #6b7390; text-align: center; }

/* リスト系ウィジェット */
.dw-w__list, .dw-w__orders { list-style: none; display: flex; flex-direction: column; gap: 6px; flex: 1; justify-content: center; }
.dw-w__list li { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; }
.dw-w__list li b { color: #4338ca; }
.dw-w__orders li { display: flex; align-items: center; gap: 8px; font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dw-w__orders li b { margin-left: auto; color: #1b2033; flex-shrink: 0; }
.dw-w__dot { width: 7px; height: 7px; border-radius: 50%; background: #34d399; flex-shrink: 0; }
.dw-w__dot--b { background: #38bdf8; }

@media (max-width: 760px) {
  .dw__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 420px) {
  .dw__grid { grid-template-columns: 1fr; }
  .dw-w--big, .dw-w--wide, .dw-w.is-expanded { grid-column: span 1; }
}

@media (prefers-reduced-motion: reduce) {
  .dw-w, .dw__btn { transition: none; }
}

【JavaScript】
// ウィジェットのクリックで拡大/縮小トグル、並び替えボタンでDOM順をシャッフル
// (grid-auto-flow:dense が毎回すき間なく再配置する様子を実演/null安全)
(() => {
  const grid = document.getElementById('dwGrid');
  const shuffleBtn = document.getElementById('dwShuffle');
  if (!grid) return;

  const widgets = Array.from(grid.querySelectorAll('[data-widget]'));
  if (!widgets.length) return;

  widgets.forEach((w) => {
    w.addEventListener('click', () => {
      w.classList.toggle('is-expanded');
    });
  });

  if (shuffleBtn) {
    shuffleBtn.addEventListener('click', () => {
      const items = widgets.slice();
      for (let i = items.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [items[i], items[j]] = [items[j], items[i]];
      }
      items.forEach((el) => grid.appendChild(el));
    });
  }
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。