<h1>Expandable tree views in CSS</h1>
<p>Based on an article by Kate Rose Morley: <a href="https://iamkate.com/code/tree-views/">Tree views in CSS</a></p>
<form>
  <fieldset>
    <legend>Parameters</legend>
    <div>
      <label>Font size (rems) <input type="number" value="1.25" step="0.25" name="font-size" data-unit="rem"></label>
    </div>
    <div>
      <label>Line height <input type="number" value="1.75" step="0.25" name="line-height"></label>
    </div>
    <div>
      <label>Marker radius (pixels) <input type="number" value="10" min="1" name="radius" data-unit="px"></label>
    </div>
    <div>
      <label>Line thickness (pixels) <input type="number" value="4" min="1" name="thickness" data-unit="px"></label>
    </div>
  </fieldset>
</form>
<ul class="tree">
  <li>
    <details open>
      <summary>Giant planets</summary>
      <ul>
        <li>
          <details open>
            <summary>Gas giants</summary>
            <ul>
              <li>Jupiter</li>
              <li>Saturn</li>
            </ul>
          </details>
        </li>
        <li>
          <details open>
            <summary>Ice giants</summary>
            <ul>
              <li>Uranus</li>
              <li>Neptune</li>
            </ul>
          </details>
        </li>
      </ul>
    </details>
  </li>
</ul>
/* Modified version of the code from this tutorial by Kate Rose Morley: https://iamkate.com/code/tree-views/ */

/* Just some reset/form styles */
*,
*::before,
*::after {
  box-sizing: border-box;
}
input {
  font: inherit;
}
fieldset {
  padding: 16px;
  display: grid;
  gap: 16px;
}
legend {
  font-weight: 700;
}
html {
  font-family: Georgia, serif;
}
body {
  max-width: 60ch;
  margin: 0 auto;
  padding: 8px;
}

/* Tree code starts here */
.tree {
  --font-size: 1.25rem;
  --line-height: 1.75;
  /* Spacing should match line height, so derive it */
  --spacing: calc(var(--line-height) * 1em);
  /* Allow thickness of the tree lines to vary */
  --thickness: 4px;
  --radius: 10px;
  line-height: var(--line-height);
  font-size: var(--font-size);
}

.tree li {
  display: block;
  position: relative;
  padding-left: calc(2 * var(--spacing) - var(--radius) - var(--thickness));
}

.tree ul {
  margin-left: calc(var(--radius) - var(--spacing));
  padding-left: 0;
}

.tree ul li {
  border-left: var(--thickness) solid blue;
}

.tree ul li:last-child {
  border-color: transparent;
}

.tree ul li::before {
  content: "";
  display: block;
  position: absolute;
  top: calc(var(--spacing) / -2);
  left: calc(-1 * var(--thickness));
  width: calc(var(--spacing) + var(--thickness));
  height: calc(var(--spacing) + var(--thickness) / 2);
  border: solid red;
  border-width: 0 0 var(--thickness) var(--thickness);
}

.tree summary {
  display: block;
  cursor: pointer;
}

.tree summary::marker,
.tree summary::-webkit-details-marker {
  display: none;
}

.tree summary:focus {
  outline: none;
}

.tree summary:focus-visible {
  outline: var(--thickness) dotted #000;
}

.tree li::after,
.tree summary::before {
  content: "";
  display: block;
  position: absolute;
  top: calc(var(--spacing) / 2 - var(--radius));
  left: calc(var(--spacing) - var(--radius) - var(--thickness) / 2);
  width: calc(2 * var(--radius));
  height: calc(2 * var(--radius));
  border-radius: 50%;
  background: #ddd;
}

.tree summary::before {
  content: "+";
  z-index: 1;
  background: #696;
  color: white;
  line-height: calc(2 * var(--radius));
  text-align: center;
}

.tree details[open] > summary::before {
  content: "−";
}
const form = document.querySelector('form');
const tree = document.querySelector('.tree');

Array.from(form.elements).forEach((input) => {
  input.addEventListener('input', (e) => {
    const rawValue = e.target.valueAsNumber;
    const unit = e.target.getAttribute('data-unit') ?? '';
    const propertyValue = `${rawValue}${unit}`;
    const propertyName = e.target.name;
    tree.style.setProperty(`--${propertyName}`, propertyValue);
  });
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.