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;
}
Which produces the following, in which you can see we've essentially already achieved 4 triangle shapes:
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;
}
In the demo image below, I've added a red outline
to see the bounding box so we can discuss some improvements.
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;
}
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.
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;
}
Resulting in:
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;
}
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%);
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.
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%;
With our previous linear-gradient
unchanged, this is the result:
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%);
And here's our progress - which, while technically a triangle, isn't quite the full look we want:
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%);
But - we still haven't quite completed the effect, as can be seen in the progress image:
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;
And now we have our desired result:
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;
}
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%);
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:
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 likebox-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)
Woah looks cool ๐