Yes, you can absolutely create a compelling hierarchy tree from a nested <ul>
structure using pure CSS! It's a common and fun challenge. Your current approach is already very close; the key is to fine-tune the positioning and dimensions of the pseudo-elements (::before
and ::after
) to draw those connecting lines accurately.
Here's a refined CSS solution that should give you the desired command-line tree look, along with an explanation of the adjustments.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Hierarchy Tree</title>
<style>
body {
font-family: monospace; /* For that terminal-like feel */
color: #333;
padding: 20px;
}
ul {
list-style: none;
margin: 0;
padding-left: 20px; /* Adjust as needed for initial indent */
position: relative;
}
li {
position: relative;
padding-left: 20px; /* Space for the horizontal line and text */
margin-bottom: 5px; /* Small space between items */
line-height: 1.5; /* Vertical spacing for text */
}
/* Horizontal line for each item */
li::before {
content: '';
position: absolute;
top: 0.75em; /* Adjust to align with the text baseline */
left: -5px; /* Pull the line slightly to the left to connect with parent's vertical line */
width: 15px; /* Length of the horizontal line */
height: 0;
border-top: 1px solid #999;
}
/* Vertical line for connecting child branches */
li::after {
content: '';
position: absolute;
top: 0.75em; /* Start from the same height as the horizontal line */
left: -5px; /* Align with the horizontal line start */
height: calc(100% + 5px); /* Extend beyond the current li to connect to siblings/next parent */
border-left: 1px solid #999;
}
/* Hide vertical line for the last child in a branch */
li:last-child::after {
height: 0.75em; /* Only extend to the horizontal line of the current li */
}
/* Remove the initial horizontal line for the very first item (if desired) */
ul:first-child > li:first-child::before {
border-top: none;
}
/* Remove the initial vertical line for the very first item (if desired) */
ul:first-child > li:first-child::after {
border-left: none;
}
/* Style for the nested ULs to create proper alignment */
li > ul {
padding-left: 20px; /* Indent for nested lists */
margin-top: 5px; /* Adjust spacing between parent and child UL */
}
/* Special handling for the vertical line coming *down* from a parent */
li:not(:last-child) > ul::before {
content: '';
position: absolute;
top: -5px; /* Align with the bottom of the parent's vertical line */
left: 0;
height: 10px; /* Length of the connecting vertical line */
border-left: 1px solid #999;
}
</style>
</head>
<body>
<ul>
<li>1830 Palmyra
<ul>
<li>1837 Kirtland
<ul>
<li>1840 Nauvoo
<ul>
<li>1841 Liverpool
<ul>
<li>1849 Liverpool
<ul>
<li>1854 Liverpool
<ul>
<li>1871 SaltLakeCity
<ul>
<li>1877 SaltLakeCity</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>1842 Nauvoo
<ul>
<li>1858 NewYork
<ul>
<li>1874 Iowa
<ul>
<li>2013 SaltLakeCity</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
</html>
Let's break down the key adjustments and the thought process behind them:
ul
Padding:
padding-left: 20px;
on the ul
is crucial. This creates the initial indentation for each level and gives space for the vertical connecting lines.li
Positioning and Padding:
position: relative;
on the li
is still essential for positioning its pseudo-elements.
padding-left: 20px;
on the li
provides the necessary spacing for the ::before
(horizontal line) and the visual "indent" for the list item's text.
margin-bottom: 5px;
and line-height: 1.5;
are for better visual spacing between list items.
li::before
(Horizontal Line):
top: 0.75em;
: This value is critical for vertically aligning the horizontal line with the text of the list item. 0.75em
often works well to place it roughly in the middle of a typical line of text.
left: -5px;
: This slight negative left
value pulls the horizontal line a bit to the left. This allows it to overlap and connect perfectly with the vertical line coming down from the parent li
(or the vertical line of a sibling li
's ::after
pseudo-element).
width: 15px;
: This defines the length of the horizontal line extending from the vertical connector to the list item's text. You can adjust this to control how far the "└───" part extends.
li::after
(Vertical Line):
top: 0.75em;
: Similar to ::before
, this ensures the vertical line starts at the same vertical position as the horizontal line.
left: -5px;
: Aligns it precisely with the horizontal line's starting point.
height: calc(100% + 5px);
: This is a significant change!
100%
makes the line extend to the bottom of the current li
.
Adding + 5px
(or a similar small value) makes it slightly overshoot the current li
. This overshoot is vital for connecting neatly with the next sibling li
's horizontal line. Without it, you'd often see a small gap.
li:last-child::after
: For the last child in a branch, we don't want the vertical line to extend indefinitely. Setting its height
to 0.75em
(or top
's value) makes it just reach the horizontal line, effectively stopping the branch.
ul:first-child > li:first-child::before/::after
(Optional Cleanup):
1830 Palmyra
in your example). This makes the tree start cleanly without extraneous lines.li > ul
Indentation:
padding-left: 20px;
on li > ul
ensures that nested ul
elements are further indented, creating the visual hierarchy.
margin-top: 5px;
adds a little breathing room between a parent item and its nested child list.
li:not(:last-child) > ul::before
(Connecting Vertical Line for Nested ULs):
This is a crucial addition to correctly render the vertical line between a parent item and its child ul
when the parent is not the last child itself.
It creates a short vertical line (height: 10px;
) that effectively bridges the gap from the parent's li::after
down to the ul
's content, maintaining the continuous vertical branch.
top: -5px;
adjusts its position to connect seamlessly.
Line Thickness & Color: Easily change border-top
and border-left
values for 1px solid #999
to 2px dashed #007bff
or whatever suits your design.
Spacing: Adjust padding-left
on ul
and li
, margin-bottom
on li
, and margin-top
on li > ul
to control the horizontal and vertical density of your tree.
Em vs. Px: Using em
for top
values and px
for width
and height
gives you a good balance. em
values scale with font size, which is good for vertical alignment with text, while px
gives precise control over line lengths.
Accessibility: While this is a visual representation, remember that the underlying HTML <ul>
structure is inherently accessible and semantically correct for lists. This CSS merely enhances the presentation.
With these adjustments, you should achieve a much cleaner and more accurate visual representation of your timeline/tree structure using only HTML and CSS. Give it a try! You'll love the results.