A Sass Mixin for CSS Triangles

Share this article

There is a very popular CSS trick consisting on using a mix of solid and transparent borders on a 0 width, 0 height element in order to fake a triangle. If you don’t know technique yet, you can find more information about it in the following resources:

While this technique comes with some quirks, it actually does the job well and has the benefit of being very compatible across the wide range of browsers we have to support.

Now the thing is, I never completely remember how to use this little piece of code. Which borders need to be transparent? Which one should be solid? I just can’t figure this out and I bet it’s the same for many of you as well. So this is typically the kind of thing you would like to automate using Sass.

There are probably as many CSS triangle Sass mixins as there are Sass developers. What if I showed you my own mixin to deal with CSS triangles/arrows in CSS?

What Do We Need?

Before digging into the code, it would be a good idea to check what we need in order to make a triangle out of CSS. First: a direction. We need to define a direction for our arrow, either top, right, bottom, or left. Also, what about a position? Like at 1.5em from the top, 100% from the left, for instance. Also we could define a color, and perhaps a size as well, although those 2 arguments could have defaults.

So in the end, our mixin is a short way for us to say Generate a triangle pointing to this direction, at this spot, in this color, and in the following dimensions. Sounds good, right?

Note that in order to avoid using extra markup, I like to use pseudo-elements for triangles. For instance:

.element {
  /* Container of some kind */

  &::before {
    /* Including triangle mixin */
  }
}

Using Our Toolbox

Last week, I wrote about some functions and mixins to kickstart your project. In case you haven’t read that article yet, go ahead. I’ll wait.

Okay, done? What if we reused some of our mixins in order to make this triangle mixin as sharp and clean as possible? For instance, we could use the size() mixin so we don’t have to define both the width and the height. We could also keep a consistent API for positioning stuff and make use of the absolute() mixin (more about that mixin here).

In order to have all the code in this post, allow me to include the 3 mixins here as well:

// Sizing stuff
@mixin size($width, $height: $width) {
      width: $width;
      height: $height;
}

// Positioning stuff
@mixin position($position, $args) {
  @each $o in top right bottom left {
        $i: index($args, $o);

    @if $i and $i + 1 <;= length($args) and type-of(nth($args, $i + 1)) == number  {
          #{$o}: nth($args, $i + 1);
    }
  }

  position: $position;
}

// Absolutely positioning stuff
@mixin absolute($args) {
        @include position(absolute, $args);
}

Also, we will need the opposite-direction() function; either the one from Compass or the one I wrote in my previous post.

Building the Mixin

Now that we have all the utilities we need, let’s begin creating our triangle mixin. First, let’s create the mixin’s skeleton.

The Mixin Signature

Here is what our mixin will look like:

@mixin triangle(
  $direction,
      $position,
  $color: currentColor, 
      $size: 1em
) {
  /* Mixin content */
}

Both the direction and the position are non-optional parameters. Meanwhile the color will default to the currentColor CSS value, which is always better than falling back to black. Regarding the size, since all triangles in my project were normalized to have the same size, I made size an optional parameter but feel free to make it non-optional if you want.

For those of you who like very formal signature syntax, here is what that would look like:

triangle(string $direction, list $position, color $color: currentColor, number $size: 1em)

The Mixin Core

Let’s deal with the easiest part first: The mixin core. In other words, the dimensions and position of the triangle. We will take care of the borders later.

@mixin triangle($direction, $position, $color: currentcolor, $size: 1em) {
  @include absolute($position);
  @include size(0);
  content: '';
  z-index: 2;

  /* Border stuff */
}

Pretty clean, isn’t it? Here’s what it does:

  1. Positions the element thanks to the absolute() mixin.
  2. Makes the element 0 width, 0 height.
  3. In case it is a pseudo-element (almost always the case), makes it appear using the content property.
  4. Uses z-index to ensure it’s on top of the default layer (I suppose you could remove this if you want).

Borders

Okay, we’re done the easiest part. Mostly because we had custom mixins easing the pain. Now we need to deal with all the borders. This is where opposite-direction comes into play. Using an example, let’s recall how this works. Consider that we want to define a triangle pointing to the right:

  • Right border should not be defined
  • Left border should be colored
  • Top border should be transparent
  • Bottom border should be transparent

This leads us to the following code:

@mixin triangle($direction, $position, $color: currentcolor, $size: 1em) {
  /* Core stuff */

  border-#{opposite-position($direction)}: $size * 1.5 solid $color;
      $perpendicular-border: $size solid transparent;

  @if $direction == top or $direction == bottom {
    border-left:   $perpendicular-border;
        border-right:  $perpendicular-border;
  }

  @else if $direction == right or $direction == left {
    border-bottom: $perpendicular-border;
        border-top:    $perpendicular-border;
  }
}

At first, we define the opposite border. To do so, we use the opposite-direction() function. In our previous example, opposite-direction would return left, hence the property would be border-left. Regarding the size, it occurred to me that it looked better when slightly increasing the size of this border. But that’s rather arbitrary, so feel free to update it if you don’t like it.

Finally, we define perpendicular borders. If the direction is either top or bottom, those borders are border-left and border-right and if the direction is left or right, perpendicular borders are border-top and border-bottom. Both borders should be transparent.

Error Handling

As always, we shouldn’t forget to handle errors in our mixin. To keep the mixin simple, I made sure the direction is okay. That is, it has to be one of the 4 common offsets. I believe it’s important to make this check because one could decide to pass an angle (e.g. 42deg), as is done for the linear-gradient() function.

@mixin triangle($direction, $position, $color: currentcolor, $size: 1em) {
  @if not index(top right bottom left, $direction) {
    @warn "Direction must be one of `top`, `right`, `bottom` or `left`; currently `#{$direction}`.";
  }

  @else {
    /* Mixin content */
  }
}

Note how we use the index() function to see if the direction is one of the 4 offsets rather than manually checking each one with ==.

In a Sass 3.3 environment, we could make our function slightly more bulletproof by lower-casing the direction first: $direction: to-lower-case($direction). This would prevent an error in case the direction is not completely lower-cased. Details matter.

I have very recently written about how to support multiple versions of Sass at the same time. I highly recommand you read the article, but basically it would look like this:

@mixin triangle($direction, $position, $color: currentcolor, $size: 1em) {
  $direction: if(function-exists("to-lower-case") == true, to-lower-case($direction), $direction);

  @if not index(top right bottom left, $direction) {
    @warn "Direction must be one of `top`, `right`, `bottom` or `left`; currently `#{$direction}`.";
  }

  @else {
    /* Mixin content */
  }
}

Regarding the position, it should be the job of the position() mixin to deal with error handling, instead of the triangle() mixin. About the color and the size, we could probably check the data type but I felt like checking the position was enough for this demonstration.

Usage and Example

Using it is quite straightforward. But remember, since the element that includes the triangle mixin is being absolutely positioned, its parent should have position: relative.

/**
 * 1. Enable absolute positioning for pseudo-element
 * 2. Using a pseudo-element to generate the arrow
 * 3. Same as @include triangle(bottom,top 100% left 1em, $color);
     */
    .tooltip { 
      $color: #3498db;

  position: relative; /* 1 */

  background: $color;
  padding: .5em;
  border-radius: .15em;
  color: white;
  text-align: center;

  &::before { /* 2 */
    @include triangle( 
      $direction : bottom, 
          $position  : top 100% left 1em, 
      $color     : $color
    ); /* 3 */
  }
}

And here is a live demo for you to play with:

See the Pen Jabzj by SitePoint (@SitePoint) on CodePen.

Final Thoughts

In the end, the mixin is quite simple, don’t you think? Not only does it reuse existing mixins to avoid having to code annoying things manually, but it also makes use of clever features like opposite-direction and the @if directive to make our code as DRY as possible.

I hope you like it. Let us know if you find it useful.

Frequently Asked Questions (FAQs) about CSS Triangles

How can I create a CSS triangle with a border?

Creating a CSS triangle with a border involves using the CSS border property. The trick is to set three borders to transparent and one to a solid color. The solid border will form the triangle. For example, to create a downward-pointing triangle with a border, you can use the following code:

.triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 50px solid red;
}
In this code, the border-top property creates the triangle, and the border-left and border-right properties create the transparent sides.

Can I create a CSS triangle with rounded corners?

Unfortunately, CSS triangles cannot have rounded corners because they are created using borders. Borders do not support the border-radius property, which is used to create rounded corners. However, you can create a similar effect using pseudo-elements and CSS transforms, but this is a more complex solution.

How can I create a CSS triangle with gradients?

Creating a CSS triangle with gradients is a bit tricky because the border property, which is used to create triangles, does not support gradients. However, you can achieve this effect by using a pseudo-element with a linear gradient and then transforming it into a triangle using the CSS transform property. Here’s an example:

.triangle {
position: relative;
width: 100px;
height: 100px;
}

.triangle::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(to right, red, blue);
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
In this code, the ::before pseudo-element creates a square with a gradient, and the clip-path property cuts it into a triangle.

How can I create a CSS triangle that points to the left or right?

To create a CSS triangle that points to the left or right, you need to adjust the border properties. For a triangle that points to the left, you can use the following code:

.triangle {
width: 0;
height: 0;
border-top: 50px solid transparent;
border-bottom: 50px solid transparent;
border-right: 50px solid red;
}
In this code, the border-right property creates the triangle, and the border-top and border-bottom properties create the transparent sides.

Can I animate a CSS triangle?

Yes, you can animate a CSS triangle using CSS animations or transitions. However, keep in mind that you can only animate certain properties, such as color, position, and size. You cannot animate the shape of the triangle itself. Here’s an example of a CSS triangle that changes color when hovered over:

.triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 50px solid red;
transition: border-bottom-color 0.5s ease;
}

.triangle:hover {
border-bottom-color: blue;
}
In this code, the transition property animates the color change of the triangle when it’s hovered over.

How can I create a CSS triangle with a shadow?

CSS triangles cannot have shadows because they are created using borders, and the box-shadow property does not apply to borders. However, you can create a similar effect by using a pseudo-element and applying the box-shadow to it. This is a more complex solution and may not work in all situations.

Can I use a CSS triangle as a tooltip arrow?

Yes, you can use a CSS triangle as a tooltip arrow. This is a common use case for CSS triangles. You can position the triangle using the CSS position property and align it with the tooltip box. Here’s an example:

.tooltip {
position: relative;
background: #333;
color: #fff;
padding: 10px;
}

.tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #333 transparent transparent transparent;
}
In this code, the ::after pseudo-element creates the triangle and positions it at the bottom of the tooltip box.

How can I create a CSS triangle with a different border color?

To create a CSS triangle with a different border color, you can use a pseudo-element and apply a border to it. Then, you can create the triangle using the border property on the main element. Here’s an example:

.triangle {
position: relative;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 50px solid red;
}

.triangle::before {
content: "";
position: absolute;
top: 2px;
left: -50px;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 50px solid white;
}
In this code, the ::before pseudo-element creates a white triangle that is slightly offset from the red triangle, creating the illusion of a border.

Can I create a CSS triangle with a pattern or image inside?

Unfortunately, CSS triangles cannot have patterns or images inside because they are created using borders. However, you can create a similar effect by using a pseudo-element with a background image and then transforming it into a triangle using the CSS transform property. This is a more complex solution and may not work in all situations.

How can I create a CSS triangle with a transparent background?

To create a CSS triangle with a transparent background, you can use the border property with transparent borders. Here’s an example:

.triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 50px solid red;
}
In this code, the border-bottom property creates the triangle, and the border-left and border-right properties are set to transparent, creating a triangle with a transparent background.

Kitty GiraudelKitty Giraudel
View Author

Non-binary trans accessibility & diversity advocate, frontend developer, author. Real life cat. She/they.

css triangle mixincss trianglessass
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week