UI Tree
Output
-
Level 1 Item 1
-
Level 2.1 Item 1
-
Level 2.1 Item 2
-
Level 2.1 Item 3
-
Level 3.1 Item 1
-
Level 3.1 Item 2
-
Level 3.1 Item 3
-
Level 1 Item 2
-
Level 2.2 Item 1
-
Level 2.2 Item 2
-
Level 2.3 Item 1
-
Level 2.3 Item 2
-
Level 2.3 Item 3
-
Level 2.2 Item 3
-
Level 1 Item 3
-
Level 2.3 Item 1
-
Level 2.3 Item 2
-
Level 2.3 Item 3
-
Level 3.3 Item 1
-
Level 3.3 Item 2
-
Level 3.3 Item 3
Code
<style>
/* just to make it easier to see */
ul.thread {
--_thread-border-width: 2px;
--_thread-border-color: var(--primary);
--_thread-border-color: var(--primary);
--_thread-border-inactive-color: oklch(from var(--primary) calc(l * 0.65) calc(c * 0.85) h);
--_thread-line-vertical-gap: 1rem;
--_thread-line-horizontal-gap: 3rem;
--_thread-root-size: 2rem;
--_content-padding: 1rem;
--_content-border-radius: 1.2rem;
--_easing:
linear(
0, 0.009 1.4%, 0.032 2.8%, 0.131 6%, 0.265 9.1%,
0.675 17.6%, 0.88 22.8%, 0.953 25.2%, 1.014 27.7%,
1.062 30.3%, 1.094 32.9%, 1.121 37.2%, 1.121 42.2%,
1.102 46.7%, 1.019 61.2%, 0.989 71.5%, 0.985 81.1%, 1
);
--_duration: 1300ms;
margin: 0;
padding: 0;
list-style: none;
anchor-name: --thread;
anchor-scope: --thread;
/*
Root node
*/
&:before {
content: "";
width: var(--_thread-root-size);
height: var(--_thread-root-size);
border-radius: 100vw;
display: block;
background: var(--secondary);
}
ul {
margin: 0;
padding: 0 0 0 var(--_thread-line-horizontal-gap);
list-style: none;
margin-block-start: var(--_thread-line-vertical-gap);
}
li {
margin: 0;
padding: 0;
&:not(:first-child) {
margin-block-start: var(--_thread-line-vertical-gap);
}
}
> li {
anchor-name: --ground;
anchor-scope: --ground;
padding-inline-start: var(--_thread-line-horizontal-gap);
&:before,
&:after {
position: absolute;
content: "";
border-style: solid;
border-width:
0
0
var(--_thread-border-width)
var(--_thread-border-width);
border-color:
transparent
transparent
var(--_thread-border-color)
var(--_thread-border-color);
border-radius:
0
0
0
calc(var(--_content-border-radius) / 2);
top: calc(anchor(top --thread) + var(--_thread-root-size) );
left: calc(anchor(left --ground) + var(--_thread-root-size) / 2);
/*bottom: anchor(center --branch);*/
/*
limitation on first level must be 1 row of text
and use line-height to calculate the vertical offset
*/
bottom: calc(anchor(top --ground) - 1.5em / 2 - var(--_thread-line-vertical-gap));
right: calc(anchor(left --ground) - var(--_thread-line-horizontal-gap) );
}
&:before {
/*
tweak to lighter color
*/
--_thread-border-color: var(--_thread-border-inactive-color);
z-index: -2;
}
&:after {
z-index: -1;
opacity: 0;
}
&:has(:hover,:focus):after {
opacity: 1;
}
}
.branch {
padding: var(--_content-padding);
margin-block-start: var(--_thread-line-vertical-gap);
background: var(--secondary);
border-radius: var(--_content-border-radius);
anchor-name: --branch;
anchor-scope: --branch;
&:not(:last-child) {
margin-block-end: var(--_thread-line-vertical-gap);
}
}
.nodes {
anchor-name: --nodes;
anchor-scope: --nodes;
isolation: isolate;
>li:has(.nodes>li>.node:hover,.nodes>li>.node:focus) {
>.node:after {
opacity: 1;
}
}
}
.node {
margin: 0;
padding: var(--_content-padding);
anchor-name: --node;
anchor-scope: --node;
border-radius: var(--_content-border-radius);
background: oklch(from var(--secondary) calc(l * 0.85) calc(c * 0.5) h);
&:before,
&:after {
position: absolute;
content: "";
border-style: solid;
border-width:
0
0
var(--_thread-border-width)
var(--_thread-border-width);
border-color:
transparent
transparent
var(--_thread-border-color)
var(--_thread-border-color);
border-radius: 0 0 0 calc(var(--_content-border-radius) / 2);
top: calc(anchor(top --nodes) - var(--_thread-line-vertical-gap));
left: calc(anchor(left --nodes) + var(--_thread-line-horizontal-gap) / 2);
bottom: anchor(center --node);
right: anchor(left --node);
/*transition: all var(--_duration) var(--_easing);*/
transition-property: opacity;
transition-duration: var(--_duration);
transition-easing: var(--_easing);
}
&:before {
/*
tweak to lighter color
*/
--_thread-border-color: var(--_thread-border-inactive-color);
z-index: -2;
}
&:after {
z-index: -1;
opacity: 0;
}
&:is(:hover,:focus):after {
opacity: 1;
}
}
}
</style>
<ul class="thread">
<li>
<div class="branch">Level 1 Item 1</div>
<ul class="nodes">
<li>
<div class="node">Level 2.1 Item 1</div>
</li>
<li>
<div class="node">Level 2.1 Item 2</div>
</li>
<li>
<div class="node">Level 2.1 Item 3</div>
<ul class="nodes">
<li>
<div class="node">Level 3.1 Item 1</div>
</li>
<li>
<div class="node">Level 3.1 Item 2</div>
</li>
<li>
<div class="node">Level 3.1 Item 3</div>
</li>
</ul>
</li>
</ul>
</li>
<li>
<div class="branch">Level 1 Item 2</div>
<ul class="nodes">
<li>
<div class="node">Level 2.2 Item 1</div>
</li>
<li>
<div class="node">Level 2.2 Item 2</div>
<ul class="nodes">
<li>
<div class="node">Level 2.3 Item 1</div>
</li>
<li>
<div class="node">Level 2.3 Item 2</div>
</li>
<li>
<div class="node">Level 2.3 Item 3</div>
</li>
</ul>
</li>
<li>
<div class="node">Level 2.2 Item 3</div>
</li>
</ul>
</li>
<li>
<div class="branch">Level 1 Item 3</div>
<ul class="nodes">
<li>
<div class="node">Level 2.3 Item 1</div>
</li>
<li>
<div class="node">Level 2.3 Item 2</div>
</li>
<li>
<div class="node">Level 2.3 Item 3</div>
<ul class="nodes">
<li>
<div class="node">Level 3.3 Item 1</div>
</li>
<li>
<div class="node">Level 3.3 Item 2</div>
</li>
<li>
<div class="node">Level 3.3 Item 3</div>
</li>
</ul>
</li>
</ul>
</li>
</ul>