Utilities for creating CSS shapes and graphic elements using pure CSS techniques, particularly the triangle mixin for creating directional arrows and decorative elements.
Generates CSS triangles pointing in specified directions using border manipulation techniques.
/**
* Generates a triangle pointing in a specified direction
* @param $direction - Direction: "up", "up-right", "right", "down-right", "down", "down-left", "left", "up-left"
* @param $width - Width of the triangle (with unit)
* @param $height - Height of the triangle (with unit)
* @param $color - Color of the triangle
*/
@mixin triangle($direction, $width, $height, $color);Usage Examples:
.arrow-up {
&::before {
@include triangle("up", 2rem, 1rem, #3498db);
content: "";
}
}
// Result:
// .arrow-up::before {
// border-style: solid;
// height: 0;
// width: 0;
// border-color: transparent transparent #3498db;
// border-width: 0 1rem 1rem;
// content: "";
// }
.dropdown-arrow {
&::after {
@include triangle("down", 10px, 6px, currentColor);
content: "";
margin-left: 8px;
}
}
.tooltip-pointer {
&::before {
@include triangle("left", 8px, 8px, #2c3e50);
content: "";
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
}
}The triangle mixin supports eight directional orientations:
// Upward pointing triangle
.triangle-up {
@include triangle("up", 20px, 15px, #e74c3c);
}
// Downward pointing triangle
.triangle-down {
@include triangle("down", 20px, 15px, #27ae60);
}
// Left pointing triangle
.triangle-left {
@include triangle("left", 15px, 20px, #3498db);
}
// Right pointing triangle
.triangle-right {
@include triangle("right", 15px, 20px, #9b59b6);
}// Upper-right diagonal
.triangle-up-right {
@include triangle("up-right", 20px, 20px, #f39c12);
}
// Lower-right diagonal
.triangle-down-right {
@include triangle("down-right", 20px, 20px, #e67e22);
}
// Lower-left diagonal
.triangle-down-left {
@include triangle("down-left", 20px, 20px, #16a085);
}
// Upper-left diagonal
.triangle-up-left {
@include triangle("up-left", 20px, 20px, #8e44ad);
}.dropdown-trigger {
position: relative;
padding-right: 24px;
&::after {
@include triangle("down", 8px, 4px, currentColor);
content: "";
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
transition: transform 0.2s ease;
}
&.open::after {
transform: translateY(-50%) rotate(180deg);
}
}
.select-arrow {
position: relative;
&::after {
@include triangle("down", 12px, 6px, #7f8c8d);
content: "";
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
}.breadcrumb {
display: flex;
align-items: center;
.item {
&:not(:last-child)::after {
@include triangle("right", 6px, 10px, #bdc3c7);
content: "";
margin: 0 8px;
}
}
}
.breadcrumb-chevron {
.separator {
@include triangle("right", 8px, 12px, rgba(0, 0, 0, 0.3));
margin: 0 12px;
}
}.tooltip {
position: relative;
// Top tooltip
&.tooltip-top::before {
@include triangle("down", 12px, 6px, #2c3e50);
content: "";
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
}
// Bottom tooltip
&.tooltip-bottom::before {
@include triangle("up", 12px, 6px, #2c3e50);
content: "";
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
}
// Left tooltip
&.tooltip-left::before {
@include triangle("right", 6px, 12px, #2c3e50);
content: "";
position: absolute;
right: -6px;
top: 50%;
transform: translateY(-50%);
}
// Right tooltip
&.tooltip-right::before {
@include triangle("left", 6px, 12px, #2c3e50);
content: "";
position: absolute;
left: -6px;
top: 50%;
transform: translateY(-50%);
}
}.speech-bubble {
position: relative;
background: #3498db;
color: white;
padding: 12px 16px;
border-radius: 8px;
// Bottom-left pointer
&.bottom-left::after {
@include triangle("down-left", 16px, 16px, #3498db);
content: "";
position: absolute;
bottom: -16px;
left: 20px;
}
// Bottom-right pointer
&.bottom-right::after {
@include triangle("down-right", 16px, 16px, #3498db);
content: "";
position: absolute;
bottom: -16px;
right: 20px;
}
}.step-indicator {
display: flex;
align-items: center;
.step {
flex: 1;
position: relative;
background: #ecf0f1;
height: 4px;
&.completed {
background: #27ae60;
}
&:not(:last-child)::after {
@include triangle("right", 16px, 20px, #ecf0f1);
content: "";
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
z-index: 1;
}
&.completed:not(:last-child)::after {
@include triangle("right", 16px, 20px, #27ae60);
}
}
}.banner {
position: relative;
background: #e74c3c;
color: white;
padding: 20px;
// Decorative corner fold
&::after {
@include triangle("down-right", 20px, 20px, rgba(0, 0, 0, 0.2));
content: "";
position: absolute;
top: 0;
right: 0;
}
}
.ribbon {
position: relative;
background: #f39c12;
color: white;
padding: 8px 24px;
// Ribbon tails
&::before,
&::after {
content: "";
position: absolute;
top: 100%;
}
&::before {
@include triangle("down-left", 12px, 8px, darken(#f39c12, 20%));
left: 0;
}
&::after {
@include triangle("down-right", 12px, 8px, darken(#f39c12, 20%));
right: 0;
}
}The triangle mixin includes comprehensive validation:
// Invalid direction
.invalid-triangle {
@include triangle("invalid-direction", 10px, 10px, red);
// Error: Direction must be `up`, `up-right`, `right`, `down-right`, `down`, `down-left`, `left` or `up-left`.
}
// Invalid color
.invalid-color {
@include triangle("up", 10px, 10px, "not-a-color");
// Error: `"not-a-color"` is not a valid color for the `$color` argument in the `triangle` mixin.
}