---
title: You might not need Bourbon
teaser: Replacing our beloved Sass tool set with native CSS.
tags: css,bourbon,sass,frontend,open source
author: Elaina Natario
published_on: 2024-09-12
---

Over 13 years ago, thoughtbot [introduced Bourbon to the open-source community](https://thoughtbot.com/blog/introducing-bourbon-sass-mixins). At a time when Sass was gaining momentum, our developers and designers created a library of useful mixins to accelerate development across projects. As the library expanded, we began managing versioning and distribution through Git and a Ruby gem. Bourbon quickly claimed the spot of the most GitHub stars among all our open source projects, and garnered significant attention over the years.

Today, many of the features [Bourbon](https://www.bourbon.io) once provided are now achievable with native CSS. This evolution is a positive step, allowing us to rely on standardized web capabilities rather than external dependencies. While Bourbon still serves specific use cases, it has largely become obsolete as native CSS simplifies our workflow and enhances the sustainability of our projects. Let’s explore how to replace each helper with modern CSS features.

## Directional border helpers

All of the border helpers ([border-color], [border-top-radius], [border-right-radius], [border-bottom-radius], [border-left-radius], [border-style], and [border-width]) in Bourbon provide a succinct way to define color, radii, style, and width for specific sides of an element when you’re dealing with multiple directions.

```scss
.element {
  @include border-width(1em null 20px);
}

// CSS output
.element {
  border-bottom-width: 20px;
  border-top-width: 1em;
}
```

Right off the bat, I don’t have a clever replacement for this [until CSS gets native mixins](https://github.com/w3c/csswg-drafts/issues/9350). These properties can also be written out the shorthand way, defining each value in clockwise order (top, right, bottom, left):

```css
.element {
  border-width: 1px 2px 10px 2px;
}
```

Which is the same as this:

```css
.element {
  border-top-width: 1px;
  border-right-width: 2px;
  border-bottom-width: 10px;
  border-left-width: 2px;
}
```

Shorthand can also be used to define horizontal and vertical values:

```css
.element {
  border-width: 1px 10px;
}
```

Which is the same as this:

```css
.element {
  border-top-width: 1px;
  border-right-width: 10px;
  border-bottom-width: 1px;
  border-left-width: 10px;
}
```

What’s not provided in native CSS shorthand is the `null` value in the Bourbon mixin that allows you to omit a direction. And while that is a handy feature, I’d argue if you’re doing anything complex with varying values for each direction, it’s likely better to be more verbose in your code so it’s clear which value is affecting which direction.

Additionally, this might also be a good place for a [custom property] if you’re using the same values in different elements:

```css
:root {
  --border-width-base: 1px;
  --border-width-large: 10px;

  --border-color-base: #eee;
  --border-color-fancy: #b4da55;
}

.element {
  border-top: var(--border-width-base) solid var(--border-color-base);
}

.element–fancy {
  border: var(--border-width-large) solid var(--border-color-fancy);
}
```

## Button and input element lists

The all buttons ([all-buttons], [all-buttons-active], [all-buttons-focus], [all-buttons-hover]) and all text inputs helpers ([all-text-inputs], [all-text-inputs-active], [all-text-inputs-focus], [all-text-inputs-hover], [all-text-inputs-invalid]) select elements that fall within those categories by their attribute and their associated pseudo class. These elements are often defined together when styling forms and their interactive states.

```scss
#{$all-text-inputs-focus} {
  border: 1px solid #1565c0;
}

// CSS output
[type='color']:focus,
[type='date']:focus,
[type='datetime']:focus,
[type='datetime-local']:focus,
[type='email']:focus,
[type='month']:focus,
[type='number']:focus,
[type='password']:focus,
[type='search']:focus,
[type='tel']:focus,
[type='text']:focus,
[type='time']:focus,
[type='url']:focus,
[type='week']:focus,
input:not([type]):focus,
textarea:focus {
  border: 1px solid #1565c0;
}
```

The [`:is` pseudo-class] allows us to write out these selector lists with their states in a similar way. You still have to actually write out the starting selectors, but once again, the argument that erring on the side of verbosity is usually more clear.

In this example we can target the `:focus` state of our selector list by appending it after our `:is` argument.

```css
:is(
  input:not([type]),
  select,
  textarea,
  [type="color"],
  [type="date"],
  [type="datetime"],
  [type="datetime-local"],
  [type="email"],
  [type="month"],
  [type="month"],
  [type="number"],
  [type="password"],
  [type="search"],
  [type="tel"],
  [type="text"],
  [type="time"],
  [type="url"],
  [type="week"]
):focus {
  border: 1px solid #1565c0;
}
```

You can also achieve this with [CSS nesting]:

```css
input:not([type]),
select,
textarea,
[type="color"],
[type="date"],
[type="datetime"],
[type="datetime-local"],
[type="email"],
[type="month"],
[type="month"],
[type="number"],
[type="password"],
[type="search"],
[type="tel"],
[type="text"],
[type="time"],
[type="url"],
[type="week"] {
  &:focus {
    border: 1px solid #1565c0;
  }
}
```

Yet another way is to use the [`:where` pseudo-class]. The syntax is identical to the [`:is` pseudo-class], but it has 0 specificity. This means that unlike the previous 2 examples, you can override the declarations later in the cascade if needed.

```css
:where(
  input:not([type]),
  select,
  textarea,
  [type="color"],
  [type="date"],
  [type="datetime"],
  [type="datetime-local"],
  [type="email"],
  [type="month"],
  [type="month"],
  [type="number"],
  [type="password"],
  [type="search"],
  [type="tel"],
  [type="text"],
  [type="time"],
  [type="url"],
  [type="week"]
):focus {
  border: 1px solid #1565c0;
}
```

Now I want to override the focus border color just for `[type="email"]` later on in the cascade. With `:where`, it'll take on this new styling (which it won't with `:is` and nesting):

```css
[type="email"]:focus {
  border-color: orange;
}
```

## Clearfix

With features like [flexbox] and [grid], there’s not a lot of need for using floats to create page layouts. And thus, not a huge need for the [clearfix] hack (which prevented a parent with floated children from collapsing its height). The mixin version provided in Bourbon is a short set of properties to write out manually if needed for one-off situations:

```css
.element::after {
  clear: both;
  content: "";
  display: block;
}
```

## Contrast-switch

The [contrast-switch] helper will output a lighter or darker foreground color depending on the contrast ratio with the background color.

```scss
.element {
  $button-color: #2d72d9;
  background-color: $button-color;
  color: contrast-switch($button-color, #222, #eee);
}

// CSS output
.element {
  background-color: #2d72d9;
  color: #eee;
}
```

This doesn’t have a native CSS fix …yet. The good news is there’s an [experimental function](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-contrast) that, if launched in browsers, would replace this helper.

One consideration is to eliminate the need for this by building a palette and style guide that defines these color combinations, maintaining [compliant contrast](https://webaim.org/resources/contrastchecker/).

## Ellipsis

[Ellipsis] is a mixin that allows you to truncate text and append an ellipsis when it overflows.

```scss
.element {
  @include ellipsis;
}

// CSS output
.element {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  word-wrap: normal;
}
```

A newer way to achieve this uses a property called [line-clamp]. This is a more intentional way (as opposed to its hack predecessor) of achieving even more flexible results, automatically appending the ellipses after defining the amount of lines you want to show.

Do note that this way requires the `-webkit` prefix (which is [well-supported](https://caniuse.com/css-line-clamp)) and a display value of `-webkit-box`.

In this example we want to truncate with an ellipses after 2 lines of text:

```css
.element {
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  display: -webkit-box;
  overflow: hidden;
}
```

## Font-face

Bourbon’s [font-face] mixin allows you to output a block of declarations to define font family names and file paths in a `@font-face` rule.

```scss
@include font-face(
  "source-sans-pro",
  "fonts/source-sans-pro-regular",
  ("woff2", "woff")
) {
  font-style: normal;
  font-weight: 400;
}

// CSS output
@font-face {
  font-family: "source-sans-pro";
  src: url("fonts/source-sans-pro-regular.woff2") format("woff2"),
       url("fonts/source-sans-pro-regular.woff") format("woff");
  font-style: normal;
  font-weight: 400;
}
```

This mixin’s best utility allows you to pass all the font extensions you’re using without writing out each file path (`woff`, `woff2`, `ttf`, etc.). Today, [all modern browsers support `woff2` fonts](https://caniuse.com/woff2), so the array here is no longer needed. Simply put, writing it out in native CSS now isn’t any more intense than what the mixin offers.

In this example, we’re using the [`font-display`] property to ensure the fallback fonts display in the browser while any custom fonts are still downloading. Additionally, this is a [variable font], so we define a range of font weights instead of one number (which also saves us from declaring more font files).

```css
@font-face {
  font-display: swap;
  font-family: "WorkSans";
  font-style: normal;
  font-weight: 100 900;
  src: url("/static/fonts/work-sans.woff2") format("woff2");
}
```

## Font stacks

The font stacks ([font-stack-helvetica], [font-stack-verdana], [font-stack-system], [font-stack-garamond], [font-stack-hoefler-text], [font-stack-consolas], [font-stack-courier-new], [font-stack-monaco]) are variables that define a list of web-safe font families and their fallbacks.

```scss
.element {
  font-family: $font-stack-helvetica;
}

// CSS Output
.element {
  font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
```

You can refer to these font stacks (or better yet, create your own list of [web-safe fonts]) and use a [custom property] to define them.

```css
:root {
  --font-sans-stack: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  --font-serif-stack: "Garamond", "Baskerville", "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif;
  --font-body: "Work Sans", var(--font-sans-stack);
  --font-display: "Merriweather", var(--font-serif-stack);
}

body {
  font-family: var(--font-body);
}

h1,
h2,
h3 {
  font-family: var(--font-display);
}
```

## Hide-text

The [hide-text] helper is yet another legacy CSS hack, using a text indent to hide text, usually in favor of an image. Generally, I’d say avoid this hack, especially since it won’t [localize to RTL languages](https://snook.ca/archives/html_and_css/hiding-content-for-accessibility).

```css
.element {
  overflow: hidden;
  text-indent: 101%;
  white-space: nowrap;
}
```

If you’re using this to show an image that looks like text, you can instead either:

* Use a custom font
* Use an [accessible inline SVG](https://github.com/jamesmartin/inline_svg)

If you’re using hidden text to describe an actual image, you can instead:

* Use [alt text](https://ericwbailey.design/published/dungeons-and-dragons-taught-me-how-to-write-alt-text/)
* Use an [`aria-label`]
* And/or use a [figure element with a figcaption](https://thoughtbot.com/blog/alt-vs-figcaption)

## Hide-visually

Visually hiding an element allows you to remove an element from the display but still make it accessible in the DOM to screen readers. This can be useful for describing landmarks on the page for navigation without having to actually style them within your layout. However, hiding elements for some users and not for others, even with the best intentions, is a complex discussion.

```css
.element {
  border: 0;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(100%);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}
```

In his [article on visually hidden content](https://www.scottohara.me/blog/2023/03/21/visually-hidden-hack.htm), Scott O’Hara notes:

> Remember, not all screen reader users are completely blind. Some of these users, for instance, might be able to see or partially see a custom checkbox on their iOS or Android device. They might then think to try and press or drag their finger across their screen to “explore by touch” while the screen reader is enabled. However, they’d then be seemingly unable find this checkbox that visually appears to be there, but is really shoved off into a 1px by 1px square somewhere on the page, if not entirely positioned off screen. Fun! Good luck finding that 1px by 1px checkbox that might not even be discoverable in the viewport.”

The main takeaway from this is to really think about why you might need to visually hide an element. Is it simply to tidy any clutter? Would it break the layout to surface that information to all viewers? Can we instead leverage attributes (which also have their own pitfalls) like [`aria-label`] and [`aria-description`]?

## Margin and padding

Much like the directional border helpers, Bourbon’s [margin] and [padding] mixins define directional spacing while also allowing you to `null` a side, effectively skipping it within the shorthand.

```scss
.element {
  @include margin(10px 3em 20vh null);
}

// CSS output
.element {
  margin-bottom: 20vh;
  margin-right: 3em;
  margin-top: 10px;
}
```

```css
.element-one {
  @include padding(null 1rem);
}

// CSS output
.element-one {
  padding-left: 1rem;
  padding-right: 1rem;
}
```

Margins are often used to define regular spacing between elements. Instead of applying a margin to each element (or using the [axiomatic owl method]), set up a layout grid with [flexbox] or [grid] and use the [`gap` property] to define predictable gutters both horizontally and vertically.

```css
.parent-element {
  display: grid;
  gap: 1rem 20vw;
  grid-template-columns: 1fr 3fr 1fr 1fr;
  grid-template-rows: repeat(4, 10vh);
}
```

The padding mixin also defines spacing, but is more commonly used within the confines of an element instead of outside of it. You might still use the [`gap` property], especially if you’re taking advantage of [subgrid] or are managing element layout with [flexbox]. You can also set up predictable spacing both between and within elements by using custom properties and applying them throughout your CSS:

```css
:root {
  --space-base: 1.5rem;
  --space-small: 1rem;
  --space-medium: 2rem;
  --space-large: 5rem;
}

.element {
  padding: var(--space-base) var(--space-small);
}

.layout {
  display: grid;
  gap: var(--space-base);
  grid-template-columns: repeat(3, 1fr);
}
```

## Modular-scale

[Modular-scale] defines a ratio to increment a value consistently, allowing you to create spatial relationships within a scale. This is particularly useful for making a typographical scale, setting a baseline size as well as larger and smaller sizes, all while falling along the same ratio (e.g. a header might always be 3 times larger than the baseline size).

```scss
.element {
  font-size: modular-scale(2);
}

// CSS output
.element {
  font-size: 1.5625em;
}
```

Creating type scales has now grown into developing responsive type systems. The [clamp] function allows us to linearly scale any size, including font size according to the viewport. The function takes arguments where you define a minimum value, the preferred value, and the maximum value:

```css
h1 {
  font-size: clamp(1.5rem, 3vw, 4.2rem);
}
```

You can also insert math within this function:

```css
p {
  font-size: clamp(1.25rem, 1.08rem + 0.5vw, 1.5rem);
}
```

Sites like [utopia.fyi] have a generator where you can configure a full type scale for yourself:

```css
:root {
  /* Step 0: 18px → 20px */
  --step-0: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem);

  /* Step 1: 21.6px → 25px */
  --step-1: clamp(1.35rem, 1.2761rem + 0.3696vw, 1.5625rem);

  /* Step 2: 25.92px → 31.25px */
  --step-2: clamp(1.62rem, 1.5041rem + 0.5793vw, 1.9531rem);

  /* Step 3: 31.104px → 39.0625px */
  --step-3: clamp(1.944rem, 1.771rem + 0.8651vw, 2.4414rem);

  /* Step 4: 37.3248px → 48.8281px */
  --step-4: clamp(2.3328rem, 2.0827rem + 1.2504vw, 3.0518rem);
}
```

If you’d like to recreate the functionality of Bourbon’s mixin, you can use the [pow] math function in CSS, which returns the value of a based raised to the power of that number:

```css
:root {
  --scale-base: 1em;
  --scale-ratio: 1.25;

  --font-size-base: calc(var(--scale-base) * pow(var(--scale-ratio), 1));
  --font-size-small: calc(var(--scale-base) * pow(var(--scale-ratio), 0.5));
  --font-size-medium: calc(var(--scale-base) * pow(var(--scale-ratio), 2));
  --font-size-large: calc(var(--scale-base) * pow(var(--scale-ratio), 4));
}

body {
  font-size: var(--font-size-base);
}

h1 {
  font-size: var(--font-size-large);
}

h2 {
  font-size: var(--font-size-medium);
}

small {
  font-size: var(--font-size-small);
}
```

## Overflow-wrap

[Overflow-wrap] supported browsers that didn’t yet use `overflow-wrap` with the legacy property of `word-wrap`. [All modern browsers now support `overflow-wrap`](https://caniuse.com/wordwrap) so this mixin is no longer needed.

If this wasn't well supported and we needed to provide a fallback for this property, a more optimized way would use the [`@supports` feature query]:

```css
.element {
  word-wrap: break-word;
}

@supports (overflow-wrap: break-word) {
  .element {
    overflow-wrap: break-word[
  }
}
```

## Position

For setting an element’s position properties, Bourbon provides a one-liner mixin, [position], to set that value along with the placement relative to the `top`, `right`, `bottom`, and `left` sides.

```scss
.element {
  @include position(relative, 0 null null 10em);
}

// CSS output
.element {
  left: 10em;
  position: relative;
  top: 0;
}
```

There is no perfect replacement for this apart from using a [custom property] for the side values. However, you may find yourself _needing_ to define placement less and less with layout options such as [flexbox] and [grid]. The mechanics of these layouts allow you to position items along an axis with properties like [align-content], [align-items], [align-self], [justify-content], [justify-items], and [justify-self]. You can even use [grid to overlap elements](https://thoughtbot.com/blog/absolute-positioning-with-css-grid) in a similar manner to absolute positioning.

These two child elements (a and b) will overlap:

```css
.container {
  display: grid;
}

.element-a {
  grid-area: 1 / 1;
}

.element-b {
  grid-area: 1 / 1;
}
```

Another new feature that is still experimental (so not fully supported yet) is [anchor positioning], allowing you to tether elements together.

## Prefixer and value-prefixer

Vendor prefixes allow support for newer properties across different browsers before the implementation is standardized across those browsers. The [prefixer] and [value-prefixer] mixins generated those for any defined property or value, saving you the time of writing them all out.

```scss
.element {
  @include prefixer(appearance, none, ("webkit", "moz"));
}

// CSS output
.element {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
```

```css
.element {
  @include value-prefixer(cursor, grab, ("webkit", "moz"));
}

// CSS output
.element {
  cursor: -webkit-grab;
  cursor: -moz-grab;
  cursor: grab;
}
```

While prefixes are still used for some properties, they’re not as common as they once were. You can check [caniuse.com] for support of certain CSS features. Additionally, a plugin like [autoprefixer] automatically outputs recommended vendor prefixes from your CSS.

## Shade and tint

The [shade] and [tint] functions mix a color with a percentage black or white, respectively. Some folks may use this for developing a relational color palette. For example you might use a shade of your base button color when in a hover state.

```scss
.element {
  background-color: shade(#ffbb52, 60%);
}

// CSS output
.element {
  background-color: #664a20;
}
```

Before we dive into the native CSS replacement, I’ll argue that these functions often result in color palettes with far too many gradations of the same color. For example `button A` has a base color of blue and a tint of 30% while `button B` has a base color blue and a tint of 50%, meanwhile `element C` has a border color of blue with a shade of 10%. While the colors all tie back to that base blue color, the result is an undefined set of variations. Is the lighter version of blue a 20% tint or a 22% tint? What’s the use case for these variations? For that reason, I’d suggest being explicit about your color palette and defining the exact hex or rgb (or other color space) value in lieu of a function output.

With this caveat in mind, we can take advantage of [CSS Colors Module Level 4] to replace these Bourbon functions! The new spec allows us to define hues, saturation, lightness, etc. across different color spaces.

The [color-mix] function is actually from the [Level 5 Module] and is well-supported across browsers. Much like the Bourbon functions, it takes two colors and mixes them by a percentage and according to the defined color space. We can use this to make dark and light versions of a base color:

```css
:root {
  --base-color: #7df9ff;
  --light-color: color-mix(in srgb, var(--base-color), white 75%);
  --dark-color: color-mix(in srgb, var(--base-color), black 50%);
}

.element-base {
  background-color: var(--base-color);
}

.element-light {
  background-color: var(--light-color);
}

.element-dark {
  background-color: var(--dark-color);
}
```

Alternatively, you can use [math functions with calc](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors#using_math_functions) to calculate the outputs.

```css
:root {
  --base-color: #7df9ff;
  --light-color: rgb(from var(--base-color) calc(r + 100) calc(g + 100) calc(b + 100));
  --dark-color: rgb(from var(--base-color) calc(r - 100) calc(g - 100) calc(b - 100));
}

.element-base {
  background-color: var(--base-color);
}

.element-light {
  background-color: var(--light-color);
}

.element-dark {
  background-color: var(--dark-color);
}
```

## Size

The [size] mixin defines a width and height. You can also just set one number and it’ll set that number for both the width and height.

```scss
.first-element {
  @include size(2em);
}

// CSS output
.first-element {
  width: 2em;
  height: 2em;
}
```

You can use a [custom property] either globally or scoped to an element for this:

```css
.element {
  --size: 3rem;
  height: var(--size);
  width: var(--size);
}
```

Additionally, that size units are no longer constrained to pixels, rems, ems, and percentages. We can set sizes relative to the viewport with `vw`, `vh`, `vmin`, and `vmax`; relative to a container with `cqh`, `cqi`, `cqmax`, and `cqmin`; using intrinsic size with `min-content`, `max-content`, and `fit-content`; with math functions like `calc`; with comparison functions like `min`, `max`, and `clamp`. You might also not need to set explicit widths and heights if your element is within a [grid] and defined with a fractional unit `fr`.

For elements with width and height relative to each other, you can use the [`aspect-ratio`] property to define that ratio. This ratio will be maintained no matter the size of the viewport:

```css
.element {
  aspect-ratio: 16 / 9;
}
```

## Strip-unit

[Strip-unit] is a function that returns a unitless number.

```scss
$dimension: strip-unit(10em);

// Output
$dimension: 10;
```

This was useful for scss calculations that required a variety of units (e.g. `1px * 2rem - 20%`). Thankfully [calc] does a very good job of doing math with different units, including custom properties, so you shouldn’t have to strip or convert numbers for most use cases. Beyond calc, native CSS has [a variety of advanced math functions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Functions/Using_CSS_math_functions) to output stepped values, calculate trigonometric and exponential equations, and more!

```css
.element {
  width: calc((10vw * 1.5rem) - var(--space-base));
}
```

## Triangle

If you’re frequently creating tooltips or anything speech-bubble-shaped, you’ll be generating a triangle to anchor that bubble to a specific element. The [triangle] mixin does just that while also positioning it in a specific direction.

```scss
.element {
  &::before {
    @include triangle("up", 2rem, 1rem, #b25c9c);
    content: "";
  }
}

// CSS output
.element::before {
  border-style: solid;
  height: 0;
  width: 0;
  border-color: transparent transparent #b25c9c;
  border-width: 0 1rem 1rem;
  content: "";
}
```

In native CSS, the [polygon function] outputs any cornered shape, based on pairs of coordinates. Along with [clip-path], you can render a triangle in any direction:

```css
:root {
  --triangle-up: polygon(0% 100%, 50% 0%, 100% 100%);
  --triangle-right: polygon(0 0, 0 100%, 100% 50%);
  --triangle-down: polygon(100% 0, 0 0, 50% 100%);
  --triangle-left: polygon(100% 100%, 100% 0, 0 50%);
}

.triangle {
  width: 2rem;
  height: 2rem;
  background-color: blue;
}

.triangle-up {
  clip-path: var(--triangle-up);
}

.triangle-right {
  clip-path: var(--triangle-right);
}

.triangle-down {
  clip-path: var(--triangle-down);
}

.triangle-left {
  clip-path: var(--triangle-left);
}
```

```html
<div class="triangle triangle-up"></div>
<div class="triangle triangle-right"></div>
<div class="triangle triangle-down"></div>
<div class="triangle triangle-left"></div>
```

Alternatively, you can use `transform: rotate` to adjust the triangle’s position:

```css
:root {
  --triangle-shape: polygon(0% 100%, 50% 0%, 100% 100%);
}

.triangle {
  width: 2rem;
  height: 2rem;
  background-color: green;
  clip-path: var(--triangle-shape);
}

.triangle-rotate-up {
  transform: rotate(0deg);
}

.triangle-rotate-right {
  transform: rotate(90deg);
}

.triangle-rotate-down {
  transform: rotate(180deg);
}

.triangle-rotate-left {
  transform: rotate(-90deg);
}
```

```html
<div class="triangle triangle-rotate-up"></div>
<div class="triangle triangle-rotate-right"></div>
<div class="triangle triangle-rotate-down"></div>
<div class="triangle triangle-rotate-left"></div>
```

[align-content]: https://developer.mozilla.org/en-US/docs/Web/CSS/align-content
[align-items]: https://developer.mozilla.org/en-US/docs/Web/CSS/align-items
[align-self]: https://developer.mozilla.org/en-US/docs/Web/CSS/align-self
[all-buttons]: https://www.bourbon.io/docs/latest/#all-buttons
[all-buttons-active]: https://www.bourbon.io/docs/latest/#all-buttons-active
[all-buttons-focus]: https://www.bourbon.io/docs/latest/#all-buttons-focus
[all-buttons-hover]: https://www.bourbon.io/docs/latest/#all-buttons-hover
[all-text-inputs]: https://www.bourbon.io/docs/latest/#all-text-inputs
[all-text-inputs-active]: https://www.bourbon.io/docs/latest/#all-text-inputs-active
[all-text-inputs-focus]: https://www.bourbon.io/docs/latest/#all-text-inputs-focus
[all-text-inputs-hover]: https://www.bourbon.io/docs/latest/#all-text-inputs-hover
[all-text-inputs-invalid]: https://www.bourbon.io/docs/latest/#all-text-inputs-invalid
[anchor positioning]:https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples
[`aria-description`]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-description
[`aria-label`]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label
[`aspect-ratio`]: https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio
[autoprefixer]: https://github.com/postcss/autoprefixer
[axiomatic owl method]: https://alistapart.com/article/axiomatic-css-and-lobotomized-owls/
[border-bottom-radius]: https://www.bourbon.io/docs/latest/#border-bottom-radius
[border-color]: https://www.bourbon.io/docs/latest/#border-color
[border-left-radius]: https://www.bourbon.io/docs/latest/#border-left-radius
[border-right-radius]: https://www.bourbon.io/docs/latest/#border-right-radius
[border-style]: https://www.bourbon.io/docs/latest/#border-style
[border-top-radius]: https://www.bourbon.io/docs/latest/#border-top-radius
[border-width]: https://www.bourbon.io/docs/latest/#border-width
[calc]: https://developer.mozilla.org/en-US/docs/Web/CSS/calc
[caniuse.com]: https://caniuse.com/
[clamp]: https://developer.mozilla.org/en-US/docs/Web/CSS/clamp
[clearfix]: https://www.bourbon.io/docs/latest/#clearfix
[clip-path]: https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path
[color-mix]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix
[contrast-switch]: https://www.bourbon.io/docs/latest/#contrast-switch
[CSS Colors Module Level 4]: https://developer.mozilla.org/en-US/blog/css-color-module-level-4/
[CSS nesting]: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector
[custom property]: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
[Ellipsis]: https://www.bourbon.io/docs/latest/#ellipsis
[flexbox]: https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox
[`font-display`]: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
[font-face]: https://www.bourbon.io/docs/latest/#font-face
[font-stack-consolas]: https://www.bourbon.io/docs/latest/#font-stack-consolas
[font-stack-courier-new]: https://www.bourbon.io/docs/latest/#font-stack-courier-new
[font-stack-garamond]: https://www.bourbon.io/docs/latest/#font-stack-garamond
[font-stack-helvetica]: https://www.bourbon.io/docs/latest/#font-stack-helvetica
[font-stack-hoefler-text]: https://www.bourbon.io/docs/latest/#font-stack-hoefler-text
[font-stack-monaco]: https://www.bourbon.io/docs/latest/#font-stack-monaco
[font-stack-system]: https://www.bourbon.io/docs/latest/#font-stack-system
[font-stack-verdana]: https://www.bourbon.io/docs/latest/#font-stack-verdana
[`gap` property]: https://developer.mozilla.org/en-US/docs/Web/CSS/gap
[grid]: https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids
[hide-text]: https://www.bourbon.io/docs/latest/#hide-text
[`:is` pseudo-class]: https://developer.mozilla.org/en-US/docs/Web/CSS/:is
[justify-content]: https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content
[justify-items]: https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items
[justify-self]: https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self
[Level 5 Module]: https://drafts.csswg.org/css-color-5/#color-mix
[line-clamp]: https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-line-clamp
[margin]: https://www.bourbon.io/docs/latest/#margin
[Modular-scale]: https://www.bourbon.io/docs/latest/#modular-scale
[Overflow-wrap]: https://www.bourbon.io/docs/latest/#overflow-wrap
[padding]: https://www.bourbon.io/docs/latest/#padding
[polygon function]: https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape/polygon
[position]: https://www.bourbon.io/docs/latest/#position
[pow]: https://developer.mozilla.org/en-US/docs/Web/CSS/pow
[prefixer]: https://www.bourbon.io/docs/latest/#prefixer
[shade]: https://www.bourbon.io/docs/latest/#shade
[size]: https://www.bourbon.io/docs/latest/#size
[Strip-unit]: https://www.bourbon.io/docs/latest/#strip-unit
[subgrid]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Subgrid
[`@supports` feature query]: https://developer.mozilla.org/en-US/docs/Web/CSS/@supports
[tint]: https://www.bourbon.io/docs/latest/#tint
[triangle]: https://www.bourbon.io/docs/latest/#triangle
[utopia.fyi]: https://utopia.fyi/type/calculator/
[value-prefixer]: https://www.bourbon.io/docs/latest/#value-prefixer
[variable font]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide
[web-safe fonts]: https://www.cssfontstack.com/
[`:where` pseudo-class]: https://developer.mozilla.org/en-US/docs/Web/CSS/:where
