Skip to main content

CSS step arrows

Submitted by callum on 21 Sep 2012
work css

Recently had to make a multi step form which had a step navigation and an arrow that points in the direction of travel, so you could see how far you had got through the form. Normally I would have used images to generate the arrows, but as there was a good chance that both the size of the text and the colours would change, I wanted to do a CSS version that would remove the tedious image updates that would have followed. Due to the way the form was set up, I used JQuery for much of the element control.

An example which is set to show 'step 2' as the current, can be seen here: CSS Arrow Example: Completed Version.

To set up you need an unordered list wrapped in a div:

<div class="multi-form-steps">
  <ul class="step-4">
    <li>Step 1</li>
    <li>Step 2</li>
    <li>Step 3</li>
    <li>Step 4</li>
  </ul>
</div>

The div is there simply to add a wrapper to the list so that styles are applied only to this list and no others that may be on the page. The list items need to be in-line, which is dealt with in the .multi-form-steps ul li definition below, but if there is white space between the closing and next opening <li> then a gap between the two elements will be shown, which is not what is required in this setup. [CSS Arrow Example: Version: with gaps between the <li> tags]

There are several solutions to fix this

  • CSS: use a -4px margin on the <li>
  • CSS: set the parent font size to 0, e.g. wrap the li text in a span and:
    ul{font-size:0;}
    ul li span {font-size:16px;}
  • HTML: making sure there is no white space between the tags: </li><li>.
    As I had control over the tags, this is the option I took.

The ul is set up to remove the list bullet points and to make sure the list always fills the parent wrapper.

.multi-form-steps ul{
  list-style: none outside none;
  width:100%;
  padding:0;
  margin:0 !important;
}

The list items are then shown in-line using:

.multi-form-steps ul li{
  display: inline-block;
  background:#BF9B6B;
  position:relative;
  height:36px; /* double arrow wrapper height */
  overflow: hidden;
}

This also sets the background colour to the default and the overall height of the blocks. The height of the block is double the height of the arrow wrapper setting.

The width of each block is determined by the class added to the ul tag. in this case it is .step-4:

.multi-form-steps ul.step-4 li{
  width:25%;
}

By keeping these as separate css statements, the width of the <li> can be quickly adjusted depending on how many there are.

The text shown in the block needs to be inline and middle aligned with the arrows. The height of the block is also double the height of the arrow wrapper setting.

.multi-form-steps ul li span.step-text{
  padding:0 10px;
  line-height:100%;
  color:#fff;
  display:inline-block;
  vertical-align:middle;
  margin-top:10px;
  height:36px; /* double arrow wrapper height */
  overflow: hidden;
}

When a block is active, it's colour changes by the addition of the .active class to the current <li>:

<li class="active">Step 2</li>
.multi-form-steps ul li.active{
  background:#452E13;
}

To set up the arrows, some spans need to be added. There are 3 nested spans as folllows:

<span class="arrow-background">
  <span class="arrow-wrapper">
    <span class="arrow"></span>
  </span>
</span>

The .arrow-background class moves the arrow block to the right of the text:

.multi-form-steps ul li .arrow-background{
  position:absolute;
  display:inline-block;
  right:0;
}

And matches the arrow background colour when the block is active:

.multi-form-steps ul li.active .arrow-background{
  background:#BF9B6B;
}

The .arrow-wrapper class sets up the keyline around the arrow that matches the background. The arrow is constructed as follows:

border-top: 18px solid transparent;
border-bottom: 18px solid transparent;
border-left: 18px solid #FFF;

Which creates an arrow pointing to the right. To change the direction of the arrow, the direction side is not set. The opposite side is set to the colour setting and the remaining two sides are set to solid transparent. The height of the arrow should be set to half the final size. The final full size is set in the li & .step-text css settings.

.multi-form-steps ul li span.arrow-wrapper{
  position:relative;
  display:inline-block;
  vertical-align:middle;
  width: 0;
  height: 0;
  border-top: 18px solid transparent;
  border-bottom: 18px solid transparent;
  border-left: 18px solid #FFF;
}

As the last step did not require an arrow, then the last li had a .last class added and the arrow was set to be the same colour as the background:

<li class="last">Step 4</li>

.multi-form-steps ul li.last span.arrow-wrapper{
  border-left: 18px solid #BF9B6B;
}

The arrow that matches the background colour is set in the same way, but the size is slightly smaller to allow for the keyline arrow to show. To give a 1px keyline, the size of the border and the height/width of the sapn is 2px smaller than the size of the .arrow-wrapper which provides the keyline arrow. With absolute positioning the arrow span must be moved up this amount and to the right the full amount of the border-left setting in the .arrow-wrapper. This moves the background matched arrow block over the keyline arrow block to give the keyline.

.multi-form-steps ul li span.arrow{
  display:block;
  position:absolute;
  top:-16px; 
  left:-18px; 
  width: 0;
  height: 0;
  border-top: 16px solid transparent;
  border-bottom: 16px solid transparent;
  border-left: 16px solid #BF9B6B;
}

The colours are switched using the .active class and the last arrow keyline is set to the active background colour to hide it.

.multi-form-steps ul li.active span.arrow,
.multi-form-steps ul li.last.active span.arrow-wrapper {
  border-left: 16px solid #452E13;
}

.multi-form-steps ul li.last.active span.arrow-wrapper,
.multi-form-steps ul li.last.active .arrow-background{
  background:#452E13 !important;
}

Finally there is the problem of the background to the right of the arrow not matching the active colour of the current arrow [as can be seen here: Version: without the previous-active class]

My solution was to add the .previous-active class to the last <li> and set the background colour as below.

<li class="previous-active">Step 1</li>

.multi-form-steps ul li.previous-active .arrow-background{
  background:#452E13;
}

The Full CSS & HTML:

/* multistep form - steps */
.multi-form-steps ul{
  list-style: none outside none;
  width:100%;
  padding:0;
  margin:0 !important;
}
.multi-form-steps ul li{
  display: inline-block;
  background:#BF9B6B;
  position:relative;
  height:36px; /* double arrow wrapper height */
  overflow: hidden;
}
.multi-form-steps ul li.previous-active .arrow-background,
.multi-form-steps ul li.active{
  background:#452E13;
}
.multi-form-steps ul li .arrow-background{
  position:absolute;
  display:inline-block;
  right:0;
}
.multi-form-steps ul li.active .arrow-background{
  background:#BF9B6B;
}
.multi-form-steps ul li span.step-text{
  padding:0 10px;
  line-height:100%;
  color:#fff;
  display:inline-block;
  vertical-align:middle;
  margin-top:10px;
  height:36px; /* double arrow wrapper height */
  overflow: hidden;
}
.multi-form-steps ul li span.arrow-wrapper{
  position:relative;
  display:inline-block;
  vertical-align:middle;
  width: 0;
  height: 0;
  border-top: 18px solid transparent;
  border-bottom: 18px solid transparent;
  border-left: 18px solid #FFF;
}
.multi-form-steps ul li.last span.arrow-wrapper{
  border-left: 18px solid #BF9B6B;
}
.multi-form-steps ul li span.arrow{
  display:block;
  position:absolute;
  top:-16px; /* height of THIS arrow - arrow wrapper - 2px */
  left:-18px; /* width of arrow-wrapper */
  width: 0;
  height: 0;
  border-top: 16px solid transparent;/* arrow wrapper - 2px */
  border-bottom: 16px solid transparent;/* arrow wrapper - 2px */
  border-left: 16px solid #BF9B6B;/* arrow wrapper - 2px */
}
.multi-form-steps ul li.active span.arrow,
.multi-form-steps ul li.last.active span.arrow-wrapper {
  border-left: 16px solid #452E13;
}
.multi-form-steps ul li.last.active span.arrow-wrapper,
.multi-form-steps ul li.last.active .arrow-background{
  background:#452E13 !important;
}
/* set the width for the 4 step bar */
.multi-form-steps ul.step-4 li{
  width:25%;
}
<div class="multi-form-steps">
  <ul class="step-4">
    <li class="previous-active">
      <span class="step-text">Step 1</span>
      <span class="arrow-background">
        <span class="arrow-wrapper">
          <span class="arrow"></span>
        </span>
      </span>
    </li>
    <li class="active">
      <span class="step-text">Step 2</span>
      <span class="arrow-background">
        <span class="arrow-wrapper">
          <span class="arrow"></span>
        </span>
      </span>
    </li>
    <li>
      <span class="step-text">Step 3</span>
      <span class="arrow-background">
        <span class="arrow-wrapper">
          <span class="arrow"></span>
        </span>
      </span>
    </li>
    <li class="last">
      <span class="step-text">Step 4</span>
      <span class="arrow-background">
        <span class="arrow-wrapper">
          <span class="arrow"></span>
        </span>
      </span>
    </li>
  </ul>
</div>