How a CSS Triangle Works: The Border Trick Behind Tooltip Arrows
A plain explanation of the CSS border trick that draws a triangle with no image or SVG, plus how to control direction and size for tooltip arrows and carets.
How a CSS Triangle Works: The Border Trick Behind Tooltip Arrows
Every tooltip tail, dropdown caret, and chat-bubble pointer you have ever seen is probably a triangle. And a surprising number of them are not images, not SVG, not an icon font — they are a single empty <div> with some clever borders. The technique is old, it works in every browser back to IE6, and once it clicks you will never reach for an image asset to draw an arrow again.
This post explains exactly why the trick works, walks through the CSS for an upward-pointing triangle, and shows how to control the direction and size. If you just want to point, click, and copy the result, the CSS Triangle Generator gives you both the border version and the modern clip-path version side by side. But understanding the mechanism is what lets you debug it when an arrow points the wrong way at 11pm.
The one idea: a zero-size box with thick borders
Here is the concrete thing to hold in your head. Take an element, set its width and height to 0, and give it a thick border. Because the box has no interior, all four borders collapse to a single point in the centre. The browser has to decide where one border ends and the next begins, and it draws that seam as a 45-degree diagonal — a miter, like the corner of a picture frame.
The result is that each of the four borders is no longer a rectangle. It is a triangular wedge, with its point at the centre of the box and its base on the outside edge. Four wedges, meeting at the middle, pointing outward in four directions: up, down, left, right.
Now make three of those four borders transparent and leave one with a real colour. You have hidden three wedges and kept one. That single visible wedge is your triangle. This is the whole trick: a zero-size element with thick borders, three transparent and one coloured, renders a triangle.
The direction it points is the part that trips everyone up, so read this twice: the coloured side points away from its own edge. A solid bottom border produces a triangle that points up. A solid top border points down, a solid left border points right, a solid right border points left. The wedge always aims at the centre of the box and away from the side it sits on.
A worked example: the upward triangle
Let's build a triangle that points up. We want the coloured border on the bottom, and the two side borders transparent to carve out the slopes.
.triangle-up {
width: 0;
height: 0;
border-left: 25px solid transparent;
border-right: 25px solid transparent;
border-bottom: 40px solid #3b82f6;
border-top: 0;
}
Read it line by line. width: 0; height: 0 forces the borders into wedges. The left and right borders are 25px each and transparent — they cut the diagonal slopes of the triangle, and together they define the base, which is 25 + 25 = 50px wide. The bottom border is 40px of solid blue, and that thickness becomes the triangle's height. The top border is zeroed out because we don't need a downward wedge at all.
So this triangle is 50px wide at the base, 40px tall, and blue. Want it wider and flatter? Bump the two side borders. Want it taller? Bump the bottom border. The base width is always twice one side border, because the apex sits exactly between them — which is also why both side borders must be equal. Make them unequal and the tip slides off-centre into a scalene shape, which is occasionally what you want and usually a bug.
To point the same triangle down, move the colour to the top border and zero the bottom:
.triangle-down {
width: 0;
height: 0;
border-left: 25px solid transparent;
border-right: 25px solid transparent;
border-top: 40px solid #3b82f6;
}
For left and right arrows, swap the roles: colour the right border (points left) or the left border (points right), and make the top and bottom borders transparent instead of the sides.
Tooltip arrows, carets, and bubble pointers
The reason this trick refuses to die is that it is perfect for the small decorative arrows that real interfaces need constantly.
A tooltip tail is a downward triangle pinned under the bubble. Attach it as a ::after pseudo-element with position: absolute; left: 50%; transform: translateX(-50%); top: 100%, colour it to match the tooltip background, and it sits flush beneath the bubble pointing at whatever the tooltip describes. Because a border triangle has no box area, it never intercepts pointer events from the content underneath it — a genuinely useful side effect.
A dropdown caret is the same downward triangle, tiny — say 10px wide by 6px tall — coloured to match your text, dropped in as an inline-block ::after on the trigger button. No icon font, no SVG request, no extra HTTP round-trip. It is part of the stylesheet. Add a CSS transition and rotate it 180 degrees on the open state so the caret flips when the menu expands. If you want to drive that rotation cleanly, the CSS Transform Generator will hand you the exact rotate() and transition snippet.
A chat-bubble pointer is a left- or right-facing triangle absolutely positioned on the bubble's edge and vertically centred. Point it left for incoming messages so it aims back toward the avatar, point it right for the sent side. Two mirror copies of the same shape give you both sides of a messaging UI.
When the border trick fails: reach for clip-path
The border method has one hard limit that catches everyone at least once. The element is width: 0; height: 0, so it has no surface — only borders. That means background, background-image, and any text inside are invisible. You cannot fill a border triangle with a gradient or a photo, because there is nothing to fill.
I learned this the hard way building a hero section with a diagonal divider. I wanted the triangular cut filled with the same brand gradient as the rest of the band, and I spent twenty minutes setting background: linear-gradient(...) on a border triangle and getting nothing. The element had no body to paint. The fix was to switch methods entirely: a real width × height box clipped with clip-path: polygon(...). That box keeps its dimensions and accepts any background, so the gradient clipped neatly to the triangle.
So the rule of thumb is simple. Use the border method for tiny decorative arrows where compatibility matters and you only need a flat colour. Use clip-path when you need a real box size, a fill (gradient, image, or text inside the shape), or a smoothly animatable shape — clip-path transitions, the border hack does not. If you go that route, a clip-path generator saves you from hand-computing polygon coordinates. And for the gradient itself, the gradient generator produces the linear-gradient you'll paste into the clipped box.
Quick reference
A few things worth pinning to memory:
- width and height must be 0. Any real dimension turns the wedges back into trapezoids and you get a frame, not a triangle.
- The colour goes on the opposite border from where the arrow points. Pointing up means a coloured bottom border. If your arrow is backwards, you almost certainly coloured the wrong side.
- Both side borders must be equal for a symmetric isosceles triangle; unequal sides shift the apex off-centre.
- No background on border triangles. If you need a fill, that is your signal to switch to
clip-path.
The border trick is one of those bits of CSS that feels like sleight of hand until you see the four wedges, and then it is obvious forever. Generate one, pop open your browser's element inspector, and watch the borders collapse to a point — that single moment makes the whole thing intuitive.
Made by Toolora · Updated 2026-06-13