CSS Triangle Generator
Draw a triangle with the border trick and with clip-path, side by side, since one is a box that can carry a gradient and the other is not a box at all.
Enable JavaScript to change the triangle; the CSS below is for the defaults.
With borders, the coloured side is the one opposite the point, which is the part of the trick that never becomes intuitive.
A tooltip arrow usually hangs off a pseudo-element, so something like .tooltip::before is the common case.
Borders. No box, so no background and no gradient.
clip-path. A real box, so it can carry a gradient.
/* The border trick: an element with no size, made of borders. It works
everywhere and it cannot take a background, a gradient or a shadow,
because there is no box to put one on. */
.arrow {
width: 0;
height: 0;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
border-bottom: 14px solid rgb(30 86 236);
}
/* clip-path: a real box with a triangle cut out of it. It can carry a
gradient or an image, it can be animated, and the points are readable. */
.arrow--clip {
width: 24px;
height: 14px;
background: rgb(30 86 236);
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
There are two ways to draw a triangle in CSS and they are not the same object.
The border trick is an element with no width, no height, and three borders, two of them transparent. It has worked in every browser since the 1990s. What it produces is not a box: it cannot take a background, a gradient, an image or a shadow, because the shape is made of borders and there is nothing inside it.
clip-path takes a real element and cuts a triangle out of it. Everything a box can do still works: a
gradient, an image, an animation, a transform. The points are also readable, which the border version
never is, because the coloured side is the one opposite the point.
Both are generated, side by side, and the preview puts a gradient on the clip-path one to show what the border version gives up.
How to use
- Pick the direction. Four sides and four corners; a corner triangle is two borders rather than three.
- Set the width and height. For the border version the width is split into two halves, so the shape comes out as wide as you asked.
- Copy whichever suits. A tooltip arrow usually wants the border version on a pseudo-element; anything with a gradient wants the clip-path one.
Example
/* The border trick: an element with no size, made of borders. It works
everywhere and it cannot take a background, a gradient or a shadow,
because there is no box to put one on. */
.arrow {
width: 0;
height: 0;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
border-bottom: 14px solid rgb(30 86 236);
}
/* clip-path: a real box with a triangle cut out of it. It can carry a
gradient or an image, it can be animated, and the points are readable. */
.arrow--clip {
width: 24px;
height: 14px;
background: rgb(30 86 236);
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
The border triangle points up and the coloured border is the bottom one. That is not a typo, and it is the single most confusing thing about the technique.
Pitfalls
The coloured side is opposite the point. Up means border-bottom. Left means border-right. Every time.
A border triangle has no box. No background, no gradient, no border, no shadow, no rounding. If you need
any of those, use clip-path.
Half the width each side. The two transparent borders are each half the total width, which is why a 24px triangle is built from two 12px borders.
A shadow needs filter: drop-shadow. box-shadow follows the element’s box, which for a border
triangle is a zero-sized point. drop-shadow follows the painted shape and works for both versions.
A tooltip arrow usually belongs on a pseudo-element. .tooltip::before with position: absolute, so the
arrow does not take part in the tooltip’s own layout.
Subpixel edges look ragged. Fractional sizes and transforms can leave a triangle with a soft or jagged hypotenuse. Whole pixels help; so does a slightly larger shape.
Clipping cuts everything, including children. clip-path on a container clips its contents too, which
is occasionally what you wanted and usually a surprise.
Compatibility
The border trick works in every browser that has ever shipped CSS, which is why it is still in use.
clip-path with a polygon() works in Chrome, Edge, Firefox 54 and Safari 9.1 with the -webkit- prefix
and unprefixed in current versions. For a triangle, both approaches are safe today and the choice is about
what the shape has to do.
Both versions are generated by one function, mirrored in PHP for the first paint, with a case file comparing the two on every build: each direction family, a corner, a fractional height and a negative size.
The preview shows them next to each other with a gradient on the clip-path version, since that is the difference that decides which one to use and it cannot be seen from the CSS alone.
Frequently asked questions
Why is the bottom border coloured for a triangle that points up?
How do I put a shadow on a CSS triangle?
filter: drop-shadow(0 2px 2px rgb(0 0 0 / 0.2)). It follows the painted shape rather than the box, so it
works for the border version, which has no box at all.Can I round the corners?
clip-path you can approximate it using a path() instead of a polygon(), or use
an inline SVG, which is the more maintainable answer for a rounded arrow.Which one should I use for a tooltip?
Why does my triangle look blurry?
transform: scale() somewhere above it is resampling the edges.