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>