How can I create a responsive Arrow-Pointer shape in CSS?

Viewed 1029

I'm trying to build an EU Energy Efficiency Rating widget, which looks sth like this:
enter image description here

However, I'm struggling to create the arrow shape in a way that would be responsive to changes in height/width, plus I need a black border all around each arrow.
This is what I've come up with until now for each arrow:

span {
  display: flex;
  flex-direction: row;
  align-items: center;
  font-weight: 700;
  font-size: 1em;
  color: white;
  padding: 2%;
  height: 70%;
  background: #3b7634;
  width: 10%;
}

span::before {
  top: 0;
  background: linear-gradient(to right top, red 50%, transparent 50%);
}

span::after {
  bottom: 0;
  background: linear-gradient(to right bottom, red 50%, transparent 50%);
}

which looks like this:
enter image description here

Note how the tip is too "pointy" and a little rough. This is indeed responsive, but it's not pretty, nor is it possible to apply a border around the whole body + tip, bc the tip is created using linear-gradient.

So, is there any better way of doing it?
I'm using Angular btw. Thanks in advance.

3 Answers

You would want to use a triangle rather than a linear-gradient. For example:

https://codepen.io/seanstopnik/pen/072a65f5fa4a4da25e46506f7c19fb3d

* {
  box-sizing: border-box;
}

.arrow {
  position: relative;
  width: 20%;
  height: 22px;
  color: #fff;
  text-align: right;
  background-color: red;
  padding: 2px 10px;
}

.arrow--right:before {
  position: absolute;
  width: 0;
  height: 0;
  content: "";
  border-width: 11px;
  border-style: solid;
  border-color: transparent;
  border-left-color: red;
  top: 0;
  right: -22px;
}
<div class="arrow arrow--right">A</div>

You may also use clip-path to cut that arrow off.

The clip-path CSS property creates a clipping region that sets what part of an element should be shown. Parts that are inside the region are shown, while those outside are hidden.

example

/* cut an arrow shape on the right side */

span {
  clip-path: polygon( 0% 0%, calc(100% - 0.4em) 0%, 100% 50%, calc(100% - 0.4em) 100%, 0% 100%);
}


/* give it a shadow or a border ? */

div {
  filter: drop-shadow(2px 2px 2px);
}


/* styling : size and colors */

span {
  font-size: clamp(1em, 5vmax, 40px);
  padding-right: 0.5em;
  color: white;
  float: left;
  clear: left;
  margin: 0.1em;
}

span:nth-child(1) {
  background: #018133;
  padding-left: 2em;
}

span:nth-child(2) {
  background: #27B432;
  padding-left: 2.8em;
}

span:nth-child(3) {
  background: #88E213;
  padding-left: 3.6em;
}

span:nth-child(4) {
  background: #F0EF00;
  padding-left: 4.2em;
}

span:nth-child(5) {
  background: #FC9000;
  padding-left: 5em;
}

span:nth-child(6) {
  background: #FD0000;
  padding-left: 5.8em;
}

span:nth-child(7) {
  background: #FD001D;
  padding-left: 6.45em;
}
<div>
  <span>A</span>
  <span>B</span>
  <span>C</span>
  <span>D</span>
  <span>E</span>
  <span>F</span>
  <span>G</span>
</div>

Codepen demo

Here is an online tool to help you create your first shapes (many examples ready to copy/paste) https://bennettfeely.com/clippy/

This is the best I can come up with using calc and 1px images. I'd imagine this technique works with square <svg>s as well.

The downsides of this is:

  • I can't think of a way to perfectly calculate the bar width to minus the protruding arrow tip.
  • Not purely CSS. But this works great when you're already using a component framework.

The tip

I'm not sure if there's a way to make a dynamic square with height at 100%. The reverse is absolutely possible though. https://spin.atomicobject.com/2015/07/14/css-responsive-square/

The solution here is to use a square image to exploit the browser's ability to maintain image aspect ratios.

This is a 1px transparent PNG image I found online. We should also be aware that we can't use ::before or ::after with images.

The border

CSS can't do borders with odd shapes, but we can hack it. Here I'm using box-shadow. But you can also use actual border. Sometimes, you can use 2 different rectangles for borders as well.

The solution here is to separate the background color and the borders at different levels, because around the bend, the horizontal border actually needs to overlap into the tilted square. (Or vice versa, the border of the tilted square needs to overlap the horizontal borders.)


But overall, I think this looks most like the energy efficiency arrow.

* { box-sizing: border-box; }

.efficiency {
  border: 1px solid black;
  /* Remove the following rules to support older browsers. */
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.bar {
  display: flex;
  flex-direction: row;
  align-items: center;
  font-weight: 700;
  font-size: 1em;
  color: white;
  flex: 1;
  background: #3b7634;
  position: relative;
  z-index: 0;
  /*
  If you want to support older browsers:
  height: calc(100% / 7 - 3px);
  margin-bottom: 3px;
  */
}

.bar > .text {
  display: flex;
  flex-direction: row;
  align-items: center;
  height: 100%;
  width: 100%;
  border: 2px solid black;
  border-right: none;
  z-index: 1;
}

.a { width: 20%; background-color: #00A851 }
.b { width: 30%; background-color: #4FB947 }
.c { width: 40%; background-color: #C1D82D }
.d { width: 50%; background-color: #FEF200 }
.e { width: 60%; background-color: #FCBA0B }
.f { width: 70%; background-color: #F3711A }
.g { width: 80%; background-color: #EE141F }

.arrow {
  height: calc(100% / 1.41421356237); /* square root 2 */
  transform: translateX(-50%) rotate(45deg);
  position: absolute;
  left: 100%;
  user-select: none;
  background-color: inherit;
  box-shadow: inset -2px 2px black;
}
<h1>These look great</h1>

<div class="efficiency" style="width: 400px; height: 300px">
  <div class="bar a"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">A</div></div>
  <div class="bar b"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">B</div></div>
  <div class="bar c"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">C</div></div>
  <div class="bar d"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">D</div></div>
  <div class="bar e"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">E</div></div>
  <div class="bar f"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">F</div></div>
  <div class="bar g"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">G</div></div>
</div>

<div class="efficiency" style="width: 300px; height: 400px">
  <div class="bar a"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">A</div></div>
  <div class="bar b"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">B</div></div>
  <div class="bar c"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">C</div></div>
  <div class="bar d"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">D</div></div>
  <div class="bar e"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">E</div></div>
  <div class="bar f"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">F</div></div>
  <div class="bar g"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">G</div></div>
</div>

<h1>Just don't go extreme</h1>

<div class="efficiency" style="width: 100px; height: 400px">
  <div class="bar a"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">A</div></div>
  <div class="bar b"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">B</div></div>
  <div class="bar c"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">C</div></div>
  <div class="bar d"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">D</div></div>
  <div class="bar e"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">E</div></div>
  <div class="bar f"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">F</div></div>
  <div class="bar g"><img class="arrow" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="><div class="text">G</div></div>
</div>

enter image description here

Related