DEV Community

Cover image for Pure CSS Shapes 3 Ways
Stephanie Eckles
Stephanie Eckles

Posted on • Edited on • Originally published at moderncss.dev

Pure CSS Shapes 3 Ways

This is episode #22 in a series examining modern CSS solutions to problems I've been solving over the last 13+ years of being a frontend developer. Visit ModernCSS.dev to view the whole series and additional resources.

Modern CSS - and modern browser support - provides us three excellent methods to create pure, basic CSS shapes. In this tutorial, we will examine how to create CSS triangles using:

  • borders
  • linear gradients
  • clip-path

Method 1: Borders

This is the first trick I learned to create CSS triangles, and it's still a solid standby.

Given a zero width and zero height element, any values provided border directly intersect and are the only visible indication of an element. This intersection is what we can take advantage of to create a triangle shape.

To illustrate how this works, we'll supply a different border color to each side:

.triangle {
  border: 10px solid blue;
  border-right-color: red;
  border-bottom-color: black;
  border-left-color: green;
}
Enter fullscreen mode Exit fullscreen mode

Which produces the following, in which you can see we've essentially already achieved 4 triangle shapes:

result of the previously defined CSS rule showing 4 triangles due to the border colors

In order to create this as a single triangle instead, we first decide which direction we want the triangle pointing. So, if we want it pointing to the right, similar to a "play" icon, we want to keep the left border visible. Then, we set the colors of the other borders to transparent, like so:

.triangle {
  border: 10px solid transparent;
  border-left-color: blue;
}
Enter fullscreen mode Exit fullscreen mode

In the demo image below, I've added a red outline to see the bounding box so we can discuss some improvements.

a blue triangle shape pointing to the right with a red outline to show the bounding box

One improvement we can make is to remove width of the right border to prevent it being included in the total width of the element. We can also set unique values for top and bottom to elongate the triangle visual. Here's a compact way to achieve these results:

.triangle {
  border-style: solid;
  border-color: transparent;
  /* top | right | bottom | left */
  border-width: 7px 0 7px 10px;
  border-left-color: blue;
}
Enter fullscreen mode Exit fullscreen mode

As seen in the updated image below, we first assign a solid, transparent border. Then we define widths such that the top and bottom are a smaller value than the left to adjust the aspect ratio and render an elongated triangle.

final triangle

So to point the triangle a different direction, such as up, we just shuffle the values so that the bottom border gains the color value and the top border is set to zero:

.triangle {
  border-style: solid;
  border-color: transparent;
  /* top | right | bottom | left */
  border-width: 0 7px 10px 7px;
  border-bottom-color: blue;
}
Enter fullscreen mode Exit fullscreen mode

Resulting in:

demo of the CSS triangle pointing upwards

Borders are very effective for triangles, but not very extendible beyond that shape without getting more elements involved. This is where our next two methods come to the rescue.

Method 2: linear-gradient

CSS gradients are created as background-image values.

First let's set our stage, if you will, by defining box dimensions and preventing background-repeat:

.triangle {
  width: 8em;
  height: 10em;
  background-repeat: no-repeat;
  /* Optional - helping us see the bounding box */
  outline: 1px solid red;
}
Enter fullscreen mode Exit fullscreen mode

Following that, we'll add our first gradient. This will create the appearance of coloring half of our element blue because we are creating a hard-stop at 50% between blue and a transparent value.

background-image: linear-gradient(45deg, blue 50%, rgba(255,255,255,0) 50%);
Enter fullscreen mode Exit fullscreen mode

Now, if our element was square, this would appear to cut corner to corner, but we ultimately want a slightly different aspect ratio like we did before.

progress of adding the first gradient showing a partly blue element but not yet a triangle

Our goal is to create a triangle with the same appearance as when using our border method. To do this, we will have to adjust the background-size and background-position values.

The first adjustment is to change the background-size. In shorthand, the first value is width and the second height. We want our triangle to be allowed 100% of the width, but only 50% of the height, so add the following:

background-size: 100% 50%;
Enter fullscreen mode Exit fullscreen mode

With our previous linear-gradient unchanged, this is the result:

updated triangle resized with background-size showing an odd shape in the upper left of the bounding box

Due to the 45deg angle of the gradient, the shape appears a bit strange. We need to adjust the angle so that the top side of the triangle appears to cut from the top-left corner to the middle of the right side of the bounding box.

I'm not a math wizard, so this took a bit of experimentation using DevTools to find the right value ๐Ÿ˜‰

Update the linear-gradient value to the following:

linear-gradient(32deg, blue 50%, rgba(255,255,255,0) 50%);
Enter fullscreen mode Exit fullscreen mode

And here's our progress - which, while technically a triangle, isn't quite the full look we want:

progress of completing one side of the triangle

While for the border trick we had to rely on the intersection to create the shapes, for linear-gradient we have to take advantage of the ability to add multiple backgrounds to layer the effects and achieve our full triangle.

So, we'll duplicate our linear-gradient and update it's degrees value to become a mirror-shape of the first, since it will be positioned below it. This results in the following for the full background-image definition:

background-image: 
  linear-gradient(32deg, blue 50%, rgba(255,255,255,0) 50%),
  linear-gradient(148deg, blue 50%, rgba(255,255,255,0) 50%);
Enter fullscreen mode Exit fullscreen mode

But - we still haven't quite completed the effect, as can be seen in the progress image:

the second linear-gradient triangle is overlapping the first

The reason for the overlap is because the default position of both gradients is 0 0 - otherwise known as top left. This is fine for our original gradient, but we need to adjust the second.

To do this, we need to set multiple values on background-position. These go in the same order as background-image:

background-position: top left, bottom left;
Enter fullscreen mode Exit fullscreen mode

And now we have our desired result:

final triangle created with CSS gradients

The downside of this method is that it's rather inflexible to changing aspect ratio without also re-calculating the degrees.

However, CSS gradients can be used to create more shapes especially due to their ability to be layered to create effects.

For a master class in CSS gradients for shapes and full illustrations, check out A Single Div by Lynn Fisher

Method 3: clip-path

This final method is the slimmest and most scalable. It is currently slightly lagging in support so be sure to check our own analytics to determine if this is an acceptable solution.

Here's our starting point for our element, which is box dimensions and a background-color:

.triangle {
  width: 16px;
  height: 20px;
  background-color: blue;
}
Enter fullscreen mode Exit fullscreen mode

The concept of clip-path is that you use it to draw a polygon shape (or circle, or ellipse) and position it within the element. Any areas outside of the clip-path are effectively not drawn by the browser, thus "clipping" the appearance to just the bounds of the clip-path.

To illustrate this more, and to generate your desired clip-path definition, check out the online generator: Clippy

The syntax can be a bit more difficult to get used to, so I definitely suggest using the generator noted above to create your path.

For our purposes, here's a triangle pointing to the right:

clip-path: polygon(0 0, 0% 100%, 100% 50%);
Enter fullscreen mode Exit fullscreen mode

With a clip-path, you are defining coordinates for every point you place along the path. So in this case, we have a point at the top-left (0 0), bottom-left (0% 100%), and right-center (100% 50%).

And here is our result:

completed triangle using clip-path

While clip-path is very flexible for many shapes, and also the most scalable due to adapting to any bounding box or aspect ratio, there are some caveats.

When I mentioned the browser doesn't draw anything outside of the bounding box, this includes borders, box-shadow, and outline. Those things are not re-drawn to fit the clipped shape. This can be a gotcha, and may require additional elements or moving of effects to a parent to replace the lost effects.

Here's an egghead video by Colby Fayock to better understand clip-path and how to bring back effects like box-shadow

Demo

This demo shows our three ways to create a CSS triangle, which is added to each element using ::after and makes use of viewport units to be responsive.

Top comments (2)

Collapse
 
yo profile image
Yoginth

Woah looks cool ๐Ÿ˜