GODRICH

ダッシュボード ウィジェット単体UI9選 —そのまま貼れる部品だけ

管理画面をまるごと使うのではなく、この部品だけを自分のプロジェクトにそのまま貼りたい人のために、単体で動くダッシュボード ウィジェットを9個集めました。ドーナツチャートは実際に数字が数え上がります。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9個は人気順ではなく、画面に入ってくる順番で並べました。まず骨格になるサイドバー、次に一目で把握できる要約(統計カード・表・チャート)、続いて実際に触る部分(フィルター・空の状態・設定)、最後にヘッダーの隅に付く小さなポップアップ2つ(通知・ユーザーメニュー)です。どのデモも完成した管理画面ではなく、その部品ひとつだけを画面中央に置いています。

01単体サイドバーナビ

折りたたみボタンを押すと、サイドバーの幅が実際に208pxから76pxへ縮み、文字ラベルが消えてアイコンだけが残ります。今選ばれているメニューは、左の細い縦バーと薄い背景色で示されます。

widtharia-pressedclick
.sd { width: 208px; transition: width $duration $easing; }
.sd.is-collapsed { width: 76px; }
.sd__label { transition: opacity $duration $easing; }
.sd.is-collapsed .sd__label { opacity: 0; }

02スパークライン表示の統計カード

期間の切り替えを押すと、訪問者数・コンバージョン率・売上の3つの数字が実際に入れ替わります。下のミニグラフ線は SVG の pathLength を100に固定しているので、形が違っても同じ描画方法で動きます。

stroke-dashoffsetopacitymouseenter
.sc__spark-path { stroke-dasharray: 100; stroke-dashoffset: 100; }
.sc__card.is-demo .sc__spark-path { animation: sc-draw 2s $easing infinite; }
@keyframes sc-draw {
  0%  { stroke-dashoffset: 100; }
  60% { stroke-dashoffset: 0; }
}

03並び替え可能なミニテーブル

列見出しを押すと、矢印の向きが変わって行の順番が実際に入れ替わります。金額列は数値として、名前列は文字列として、それぞれ別の比較方法で並べ替えています。

clickaria-sortorder
rows.sort(function (a, b) {
  var av = a.dataset[key], bv = b.dataset[key];
  if (key === 'amount') { av = Number(av); bv = Number(bv); return (av - bv) * dir; }
  return av.localeCompare(bv) * dir;
});
rows.forEach(function (r) { tbody.appendChild(r); });

04中央カウンター付きドーナツチャート

凡例を押すと、その区間が実際にリングから外れて、中央の数字が新しい合計まで数え上がります。リングは SVG ではなく conic-gradient と登録済みのカスタムプロパティ(@property)だけで描いています。

@propertyconic-gradientclick
@property --pct-a { syntax: "<percentage>"; inherits: true; initial-value: 0%; }
.cc__ring {
  background: conic-gradient($color 0 var(--pct-a), $stage-yellow var(--pct-a) var(--pct-b), rgba(23,20,26,.08) var(--pct-b) 100%);
  mask: radial-gradient(farthest-side, transparent 60%, #000 61%);
  transition: --pct-a $duration $easing, --pct-b $duration $easing;
}

05チップ型フィルターバー

チップを押すたびに実際に選択状態(色が付いた錠剤の形)に変わり、下の選択数も一緒に増えます。ひとつでも選ぶと「全解除」の文字ボタンが現れます。

aria-pressedtransformclick
c.addEventListener('click', function () {
  c.classList.remove('is-demo');
  var on = c.getAttribute('aria-pressed') === 'true';
  c.setAttribute('aria-pressed', String(!on));
  refresh();
});

06追加モーフ付き空状態イラスト

図形だけで描いた空の箱イラストがゆっくり浮かび続け、「最初の項目を追加」ボタンを押すとその場所が実際にリストカード1枚へ変わります。もう一度押すと元の空の状態に戻ります。

transformopacityclick
.es__empty, .es__item { position: absolute; inset: 0; transition: opacity $duration $easing, transform $duration $easing; }
.es__item { opacity: 0; transform: scale(.9); }
.es.is-added .es__item { opacity: 1; transform: scale(1); }
.es.is-added .es__empty { opacity: 0; transform: scale(.9); }

07保存状態付き設定フォームセクション

トグルを切り替えて保存ボタンを押すと、ボタンが実際に3段階に変わります。くるくる回るリング状のスピナーになり、700ミリ秒後に緑色の「保存済み」になり、1.6秒後に元の文字に戻ります。設定ページ丸ごとではなく、このセクションだけを移植できるよう<form>ひとつで閉じています。

transitionaria-checkedclick
save.classList.add('is-busy'); label.textContent = '保存中';
setTimeout(function () {
  save.classList.remove('is-busy'); save.classList.add('is-done'); label.textContent = '保存済み';
  setTimeout(function () { save.classList.remove('is-done'); label.textContent = '保存'; }, 1600);
}, 700);

08通知ドロップダウンパネル

ベルアイコンを押すと、パネルが実際にその場から広がって開きます。通知を1件押すとその項目だけ既読になり、上のバッジの数字が残りの未読数だけ実際に減ります。

transform-originaria-expandedclick
function syncBadge() {
  var n = document.querySelectorAll('.np__item.is-unread').length;
  badge.textContent = String(n);
  badge.style.display = n > 0 ? '' : 'none';
}
it.addEventListener('click', function () { it.classList.remove('is-unread'); syncBadge(); });

09ユーザーアバタードロップダウン

アバターを押すと、メニューが右上を基点に実際に広がって開きます。外側をクリックするか Esc キーを押すと実際に閉じ、メニュー自体をクリックしたときは閉じないよう closest('.um') で範囲を区別しています。

transformEscapeclick
document.addEventListener('click', function (e) {
  if (!e.target.closest('.um')) close();
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });

どこで壊れるか — 落とし穴

9個を作る間に実際にはまった落とし穴は、通知とユーザーメニュー2つのポップアップの縦位置でした。舞台のCSSがウィジェットを画面中央に揃えるため、ベルやアバターがキャンバス中央に来て、その下に開くパネルが300pxの舞台の底より下にはみ出し、スクロールバーが出てしまいました。中央揃えではなく上揃えに変えて、ようやくパネル全体が1画面に収まりました。幅320px・縦が短い画面ではさらに厳しく、パネルの見出しと説明文まで全部入れると縦200pxに収まらず、狭い画面では見出しと通知の説明文だけを隠し、太字の項目名だけ残す調整を別に行いました。ドーナツチャートは最初SVGの弧2本で描こうとしましたが、開始角度をそろえる計算がずれ続けたため、conic-gradient@propertyに切り替えてから、値ひとつの変更だけで滑らかに切り替わるようになりました。並び替え表は最初、行ごとに背景色が順番に流れる作りにしましたが、実測すると隣り合うフレーム間の変化がほとんどなく「動いていない」と判定されました。表の上から下へ実際に移動するハイライト帯1本に変えたところ、この9個の圧縮ファイルのパスワードrzae7dqhで開いた最新のzipでは、表も他の8個と同じくはっきり動きます。

アクセシビリティ

9個すべて prefers-reduced-motion: reduce では装飾用の繰り返しアニメーション(サイドバーの自動開閉、スパークラインの自動描画、フィルターチップの自動押下、ベルとアバターの淡いパルス)を止めますが、実際の状態値はそのまま残します。通知パネル(08)とユーザーメニュー(09)は role="menu"role="menuitem" を備え、外側クリックやEscで閉じます。設定フォーム(07)のトグルは role="switch"aria-checked を持ち、Enterとスペースキーだけで操作できます。並び替え表(03)のボタンは aria-sort で今の並び順を伝え、空状態(06)で追加されたカードは aria-hidden を実際に切り替えて、見えないカードをスクリーンリーダーが読まないようにしています。この実装はMDNのARIAウィジェットロール文書を参考にしました。

ほかのテンプレート素材はテンプレートカテゴリーに、このサイト全体の内容は紹介ページにあります。

FAQ

完成した管理画面テンプレートではなく、このウィジェット1個だけ使ってもいいですか?

はい、それがこの9個を作った理由です。どのデモもページ全体ではなくウィジェット1個だけを画面に置いていて、zipの中にもその1個分のHTML・SCSS・JSだけが入っているので、自分のプロジェクトのサイドバーやヘッダーにそのまま移せます。

React版はCSS版と動きが違いますか?

動きは同じです。開閉や選択のような状態は useState で持ち、変数3つ(durationeasingcolor)はpropsで受け取ってCSSカスタムプロパティに渡します。ドーナツのカウントアップは useEffect の中で requestAnimationFrame を使って同じ計算を繰り返します。

通知バッジの数字やサイドバーの折りたたみは実際に動いていますか、それとも装飾ですか?

実際に動いています。通知を押すとその項目の未読状態が実際に変わり、バッジは残りの件数を数え直して表示します。サイドバーのボタンも幅の値を実際に変えます。自動再生のGIFで見える繰り返しの動きは、人が触る前だけ回る装飾ループで、クリックやタップした瞬間に止まります。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。