Search code examples
htmlcssreactjstailwind-csstailwind-ui

Issues with alignment when trying to draw lines between circle shaped divs


I am trying to create a vertical navigation menu with a certain style (as shown in the image attached to this post). In the image, there are 3 vertically aligned circles the same size as the font, the circles are connected by vertical lines, each circle has text next to it.

The problem I am facing is that the line are not starting at the bottom end of the circles and are not centered horizontally to the circles, as shown in the image.

I am a novice when it comes to UI stuff, below is my code in React using Tailwind:

<div className="flex flex-col h-full w-1/5 mt-16">
  <div 
    className="h-[63rem] w-full flex flex-col align-center mt-8 border rounded-xl border-grey-300 inset-0 bg-custom-purple"
  >
    <h2 className="text-xl text-custom-black text-left mt-8 ml-10 poppins-semibold">
      Heading Text
    </h2>

    <div className="flex flex-col ml-14 mt-16 items-start">
      {menuItems.map((item, index) => (
        <div key={index} className="flex items-center mb-20 relative">
          <div className="w-4 h-4 rounded-full border-2 border-gray-500 bg-white flex justify-center"></div>
          {index !== menuItems.length - 1 && (
            <div className="absolute top-4 bottom-0 left-2 w-0.5 h-24 bg-gray-500"></div>
          )}
          <span className="ml-14 text-md font-medium text-gray-600">{item.label}</span>
        </div>
      ))}
    </div>
  </div>
</div>

Here are the menuItems for brevity:

const menuItems = [
    { label: 'Notifications', icon: faBell },
    { label: 'Email', icon: faEnvelope },
    { label: 'Phone', icon: faPhone },
];

Image:

enter image description here

Would appreciate any sort of help, thank you.


Solution

  • Instead of having multiple lines between the items, you could have one single line that spans across all of them.

    const menuItems = [
      { label: 'Notifications' },
      { label: 'Email' },
      { label: 'Phone' },
    ];
    
    ReactDOM.createRoot(document.getElementById('app')).render(
      <div className="flex flex-col h-full w-1/5 mt-16">
        <div 
          className="h-[63rem] w-full flex flex-col align-center mt-8 border rounded-xl border-grey-300 inset-0 bg-custom-purple"
        >
          <h2 className="text-xl text-custom-black text-left mt-8 ml-10 poppins-semibold">
            Heading Text
          </h2>
    
          <div className="flex flex-col ml-14 mt-16 items-start relative z-0">
            <div class="absolute -z-10 top-[max(theme(fontSize.base.1.lineHeight)-theme(height.4),0%)] bottom-[calc(theme(margin.20)+max(theme(fontSize.base.1.lineHeight)-theme(height.4),0%))] bg-gray-500 left-2 w-0.5 -translate-x-1/2"/>
            {menuItems.map((item, index) => (
              <div key={index} className="flex items-center mb-20 relative">
                <div className="w-4 h-4 rounded-full border-2 border-gray-500 bg-white flex justify-center"></div>
                <span className="ml-14 text-md font-medium text-gray-600">{item.label}</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha512-QVs8Lo43F9lSuBykadDb0oSXDL/BbZ588urWVCRwSIoewQv/Ewg1f84mK3U790bZ0FfhFa1YSQUmIhG+pIRKeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha512-6a1107rTlA4gYpgHAqbwLAtxmWipBdJFcq8y5S/aTge3Bp+VAklABm2LO+Kg51vOWR9JMZq1Ovjl5tpluNpTeQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.tailwindcss.com/3.4.3"></script>
    
    <div id="app"></div>

    We add relative to the outer flex parent so that it becomes a position parent. We have the element that draws the line with -z-10 so that it is stacked behind the items. The line element with -z-10 would actually draw behind the <body> element, so we add z-0 to the outer relative parent, so that the parent becomes a new stacking context, ensuring the line element is seen.

    There is no text-md class by default, and you have not provided a Tailwind configuration, so I'm going to ignore this class. Thus, I'm going to assume the text sizing is the default from Tailwind preflight. The element sizes would be from the line-height, since this default (1.5rem/24px) would be larger than the w-4, 1rem/16px. The circle element is positioned vertically centered in the middle of each item from the flex layout, so we need to calculate the distance between the top of the first element and the top of the circle. This is why we have top-[max(theme(fontSize.base.1.lineHeight)-theme(height.4),0%)] on the line element.

    For the bottom-[calc(theme(margin.20)+max(theme(fontSize.base.1.lineHeight)-theme(height.4),0%))], this involves the same calculation, max(theme(fontSize.base.1.lineHeight)-theme(height.4),0%). This is because the difference between the bottom of the circle and the bottom of the element is the same as the top difference since it is aligned in the center vertically. The theme(margin.20) is for the mb-20 on the last item. Both this bottom and the previous top calculations assume each item would only be a single line.

    For left-2 -translate-x-1/2, this centers the line horizontally with the circles. Just having left-2 would be incorrect, since this would align the left edge of the line with the center of the circles (like your attempt). Really, we want the center of the circles to be aligned with the center of the line. Thus we offset half of the width of the line to have it centered by applying translateX(-50%) from -translate-x-1/2. The 50% value is always relative to the dimensions of the element that you apply it to. Thus, if we choose to change the width of the line element, we don't need to update this class – it would already be 50% of the width.