The Art of Solving Problems with CSS

At my workplace, sometimes I’m tasked with creating impractical UIs such as this ribbon shown in the screenshot below. I see them as impractical since it is not your average card UIs or navigation bars that are easy to create. These types of problem requires a bit of thinking but with a systematic approach, you can get a glimpse of a possible solution easily.

Promo ribbon's mockup

When I received the mockup from my designers, I have 2 choices: use the ribbon as image (the easy way) or use CSS (the hard way). I love a good challenge and so I chose CSS and I will take a clever solution with CSS any day provided that it gives you a clean solution with good browser support. So how did I approach this problem? What’s the trick to solving problems with CSS?

Size Up Your Opponents Problems

In Boxing & MMA, the fighters often refer to their opponents as a problem. A puzzle that they are trying to solve. They would analyze each other before fight day during their respective training regime by watching fight videos of each other or train with sparring partners that can imitate the opponent’s fighting style. They do not attack the problem straight away. They take their time analyzing the opponent so that they can strategize and pick the best game plan in order to win.

We should think about approaching our problems in the same manner by analyzing it first before jumping on a solution straight away. As developers, sometimes we can get excited and start coding immediately without thinking more about the problem. At times, I would get too excited and start coding immediately. But this way of working would force me to try and get things to work rather than thinking about a solution that is better in the long run.

I think taking the time to understand the problem first is better since I get to brainstorm a list of potential solutions and then choose the best one that gives clean and maintainable code with good browser support.

From the mockup, I can see that there are two elements overlapping on top of each other. I can also see that these elements are rotated. With this information, I get to list a bunch of potential solutions that I can use and start mocking things up if needed.

If I find it difficult to understand the problem, I would start sketching the elements. Sketching each of the elements would let me break down the problem in pieces rather than thinking of it as a whole. This makes it easier for me to think of possible solutions in a shorter amount of time.

Picking the Best Tools for the Task

There is a tool for every task, and a task for every tool – Tywin Lannister, A Storm of Swords

In order to make sure that I come up with a maintainable solution, I started thinking about the markup of my element. In this case, I want the text to be wrap around one div element, further constraining the requirements and limiting the number of solutions I can pick:

<div class="promo-ribbon">
   40% off for 3 months
</div>

I know I have two backgrounds that are overlapping. This is a quintessential absolute positioning use case. Since I want one element, this can be achieve by using pseudo elements.

The two backgrounds are slightly rotated. I have 2 options here: clip-path or transform. I thought about using clip-path first but decided against it since it is not fully supported in most browsers.

So, I have a clear idea of what the solution would be: absolute positioning, pseudo elements and transform. But at times, you would be spoilt for choice and may not know which solutions you want to go for. In that case, prototyping them would help but it is entirely optional.

Some people prefer to prototype on CodePen. I prefer to prototype directly on the page itself since it is easier to see if other existing elements affect the solution in a negative way.

Translate it to Code

Once you have a solution, translating it into code will be quite easy. I start with the wrapper element first:

.badge-promo {
  position: relative;
  overflow: hidden;
  max-height: 60px;
  margin: $spacing-width;
  margin-bottom: -($spacing-width * 2);
  z-index: 1;
  padding: 12px 14px;
  border-radius2px 2px 0 0;
  color: $white-color; 
  text-align: left; 
}

What’s important here is the overflow: hidden property. The promo-ribbon’s width will be wider than the card element; we do not want the background element’s edges to pop out of the card:

Promo ribbon popping out of the card element

Why do we need the promo-ribbon’s width to be wider? You’ll see why later. The rest are pretty self-explanatory.

Finally, we can style the pseudo elements which are the two overlapping background elements. I think this is the most fun part since you get to see it come to life:

.badge-promo {
  position: relative;
  overflow: hidden;
  max-height: 60px;
  margin: $spacing-width;
  margin-bottom: -($spacing-width * 2);
  z-index: 1;
  padding: 12px 14px;
  border-radius2px 2px 0 0;
  color: $white-color;
  text-align: left;

  &:before,
  &:after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
  }

  &:before {
    background: #a5daff;
    height: 100%;
    width: 110%;
    transform-origin: bottom right;
    transform: rotate(1deg);
  }

  &:after {
    background: #3fb0ff;
    transform: rotate(-3deg);
    transform-origin: bottom left;
    height: 100%;
    width: 110%;
  }
}

I rotate the elements with their respective transform-origin. The only thing to note is that the width of the elements are 110% which exceeds the width of the card. When you rotate the element, the edge of the element will not be hidden anymore which gives you a white space on the edge:

White space of the rotated element's edge

On a really good retina display, this white space would be even more prominent. Bumping an additional 10% to the width would ensure that the ribbon is wide enough so that the edges stay hidden.

I had to tweak my HTML and wrap the text in another span element in order to control the stacking order of the elements. The ::after element is stack on top of the text in the original stacking order. So we need to specify a higher value for the z-index of the span element that wraps around the text:

<div class="badge-promo badge-promo-new">
    <span class="badge-promo-content">New</span>
</div>
.badge-promo-content {
  position: relative;
  z-index: 1;
  text-transform: uppercase;
}

And we’re done! This is the code on CodePen if you would like to play around with it:

See the Pen Ribbon Design: Overlapping & Rotated Background Elements by Aysha Anggraini (@rrenula) on CodePen.

Final Thoughts

It is important to know your tools very closely. If you do not really understand CSS or are not familiar with some of the tools that comes with it, you won’t be able to think of a solution in the first place. I think it is very important to get your fundamentals right. With a strong foundation in CSS, solving problems like this will not be difficult. It just needs a little bit of imagination and creativity. As engineers, what are we, if not a bunch of creative creatures who use our imagination to solve problems on a daily basis? The only thing that can make your life easier in solving problems is the foundation of your knowledge. As Randy Pausch stated in his book, The Last Lecture:

Fundamentals, fundamentals, fundamentals. You’ve got to get the fundamentals down because otherwise the fancy stuff isn’t going to work.

CSS is something fancy.