Search code examples
htmlcssalignment

Dynamic horizontal list with variable width lines and content between nodes


I have a system that generates a list of nodes which may or may not have additional elements between the nodes. I want to visualize this list horizontally with lines connecting the nodes as shown in the example below.

The content between two nodes is variable and can even contain more complex elements like images and tables. The nodes themselves are labeled with only text (Point A, Point B, etc.).

Fig 1

Code & What I have tried so far

In my first attempt at trying to visualize the nodes I simply used a vertical list with the :before and :after CSS pseudo-elements to draw the lines between the nodes. I am however having difficulty translating this approach to a horizontal list.

Horizontal approach: (updated thanks to Mayank Gupta's answer below)

h1, h2 {
	margin: 0;
	padding: 0;
}
ul {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	flex-direction: row;
	text-align: center;
	list-style-type: none;
	font-size: 1.5em;
}
ul li {
	display: flex;
	justify-content: flex-start;
	flex: 2;
	position: relative;
	border-bottom: 2px solid #000;
	padding-bottom: 10px;
}
ul li:after {
	position: absolute;
	bottom: -5px;
	left: 0;
	font-family: "Font Awesome 5 Free";
	font-weight: 300;
	content: "\f111";
	height: 17px;
	width: 23px;
	background: #fff;
}
ul h2 {
	margin: 0 0 0 -1em;
	padding: 0;
}
ul li:last-of-type {
	border: none;
}
ul li > div {
	flex: 0 0 auto;
	position: absolute;
	left: 50%;
	transform: translateX(-50%);
}
<link href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" rel="stylesheet"/>
<ul>
	<li>
		<h2>Node A</h2>
		<div>Content</div>
	</li>
	<li>
		<h2>Node B</h2>
		<div>Content</div>
	</li>
	<li>
		<h2>Node C</h2>
	</li>
</ul>

Problems with this method:

  • All nodes are equal width causing the last node to take up space needlessly (last node never has any content to show), in effect this prevents the list from using all available horizontal space (or at least seemingly);
  • The line is sensitive to font-size changes causing it to misalign;
  • The node labels are centered using a negative offset, this probably isn't the best method;
  • The content div is taken out of the document flow in order to center it between the nodes, this potentially allows it to overlap the node labels as is the case in this example

How could I resolve these issues?

Constraints

  • I only support the latest version of modern browsers, so no compatibility is required for ancient browsers;
  • I prefer an HTML(5) and CSS only approach

Solution

  • Here is my answer - essentially you add display:flex to the ul list and make sure that the nodes have fixed size by using flex:0 0 auto and a fixed width. This would allow for the context between to be variable in size.

    Concerning the connection of the nodes, although it could be done by using the :before and :after pseudo-elements, there's an easier way to do it; just use a pseudo-element for the whole list (the ul element) that goes side-by-side along the width of the element (in the example actually left and right have a value of 30px to compensate the arbitrary side padding I set).

    HTML:

    <ul>
      <li class="node"><label>Point A</label></li>
      <li class="context"><span>Content here Content here Content here</span></li>
      <li class="node"><label>Point B</label></li>
      <li class="context"><span>Content here</span></li>
      <li class="node"><label>Point C</label></li>
    </ul>
    

    CSS:

    ul {
      list-style-type: none;
      display: flex;
      width: 100%;
      margin: 50px 0 0 0;
      padding: 0 30px;
      position: relative;
    }
    
    ul:before {
      content: "";
      display: block;
      position: absolute;
      left: 30px;
      right: 30px;
      top: 50%;
      margin-top: -1px;
      height: 2px;
      background: steelblue;
    }
    
    .node {
      flex: 0 0 auto;
      display: block;
      width: 12px;
      height: 12px;
      border-radius: 12px;
      border: 2px solid steelblue;
      position: relative;
      background: #fff;
    }
    
    .node label {
      position: absolute;
      top: -30px;
      left: 50%;
      transform: translateX(-50%);
      white-space: nowrap;
    }
    
    .context {
      flex: 1 1 auto;
    }
    
    .context span {
      display: block;
      margin-top: -15px;
      text-align: center;
    }
    

    Here is a working fiddle: https://jsfiddle.net/8ksxtz60/