<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::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);
});
})
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.