skip to Main Content

I’m working on a web layout where I need to display a series of steps connected by a line. I’ve set up a flexbox layout with spheres representing each step and a line connecting them. However, there’s too much space horizontally between the line and the spheres, while I want to bring the line closer to the spheres. Ideally it should be only a few pixels between them.

Here’s the HTML and CSS code I’m using:

.steps-container,
.label-container {
  display: flex;
  flex-direction: row;
  justify-content: center;
}

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.sphere {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background-color: lightgrey;
  color: white;
  font-size: 32px;
}

.line {
  position: relative;
  top: 25px;
  height: 3px;
  background-color: lightgrey;
  flex-grow: 1;
  max-width: 150px;
}

.label {
  text-align: center;
  position: relative;
}

.spacer {
  flex-grow: 1;
  max-width: 150px;
}
<div class="steps-container">
  <div class="step">
    <div class="sphere">1</div>
    <div class="label">First step label</div>
  </div>
  <div class="line">&nbsp;</div>
  <div class="step">
    <div class="sphere">2</div>
    <div class="label">Second step label</div>
  </div>
</div>

I suspect the problem lies in my .line CSS class.

I didn’t manage to resolve the problem and I’m starting to doubt it is even possible to do what I’m trying to do. I tried moving the line using position: relative but I can’t adjust the length of the line that way. I also tried using position: absolute but I just can’t get the result I’m expecting and I don’t really know how to position the line using absolute values correctly.

2

Answers


  1. The line is as wide as it can be with basic flexbox, if you put an outline on the step elements you will see the problem.

    enter image description here

    .steps-container,
    .label-container {
      display: flex;
      flex-direction: row;
      justify-content: center;
    }
    
    .step {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      outline: 1px solid red;
    }
    
    .sphere {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 50px;
      height: 50px;
      border-radius: 50%;
      background-color: lightgrey;
      color: white;
      font-size: 32px;
    }
    
    .line {
      position: relative;
      top: 25px;
      height: 3px;
      background-color: lightgrey;
      flex-grow: 1;
      max-width: 150px;
    }
    
    .label {
      text-align: center;
      position: relative;
    }
    
    .spacer {
      flex-grow: 1;
      max-width: 150px;
    }
    <div class="steps-container">
      <div class="step">
        <div class="sphere">1</div>
        <div class="label">First step label</div>
      </div>
      <div class="line">&nbsp;</div>
      <div class="step">
        <div class="sphere">2</div>
        <div class="label">Second step label</div>
      </div>
    </div>

    In addition, the label is forcing the steps to be wider than the 50px of the "sphere" as you can see in the snippet above.

    An option is to force the step to only be as wide as the sphere, which we can do by setting the width to 0 but adding a min-width of 100%.

    enter image description here

    .steps-container,
    .label-container {
      display: flex;
      flex-direction: row;
      justify-content: center;
    }
    
    .step {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      outline: 1px solid red;
    }
    
    .sphere {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 50px;
      height: 50px;
      border-radius: 50%;
      background-color: lightgrey;
      color: white;
      font-size: 32px;
    }
    
    .line {
      position: relative;
      top: 25px;
      height: 3px;
      background-color: lightgrey;
      flex-grow: 1;
      max-width: 150px;
      z-index: 2
    }
    
    .label {
      text-align: center;
      position: relative;
      width: 0;
      min-width: 100%;
    }
    
    .spacer {
      flex-grow: 1;
      max-width: 150px;
    }
    <div class="steps-container">
      <div class="step">
        <div class="sphere">1</div>
        <div class="label">First step label</div>
      </div>
      <div class="line">&nbsp;</div>
      <div class="step">
        <div class="sphere">2</div>
        <div class="label">Second step label</div>
      </div>
    </div>

    This leaves us with the problem of the label which we can solve by forcing nowrap and adjusting to recenter the text with a transform.

    .steps-container,
    .label-container {
      display: flex;
      flex-direction: row;
      justify-content: center;
    }
    
    .step {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    
    .sphere {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 50px;
      height: 50px;
      border-radius: 50%;
      background-color: lightgrey;
      color: white;
      font-size: 32px;
    }
    
    .line {
      position: relative;
      top: 25px;
      height: 3px;
      background-color: lightgrey;
      flex-grow: 1;
      max-width: 150px;
      z-index: 2
    }
    
    .label {
      text-align: center;
      position: relative;
      width: 0;
      min-width: 100%;
      white-space: nowrap;
      translate: -50%;
    }
    
    .spacer {
      flex-grow: 1;
      max-width: 150px;
    }
    <div class="steps-container">
      <div class="step">
        <div class="sphere">1</div>
        <div class="label">First step label</div>
      </div>
      <div class="line">&nbsp;</div>
      <div class="step">
        <div class="sphere">2</div>
        <div class="label">Second step label</div>
      </div>
    </div>
    Login or Signup to reply.
  2. I liked Paulie_D’s elegant solution and explanation, but it slightly shifts the label position and changes the bounding box of the steps.

    here is an alternative approach using JavaScript:

    function connect() {
        const sphereWidth = document.querySelector('.sphere').offsetWidth;
        const gap = 3;  // small gap between the spheres and the line 
        document.querySelectorAll('.steps-container').forEach((steps) => {
            const spaces = Array.from(steps.querySelectorAll('.step')).map((step) => (step.offsetWidth + sphereWidth) / 2 + gap);
            const line = steps.querySelector('.line');
            line.style.left = spaces[0] + 1 + 'px';
            line.style.right = spaces[1] + 'px';
        });
    }
    
    window.addEventListener('resize', connect);
    document.addEventListener('DOMContentLoaded', connect);
    .steps-container,
    .label-container {
        display: flex;
        flex-direction: row;
        justify-content: center;
        position: relative;
        width: fit-content;
        margin: 0 auto;
    }
    
    .step {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }
    
    .sphere {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 50px;
        height: 50px;
        border-radius: 50%;
        background-color: lightgrey;
        color: white;
        font-size: 32px;
    }
    
    .line-container {
        flex-grow: 1;
        min-width: 150px;
    }
    
    .line {
        position: absolute;
        background-color: lightgrey;
        height: 3px;
        top: 25px;
    }
    
    .label {
        text-align: center;
        position: relative;
    }
    <div class="steps-container">
        <div class="step">
            <div class="sphere">1</div>
            <div class="label">First step label</div>
        </div>
        <div class="line-container">
            <div class="line">&nbsp;</div>
        </div>
        <div class="step">
            <div class="sphere">2</div>
            <div class="label">Second step label</div>
        </div>
    </div>

    Explanation

    HTML: Wrapped the line inside a line-container to maintain the width and allow for absolute positioning.

    CSS: Positioned the line absolutely within the line-container.

    JavaScript: Calculates the space between the circles and positions the line dynamically. Event listeners ensure the line adjusts on window resize and when the DOM content is loaded.

    I hope it helps you.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search