<details>で作る Tree View デモ集

1. 基本:CSSなし

<details><summary> の標準挙動だけで階層を表現する。

  • ドキュメント
    • 概要
    • ガイド
      • 導入
      • 設定
      • 公開
  • README
<ul>
  <li>
    <details open>
      <summary>ドキュメント</summary>
      <ul>
        <li>概要</li>
        <li>
          <details open>
            <summary>ガイド</summary>
            <ul>
              <li>導入</li>
              <li>設定</li>
              <li>公開</li>
            </ul>
          </details>
        </li>
      </ul>
    </details>
  </li>
  <li>README</li>
</ul>
/* なし */

2. CSS:最低限の調整

標準の開閉挙動は残し、余白と縦線だけで読みやすくする。

マーカーはブラウザ標準のまま使い、階層の深さだけを線で補助する。ホバーやアイコンを入れないため、本文中の小さな目次や設定一覧にも馴染みやすい。

  • ドキュメント
    • 概要
    • ガイド
      • 導入
      • 設定
      • 公開
  • README
<ul class="tree-minimal">
  <li>
    <details open>
      <summary>ドキュメント</summary>
      <ul>
        <li><span class="leaf">概要</span></li>
        <li>
          <details open>
            <summary>ガイド</summary>
            <ul>
              <li><span class="leaf">導入</span></li>
              <li><span class="leaf">設定</span></li>
              <li><span class="leaf">公開</span></li>
            </ul>
          </details>
        </li>
      </ul>
    </details>
  </li>
  <li><span class="leaf">README</span></li>
</ul>
.tree-minimal {
  --line: #d7dde8;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.95rem;
}

.tree-minimal ul {
  margin: 0.15rem 0 0.15rem 1rem;
  padding-left: 0.85rem;
  list-style: none;
  border-left: 1px solid var(--line);
}

.tree-minimal li {
  margin: 0.08rem 0;
}

.tree-minimal summary,
.tree-minimal .leaf {
  display: inline-flex;
  align-items: center;
  min-height: 1.8rem;
  padding: 0.12rem 0.45rem;
  border-radius: 0.5rem;
}

.tree-minimal summary {
  cursor: pointer;
  font-weight: 650;
}

.tree-minimal summary::marker {
  color: #8a95a8;
}

3. ファイルビュー風

フォルダとファイルの見た目を追加し、エディタのファイルツリーに近づける。

summary::before で開閉マーカーを置き換え、ファイル種別はクラスごとの疑似要素で表す。実際のプロジェクトでは拡張子や種類に応じてアイコンを差し替えるだけで流用できる。

  • src
    • components
      • TreeView.html
      • TreeView.css
    • main.js
    • style.css
  • package.json
  • README.md
<ul class="file-tree">
  <li>
    <details open>
      <summary><span class="folder-icon"></span>src</summary>
      <ul>
        <li>
          <details open>
            <summary><span class="folder-icon"></span>components</summary>
            <ul>
              <li><span class="file html">TreeView.html</span></li>
              <li><span class="file css">TreeView.css</span></li>
            </ul>
          </details>
        </li>
        <li><span class="file js">main.js</span></li>
        <li><span class="file css">style.css</span></li>
      </ul>
    </details>
  </li>
  <li><span class="file json">package.json</span></li>
  <li><span class="file md">README.md</span></li>
</ul>
.file-tree {
  --indent: 1.25rem;
  --line: #d7dce5;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.95rem;
}

.file-tree ul {
  margin: 0 0 0 var(--indent);
  padding: 0 0 0 0.65rem;
  list-style: none;
  border-left: 1px solid var(--line);
}

.file-tree summary,
.file-tree .file {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 1.9rem;
  padding: 0.2rem 0.5rem;
  border-radius: 0.55rem;
}

.file-tree summary {
  cursor: pointer;
  font-weight: 700;
}

.file-tree summary::marker {
  content: "";
}

.file-tree summary::before {
  content: "▸";
  color: #687386;
  font-size: 0.78rem;
}

.file-tree details[open] > summary::before {
  content: "▾";
}

.file-tree .folder-icon::before {
  content: "📁";
}

.file-tree details[open] > summary .folder-icon::before {
  content: "📂";
}

.file-tree .file.html::before { content: "🌐"; }
.file-tree .file.css::before { content: "🎨"; }
.file-tree .file.js::before { content: "📜"; }
.file-tree .file.json::before { content: "{}"; font-weight: 800; }
.file-tree .file.md::before { content: "📝"; }

5. アニメーション付き

新しめのCSSである ::details-contentinterpolate-size を使い、JSなしで展開と収納を動かす。

<details> は閉じる瞬間に中身が非表示になるため、従来は高さのアニメーションにJavaScriptを使う場面が多かった。この作例ではCSS側で中身の領域を扱い、開く動きと閉じる動きの両方を補間する。

  • Design System
    • Colors
    • Typography
    • Components
      • Button
      • Card
      • Dialog
<ul class="animated-tree">
  <li>
    <details open>
      <summary>Design System</summary>
      <ul>
        <li><span class="leaf">Colors</span></li>
        <li><span class="leaf">Typography</span></li>
        <li>
          <details>
            <summary>Components</summary>
            <ul>
              <li><span class="leaf">Button</span></li>
              <li><span class="leaf">Card</span></li>
              <li><span class="leaf">Dialog</span></li>
            </ul>
          </details>
        </li>
      </ul>
    </details>
  </li>
</ul>
.animated-tree {
  interpolate-size: allow-keywords;
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.35rem;
}

.animated-tree ul {
  margin: 0 0 0 1.1rem;
  padding-left: 0.9rem;
  list-style: none;
  border-left: 1px solid #d6ddeb;
}

.animated-tree summary {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: fit-content;
  padding: 0.35rem 0.55rem;
  border-radius: 10px;
  cursor: pointer;
  font-weight: 760;
}

.animated-tree summary::marker {
  content: "";
}

.animated-tree summary::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid #728096;
  border-bottom: 2px solid #728096;
  transform: rotate(-45deg);
  transition: transform 180ms ease;
}

.animated-tree details[open] > summary::before {
  transform: rotate(45deg);
}

.animated-tree details::details-content {
  block-size: 0;
  overflow: clip;
  opacity: 0;
  transition:
    block-size 240ms ease,
    opacity 180ms ease,
    content-visibility 240ms ease allow-discrete;
}

.animated-tree details[open]::details-content {
  block-size: auto;
  opacity: 1;
}

.animated-tree .leaf {
  display: block;
  width: fit-content;
  padding: 0.28rem 0.55rem;
  border-radius: 10px;
}

.animated-tree summary:hover,
.animated-tree .leaf:hover {
  background: #f1f5ff;
}

@media (prefers-reduced-motion: reduce) {
  .animated-tree * {
    transition: none !important;
  }
}

6. 高度な選択UI

チェックボックス、メタ情報、区切り線を合わせた管理画面向けのツリー。権限設定やフィルタ条件に転用できる。

親ノードは開閉、子ノードは選択という役割に分けている。行レイアウトにはCSS Gridを使い、ラベル、件数、チェックボックスの位置を揃えている。

  • Products3 groups
  • Teams2 groups
<ul class="advanced-tree">
  <li>
    <details open>
      <summary><span>Products</span><span class="meta">3 groups</span></summary>
      <ul>
        <li class="child">
          <label class="node">
            <input type="checkbox" checked />
            <span>Analytics</span>
            <span class="meta">12</span>
          </label>
        </li>
        <li class="child">
          <label class="node">
            <input type="checkbox" />
            <span>Billing</span>
            <span class="meta">8</span>
          </label>
        </li>
      </ul>
    </details>
  </li>
</ul>
.advanced-tree {
  margin: 0;
  padding: 0;
  list-style: none;
  max-width: 520px;
  border: 1px solid #d7dfed;
  border-radius: 16px;
  background: #fff;
  overflow: hidden;
}

.advanced-tree ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.advanced-tree summary,
.advanced-tree .node {
  display: grid;
  align-items: center;
  gap: 0.65rem;
  min-height: 2.6rem;
  padding: 0.42rem 0.75rem;
}

.advanced-tree summary {
  grid-template-columns: auto 1fr auto;
  cursor: pointer;
  font-weight: 780;
}

.advanced-tree .node {
  grid-template-columns: auto 1fr auto;
}

.advanced-tree .child .node {
  padding-left: 2rem;
}

.advanced-tree summary::marker {
  content: "";
}

.advanced-tree summary::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid #65738a;
  border-bottom: 2px solid #65738a;
  transform: rotate(-45deg);
  transition: transform 160ms ease;
}

.advanced-tree details[open] > summary::before {
  transform: rotate(45deg);
}

.advanced-tree .meta {
  color: #69758a;
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
}

.advanced-tree input[type="checkbox"] {
  accent-color: #2f6fed;
  width: 1rem;
  height: 1rem;
  margin: 0;
}

.advanced-tree summary:hover,
.advanced-tree .node:hover {
  background: #f2f6ff;
}