A CSS border triangle is built from a 0px by 0px element whose three or four solid borders are cut at 45-degree seams to form a single solid wedge. The trick depends on the fact that browsers render each border as its own trapezoid and miter them at the corners, so when the content box has no size the four trapezoids meet at a single point and the shape collapses into a triangle. Two opposite borders are set to transparent to act as the angled sides, and one border keeps its color to form the pointing edge. This technique needs no SVG, no canvas, no image file, and no pseudo-element library, just a div with declared border widths, styles, and colors. It is the most portable way to drop a small directional indicator into a layout, a tooltip arrow, or a separator, and the same idea extends in all four cardinal directions with only the colored border moving.

How the Four Border Sides Become a Triangle
When width and height both drop to 0, the element's content box collapses but its borders remain. Each side of the box is rendered as a wedge that fills the border's declared width, and adjacent borders meet on a diagonal. With all four borders the same color, the result is a four-pointed shape; with three transparent borders and one colored border, only the wedge attached to the colored border is visible, and the visible shape is a triangle whose base spans the perpendicular borders and whose tip points away from the colored edge.
For a vertical triangle (up or down), the two transparent side borders add up to the base width and the colored border equals the visible height. For a horizontal triangle (left or right), the transparent top and bottom borders add up to the base height and the colored border equals the visible width. The generator writes each longhand border declaration explicitly so the geometry stays visible and auditable in the CSS output, instead of collapsing into a single shorthand that hides which side is colored.
| Direction | Colored border | Transparent borders | Colored width equals | Transparent pair equals |
|---|---|---|---|---|
| Up | border-bottom | border-left, border-right | Selected height | Selected width |
| Down | border-top | border-left, border-right | Selected height | Selected width |
| Left | border-right | border-top, border-bottom | Selected width | Selected height |
| Right | border-left | border-top, border-bottom | Selected width | Selected height |
The 45-degree seams also explain why each border is set to solid. A dashed or dotted border does not miter cleanly into a triangle, because the dashes are applied after the wedge is rendered and you get stripes rather than a single solid shape.
Create a CSS Border Triangle in the Browser
The fastest path from a blank stylesheet to working CSS and HTML is the CSS Triangle Generator, which keeps a live preview and the output in sync as you change settings.
- Pick the triangle direction. Choose up, down, left, or right. The decision determines which border becomes the colored pointing edge and which two borders stay transparent.
- Enter the visible width and height. Whole-number values from 10 to 300 pixels are accepted. These are the painted dimensions, not the size of the content box, which stays at 0 by 0.
- Enter a six-digit HEX color. The format must be a hash followed by exactly six hexadecimal digits, for example #1f6feb. The value is normalized to lowercase in the output.
- Inspect the live result at the actual size and background where it will be used. The preview uses the same derived border declarations shown in the CSS output, so what you see is what gets copied.
- Copy the CSS and the HTML separately. Each button asks the browser for clipboard access. If clipboard access is denied, both code blocks stay visible and you can select them manually; the page will not falsely report success.
- Decide on the accessibility treatment. The generated HTML contains a single div with aria-hidden="true", which is correct for a decorative arrow. If the triangle communicates direction, state, validation, or navigation, replace the silent snippet with visible text or an accessible label on the surrounding control.
- Rename the class if needed. The default class is lizely-triangle. If that name collides with an existing stylesheet, change it in the generator output and update both the CSS rule and the HTML class attribute together.
Direction, Size, and Color Rules the Generator Enforces
The generator is strict on purpose. The browser-side controls constrain ordinary input, and the underlying code independently rejects unsupported directions, malformed colors, decimals, and values outside the documented range. It does not quietly clamp invalid programmatic input or emit partial code.
For even dimensions, the two transparent borders split the base equally. For odd dimensions, the generator uses floor and ceiling values so the total stays exact. A 41px base, for example, is split into 20px on one transparent border and 21px on the other. The reasoning is mechanical: Chromium can quantize two 20.5px borders down to 20px each, which would shrink a requested 41px total to 40px and silently lose a pixel of painted geometry. The one-pixel asymmetry preserves the requested total instead.
Written out as the arithmetic the generator performs internally, 41 divided by 2 equals 20 with remainder 1, so the floor value is 20 and the ceiling value is 21. Both add to 41: 20 + 21 = 41.
Color rules are equally strict. The input must match a hash followed by exactly six hexadecimal digits. Three-digit shorthand like #fff, eight-digit hex with alpha like #1f6febff, CSS named colors like tomato, and CSS variables like var(--accent) are all rejected. Alpha channels, gradients, currentColor, and wide-gamut syntax are not produced. After copying, you can replace the literal with a trusted design token, but verify that the target syntax is supported in the consuming environment and that transparent side borders remain transparent.
Where Border Triangles Fall Short
The border technique is compact and portable, but it is not a general drawing tool. Because the measurable content box is 0 by 0 and the painted shape is a side effect of the borders, several things that feel obvious in SVG are awkward or impossible:
- No rounded tips. Border corners are mitered at 45 degrees and cannot be softened into a curve.
- No hollow shapes or strokes. The visible region is a solid fill; you cannot outline a triangle or cut a hole in it.
- No gradients. Borders accept a single solid color. A gradient fill would require SVG, clip-path, or a layered approach.
- No complex polygons. Only the four cardinal triangles are producible. A non-right-angle triangle, an isosceles triangle pointing on a diagonal, or a star cannot be drawn with borders alone.
- No shadows or filters. Applying box-shadow to a zero-size element with painted borders produces unexpected geometry, and SVG or filter pipelines are better choices when visual effects matter.
For any of these, move to SVG or clip-path and check browser support in the intended environment. The MDN CSS backgrounds and borders guide documents the underlying border properties and transparent color behavior, while the W3C CSS Backgrounds and Borders Level 3 specification defines the longhand border widths, styles, and colors used by the generator.
Make the Triangle Accessible and Theme-Aware
The shipped HTML is one div with aria-hidden="true", which is appropriate for a decorative arrow or separator that conveys no information to assistive technology. The moment a triangle carries meaning, such as a sort direction indicator, a validation state, an accordion chevron, or a "next" control, the silent snippet is no longer correct on its own. Add visible text inside the surrounding button or anchor, give that control an accessible name, and keep the decorative triangle hidden from assistive technology.
| Triangle role | Generated HTML | What to add in production |
|---|---|---|
| Decorative arrow or separator | div with aria-hidden="true" | Nothing; ship as generated |
| State indicator (sort, validation) | div with aria-hidden="true" | Visible text or aria-label on the surrounding control |
| Interactive control (chevron, next) | div with aria-hidden="true" | Use a button or anchor with an accessible name; the triangle stays decorative |
Color choice deserves its own check. The HEX value is painted as an opaque border, and a triangle sitting next to text or iconography needs contrast against both the immediate background and any text it touches. A light theme and a dark theme can flip that relationship. Tools like the Color Contrast Checker let you confirm WCAG ratios before shipping, especially because transparent borders are not adjustable for contrast; they are literally invisible pixels.
If you swap the literal HEX for a CSS variable after copying, verify the variable resolves to an opaque six-digit color in the consuming stylesheet. Transparent side borders must remain transparent, so do not apply theme tokens that introduce alpha or named colors to those declarations.
Test the Final Triangle Before Shipping
The preview confirms the border geometry of the triangle itself, not the layout or accessibility behavior of the destination component. Before treating the snippet as production-ready, place it inside the real component and check:
- Zoom behavior at 200 percent. Pixel-accurate borders can shift on zoom; verify the painted shape still reads as a triangle.
- High contrast and forced colors modes. Borders render with system colors in forced-colors mode on some platforms; the triangle should still be visible.
- Common mobile widths. A 100px-by-100px triangle is a different visual weight on a 320px screen than on a 1440px screen.
- Interaction states. Hover, focus, and active states on the surrounding control should still look intentional when the triangle is present.
- Performance at high counts. One decorative triangle is negligible, but hundreds of them in a list can add measurable DOM and paint work; prefer one semantic control with a decorative shape over duplicated hidden controls.
Border triangles are not a universal standard. The exact dimension limits and directional serialization are deliberately local product rules covered by four-direction, odd-dimension, exact-output, and boundary tests, not a claim about how every CSS triangle must be written. Treat the generator output as a known-good starting point and re-verify the rules above in your own component before shipping.