Custom WordPress site specialists - web design, hosting and online growth

How to create a CSS design system from scratch?

Buy Me a Coffee
How to create a CSS design system from scratch?

A CSS design system is the core of your web design, so it's important that it brings flexibility and consistency throughout the design process. Making a CSS design system from scratch is, in my opinion, the second best thing in web design. The very best is using your own CSS design system to make beautiful layouts for your website.

You will be brought back to the very start – from adding a CSS browser reset to adding features and fixing issues. If this is your first time creating a CSS design system from scratch, I can assure you that this is an educational experience.

Let's start with the process of creating a CSS design system from scratch. There are a lot of steps, but don't worry, no design system is done in 1 day. In fact, I am still tweaking the CSS design system of this very website every now and then. I am even jealous of the CSS design system I will give away for free for you to enjoy.

Use the demo to compare your code throughout this article by clicking the button below.

View demo

These are the steps to making a CSS design system that I will guide you through:

  1. Setting up the files
  2. CSS browser reset
  3. Font size and line height
  4. Colors
  5. Containers
  6. Rows and columns
  7. Spacing between elements
  8. Styling for common elements
  9. Creating a simple layout
  10. Utility classes for simple styling
  11. Styling for less common elements
  12. Utility classes for advanced layouts
  13. Creating an advanced layout
  14. Breakpoint-specific utility classes for responsive layouts
  15. Making your layout responsive
  16. Creating a header
  17. Creating a top bar
  18. Creating a footer

Let's begin!

Setting up the files

The bare minimum that you need to make a CSS design system is a folder called css-design-system with an HTML file called index.html, and a CSS file called index.css within that folder.

The contents of the HTML file:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="index.css">
    <meta name="viewport" content="width=device-width">
    <meta charset="UTF-8">
  </head>
  <body class="color-scheme-dark">
    <main>
      <article>
      </article>
    </main>
  </body>
</html>

The CSS file contains nothing yet.

CSS browser reset

Your web browser adds default styling for all HTML elements, but not every browser adds the same styling. That's why it's best to reset all styling, in order to make the CSS design system work the same way in all modern web browsers.

This is the CSS browser reset that I use:

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin:0; padding:0; border:0; font-size:100%; font:inherit; vertical-align:baseline; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

Add the CSS as shown above to the CSS file index.css. All CSS code you are about to write further into this article, is supposed to be added below the CSS browser reset, in order to be able to overwrite those if the specificity is equal.

There are multiple elements which's styling has not yet been reset, but since those styles are going to be defined anyways, there is no need to redefine those styles beforehand.

Font size and line height

The first thing you do when making a CSS design system, is setting the font sizes and line heights for the body and heading elements.

I suggest you add the following HTML inside the <article>-element:

<h2>Lorem ipsum dolor sit</h2>
<p>Lorem ipsum <a href="#">dolor sit</a>.</p>
<h2>Lorem ipsum dolor sit</h2>
<h3>Lorem ipsum dolor sit</h3>
<p>Lorem ipsum dolor sit.</p>

As you can see, we start by adding <h2> and <h3>-elements with paragraphs in between. These elements are very common, so it's good to use those elements as a starting point for defining font sizes. Less common elements, like <h1>, <h4>, <h5>, and <h6>, will use those font sizes as a point of reference.

This is the CSS that I ended up with:

body {
  font-family: Arial;
  font-size:   19px;
  line-height: 1.5em;
}
 h1,  h2,  h3,  h4,  h5,  h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: Arial;
  font-weight: 700;
  line-height: 1.2em;
}
h1, .h1 {
  font-size: 40px;
}
h2, .h2 {
  font-size: 33px;
}
h3, .h3 {
  font-size: 28px;
}
h4, .h4 {
  font-size: 24px;
}
h5, .h5 {
  font-size: 21px;
}
h6, .h6 {
  font-size: 19px;
}

As you can see in the example above, I did not target <p>-elements directly. I targeted the <body>-element instead, because paragraphs will inherit the font properties from its ancestor elements.

Using inheritance instead of directly targeting individual elements makes it easier for you to make utility classes that affect nested elements without having to target them directly.

For example, a class to overwrite the color scheme for a section. Imagine if I directly targeted paragraphs and <h1> through <h6>. That means I would have to create 7 selectors in order to overwrite the color of those elements if they were to be placed in a section with a different color scheme.

Instead of doing that, I rely on those elements inheriting the color from its ancestor elements. This will save you a lot of frustration and butchering in order to fix CSS problems whilst creating new ones in your design system.

Colors

Colors are used by multiple elements in your CSS design system. In order to manage those colors at one place, I suggest storing them as CSS variables to be able to refer to them from anywhere in the code of your CSS design system.

This is how you define CSS variables (I suggest placing this below the CSS browser reset):

:root {
  --black:        rgb(0, 0, 0);
  --darker-blue:  rgb(5, 5, 29);
  --dark-blue:    rgb(12, 12, 70);
  --light-blue:   rgb(13, 100, 176);
  --lighter-blue: rgb(180, 220, 255);
  --gray:         rgb(167, 167, 167);
  --light-gray:   rgb(210, 210, 210);
  --lighter-gray: rgb(243, 243, 243);
  --off-white:    rgb(252, 252, 252);
  --white:        rgb(255, 255, 255);

  --white-a90:    rgba(255, 255, 255, 0.9);
}

CSS variables are basically CSS properties, but they don't have any effect on their own. Instead, you make use of these variables using CSS' var-function to use them as a value for actual properties. For example, let's give hyperlinks a dark blue color:

a {
  color: var(--dark-blue);
}

Now, all hyperlinks are dark blue. What happens when those hyperlinks are placed in a section with a dark background color? They will become unreadable, so a hyperlink's color should be dynamic.

Let's define the color of hyperlinks on a light background (dark color scheme for the text) as a variable called --hyperlink-color with a value that refers to variable --dark-blue and then set the hyperlink color using variable --hyperlink-color.

a {
  color: var(--hyperlink-color);
}
.color-scheme-dark {
  --hyperlink-color: var(--dark-blue);
  color:             var(--black);
}

Now, look back at your HTML file and you might notice that class color-scheme-dark is added to the <body>-element. The color variables for elements are defined for elements with this class, to make it dark text on a light background, to ensure readability.

Let's add a class that gives a section a black background color and a class that indicates a light color scheme for its text. Add the the following CSS code below the rule set with selector .color-scheme-dark:

.color-scheme-light {
  --hyperlink-color: var(--lighter-blue);
  color:             var(--white-a90);
}
.bg-black {
  background-color: var(--black);
}

The code above allows sections to have a black background color using class bg-black and the hyperlink color to be lighter blue instead of dark blue by using class color-scheme-light, without directly targeting the hyperlinks within the section.

Let's add classes for the other colors as well:

.bg-darker-blue {
  background-color: var(--darker-blue);
}
.bg-dark-blue {
  background-color: var(--dark-blue);
}
.bg-light-blue {
  background-color: var(--light-blue);
}
.bg-lighter-blue {
  background-color: var(--lighter-blue);
}
.bg-gray {
  background-color: var(--gray);
}
.bg-light-gray {
  background-color: var(--light-gray);
}
.bg-lighter-gray {
  background-color: var(--lighter-gray);
}
.bg-off-white {
  background-color: var(--off-white);
}
.bg-white {
  background-color: var(--white);
}

Why inheritance is better than directly overriding values

Coming back to changing the color of hyperlinks within sections with dark background colors.. You might think, why not just directly overwrite the hyperlink color when placed in a dark background?

Imagine that you made a website with a white background and black text (dark color scheme). You add a section with a dark background and add the class to indicate a light color scheme for its text. Let's say you write the following CSS to do so:

.color-scheme-light a {
  color: var(--lighter-blue);
}

Then, inside of the same section you have a block that has a light background and needs dark text (dark color scheme), so you have to overwrite that value again. So there you go, you write the following CSS:

.color-scheme-dark a {
  color: var(--dark-blue);
}

Now, what happens next is a result of one selector overriding another selector. Selector .color-scheme-dark comes after selector .color-scheme-light, so it overwrites it, meaning you end up with the following: a website with a white background color and dark text, a section with a dark background color and dark text and within that section a block with a light background and dark text.

Visualization of broken color schemes

Inheritance, on the other hand, is like a waterfall, or a cascade. For example's sake, let's say the color value is the water of a waterfall. The water keeps falling down until it hits the ground. If you set a value, you will set a new ground to stop the waterfall, and you start a new waterfall of which its water has a new value (in this case the value for the color property).

Combine this with CSS variables, and you can start new waterfalls for the value of a CSS variable, while elements within the waterfall use that CSS variable for properties like the color of its text.

This way you avoid having two selectors overriding each other as is the case when directly overwriting values. That is why inheritance works so well in combination with CSS variables.

Containers

Containers are elements that determine the width of all content placed within it.

Let's say you have a viewport that is 1920 pixels wide. Inside that there is a container that is 1570 pixels wide and it's centered. This means that there will be empty space on the left and right of the container.

This is the CSS to create a container:

/* DT - Desktop */
.container {
  width:         1570px;
  margin-left:   auto;
  margin-right:  auto;
  padding-left:  25px;
  padding-right: 25px;
}

Responsive container width

If you have a screen that is 1200 pixels wide, then 1570 pixels is too wide. This means that the container has to change its width on smaller viewports.

When defining responsive widths for a container, you define the following: the minimum viewport width in which the container will fit, the maximum viewport width in which the container is wide enough, and the container width itself.

Let's say that you want to add a container width for viewports that are at least 1200 pixels wide and below 1600 pixels wide.

The container width should be a little bit narrower than the minimum viewport width, since the scrollbar can take up to 17 pixels of the viewport width by default. So let's subtract 30, because that is an even number, it's easy to remember and it has some room for whenever some browser decides to use a wider scrollbar.

Let's say the container is 1170 pixels wide. This is how I suggest you write this CSS:

/* LT - Laptop & Small desktop */
@media (min-width: 1200px) and (max-width: 1599.98px) {
  .container {
    width: 1170px;
  }
}

Did you notice I wrote max-width: 1599.98px? If you write 1599 pixels instead, there is a chance that while resizing your viewport that it can have fractional pixels. This will make it possible for your viewport not to match that media query, which is not good. It's rare, but it's easy to fix, so why not write 1599.98px instead?

Also.. If you write 1599.98px then any viewport below 1600 pixels wide will match the media query. However, if you wrote 1599.99px, then a viewport that is 1600 pixels wide would also match the media query. Why? I don't know, but I found out about this by looking at the CSS of the website of Bootstrap; they used that fix.

Now, let's go back to making responsive widths for the container. Let's say that, for viewports that are at least 992 pixels wide and below 1200 pixels wide, that the container width is 962 pixels wide.

/* TL - Tablet (landscape) */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .container {
    width: 962px;
  }
}

For viewports with a width below 1025 pixels, you can assume they do not have a scrollbar that takes up content. Viewports with such a width are most certainly tablets, not desktop devices.

Although the example above targets viewports of atleast 992 pixels wide, it does reach viewport widths that are below 1200 pixels. This is why the possibility of a scrollbar is taken into consideration and that is why 30 pixels has been subtracted from the minimum viewport width to get the container width as a result.

Below I made 3 more breakpoints, one for tablets, one for mobile devices in landscape mode or small tablets, and one for mobile devices in portrait mode.

The container is now full width for all the breakpoints written below:

/* TP - Tablet (portrait) & Tall mobile (landscape) */
@media (min-width: 768px) and (max-width: 991.98px) {
  .container {
    width: 100%;
  }
}

/* ML - Mobile (landscape) & Small tablet */
@media (min-width: 576px) and (max-width: 767.98px) {
  .container {
    width: 100%;
  }
}

/* MP - Mobile (portrait) */
@media (max-width: 575.98px) {
  .container {
    width: 100%;
  }
}

Now wrap the content within <article> in a <div>-element with class container.

Rows and columns

Now we have some essentials, we need one more thing to make actual layouts. We need rows and columns. I suggest using flexbox for rows, because it will give you a lot of flexibility; hence the name flexbox. This is how:

.row {
  display:        flex;
  flex-wrap:      wrap;
  flex-direction: row;
  align-items:    flex-start;
}

Let's break it down.

Value flex for property display turns the element into a flexbox.

Value wrap for property flex-wrap enables the flex items (columns) within the flexbox (row) to wrap to a new line, if there is no more space available.

Value row for property flex-direction makes sure that the columns go from left to right. With flexbox you can use value column to make flex items go from top to bottom, and you can append -reverse to create values row-reverse and column-reverse - those will reverse the order in which the columns are presented.

Value flex-start for property align-items makes sure the items are aligned at the top instead of stretching the columns height, which is the default behavior of flexboxes and makes columns have the same height if on the same line.

Stretching columns is not bad, but I have encountered issues where a flexbox still caused stretching on flex items if the flexbox was inside a parent flexbox that had the default stretching behavior.

I even tried applying and justify-content: flex-start on the vertical flexbox, but none seemed to work, so I applied align-items: flex-start to the parent flexbox (which is a horizontal flexbox) and then the stretching and unexplainable space was gone.

In the past I have also encountered cross browser bugs on Safari when it comes to the default stretch behavior, so I decided to disable the stretching behavior by default, while still allowing it using a class.

Now, let's add a column class. Columns need spacing between each other and they need an automatic width if not specified, which will depend on how many columns there are in the same row:

.col {
  flex-basis: 0px;
  flex-grow:  1;
}

What does flex-basis: 0px and flex-grow: 1 do? CSS property flex-grow enables the column to grow if there is space available, while setting property flex-basis to 0px will make all columns start at a width of 0 pixels and then grow by an amount equal to the amount all other columns do.

This makes it so that you don't have to specify a width for each column if you want all of them to be the same width. The available space is shared over all flex items, and the value for property flex-grow will determine how much of that available space is getting taken by that very flex item.

Spacing between elements

Spacing is one of the most important aspects of web design. It makes or breaks web designs. Consistent spacing is what takes the most time and effort when making a CSS design system. The goal is to make it as automatic as possible, while making it possible to fine tune them if necessary.

Spacings can be applied using both margins and paddings, but paddings should be used when an element has a background color, and margins are to be used to push the element away from other elements to create space.

Let's start by adding 50px of top margin for the <main>-element.

main {
  margin-top: 50px;
}

Now, let's add spacing for textual elements. Add the following below the rule set with selector h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6:

 h1:not(:first-child),  h2:not(:first-child),  h3:not(:first-child),  h4:not(:first-child),  h5:not(:first-child),  h6:not(:first-child),
.h1:not(:first-child), .h2:not(:first-child), .h3:not(:first-child), .h4:not(:first-child), .h5:not(:first-child), .h6:not(:first-child) {
  margin-top: 37.5px;
}
 h1:not(:last-child),  h2:not(:last-child),  h3:not(:last-child),  h4:not(:last-child),  h5:not(:last-child),  h6:not(:last-child),
.h1:not(:last-child), .h2:not(:last-child), .h3:not(:last-child), .h4:not(:last-child), .h5:not(:last-child), .h6:not(:last-child) {
  margin-bottom: 25px;
}

Add the following CSS between rule set with selector h6 and rule set with selector a:

p:not(:last-child) {
  margin-bottom: 12.5px;
}

Let's discuss the spacing between rows and columns. I did my very best to come up with a spacing system that makes spacing as automatic as possible. Let me try to explain how it works, because it is pretty advanced (if I may say so myself).

We start with adding margins between columns. I decided to give each column a left, right and bottom margin of 25px.

.col {
  flex-basis:    0px;
  flex-grow:     1;
  margin-left:   25px;
  margin-right:  25px;
  margin-bottom: 25px;
  max-width:     calc(100% - 50px);
}

Since there is now space on the left and right inside of the row, I decided to negate those by applying a negative margin on the left and right side of the row.

.row {
  display:        flex;
  flex-wrap:      wrap;
  flex-direction: row;
  align-items:    flex-start;
  margin-left:    -25px;
  margin-right:   -25px;
}

Instead of negating the bottom margin of the columns, I found it to be smarter to embrace it and use it as part of the spacing between rows. To do so, I applied only a top margin to the rows, equal to the bottom margin of the columns.

As a result, I have a row that has 25px of margin at the top, and 25 pixels of space that is created inside the bottom of the row by the bottom margin of the columns.

.row {
  display:        flex;
  flex-wrap:      wrap;
  flex-direction: row;
  align-items:    flex-start;
  margin-top:     25px;
  margin-left:    -25px;
  margin-right:   -25px;
}
Visualization of a rows top marginVisualization of a rows top margin box model
Visualization of spacing of a column within a rowVisualization of spacing of a column within a row box model

The spacing system will get advanced once you realize that there are rows and columns that can have a background color. This means that you need to apply paddings when rows and columns have a background color.

I decided to make variables called --has-fill-top, --has-fill-right, --has-fill-bottom, and --has-fill-left, which indicates whether it has a background color. This variable can have two values: 0 and 1.

[class^='bg-'], [class*=' bg-'] {
  --has-fill-top:    1 !important;
  --has-fill-right:  1 !important;
  --has-fill-bottom: 1 !important;
  --has-fill-left:   1 !important;
}

The selector above will query for elements that have a class attribute that either starts with bg- or when it contains substring  bg- (a space, then bg-) and sets variables --has-fill-top, --has-fill-right, --has-fill-bottom, and --has-fill-left to 1 with !important.

The reason for the use of !important is that each row and column will set the variables to 0 by default, to prevent the variables value from cascading to rows and columns with an ancestor element that has a background color, and thus has value 1 for those variables.

.row {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  display:           flex;
  flex-wrap:         wrap;
  flex-direction:    row;
  align-items:       flex-start;
  margin-top:        25px;
  margin-left:       -25px;
  margin-right:      -25px;
}
.col {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  flex-basis:        0px;
  flex-grow:         1;
  margin-left:       25px;
  margin-right:      25px;
  margin-bottom:     25px;
  max-width:         calc(100% - 50px);
}

Let's add paddings for rows that have a background color.

.row {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  display:           flex;
  flex-wrap:         wrap;
  flex-direction:    row;
  align-items:       flex-start;
  margin-top:        25px;
  margin-left:       -25px;
  margin-right:      -25px;
  padding-top:       calc(25px * 2 * var(--has-fill-top));
  padding-left:      calc(25px * var(--has-fill-left));
  padding-right:     calc(25px * var(--has-fill-right));
  padding-bottom:    calc(25px * var(--has-fill-bottom));
}

The padding values in the rule set above multiply 25px by variables --has-fill-top, --has-fill-right, --has-fill-bottom, and --has-fill-left. These variables act like a boolean value (true or false, 1 or 0).

The top padding is twice the bottom padding, because the columns inside the row do not have a top margin, just a bottom margin of 25px.

Visualization of top and bottom padding for filled rowsVisualization of top and bottom padding for filled rows box model
Visualization of spacing of a column within a filled rowVisualization of spacing of a column within a filled row box model

Rows with a background color need a bottom margin, because rows without a background color are 50 pixels apart; 25px of bottom margin by the columns inside the first row plus a top margin of 25px by the second row.

Adding a background color to a row will cover the area created by its column's bottom margin with its background color, bringing the row closer (visually) to the second row.

Visualization of background color taking up bottom margin of column within bringing it closer to the row below

To fix this, you add a bottom margin of 25px to the row if it has a background color.

.row {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  display:           flex;
  flex-wrap:         wrap;
  flex-direction:    row;
  align-items:       flex-start;
  margin-top:        25px;
  margin-bottom:     calc(25px * var(--has-fill-bottom));
  margin-left:       -25px;
  margin-right:      -25px;
  padding-top:       calc(25px * 2 * var(--has-fill-top));
  padding-left:      calc(25px * var(--has-fill-left));
  padding-right:     calc(25px * var(--has-fill-right));
  padding-bottom:    calc(25px * var(--has-fill-bottom));
}

Now the column's 25 pixels of bottom margin has been reclaimed by adding 25 pixels of bottom margin to the row that has the background color, yet still ending up with 25 pixels of space between the first and second row.

Why is that? Well.. Two vertical margins do not add up to a value to be the sum of the two margins. Instead, they pick the highest value of the two margins, which would be 25px in this case.

Before fixing these vertical spacing issues whilst making a distinction between the spacing between a row with a background color and the row below, and spacing between a row with a background color and anything but a row or column (paragraphs for instance), I would like to fix other spacing issues; since I want to present to you a visualization of spacing between rows with a background color and anything but rows or columns while those horizontal spacing fixes are in place.

So let's do something about horizontal spacing for rows with a background color. Since the left and right space within rows, which are created by the left and right margins by the columns within those rows, are now covered with a background color, I decided to undo the row's negative left and right margin that were applied in order to negate the column's left and right margin.

If I were to negate those margins for rows that have a background color, then the row would stick out on the left and right because of the left and right spacing that is visible due to the row's background color. See the visualizations below, of which the first shows a row with a background color with left and right margin negation, and the second shows a row with a background color without left and right margin negation (which is the desired outcome).

Visualization of a filled row with left and right margin negation
Visualization of a filled row without left and right margin negation

So, in order to disable the negative left and right margin of rows with a background color, I had to change the logics of working with CSS variables --has-fill-left and --has-fill-right by starting with a negative value of -25px and creating value 0 by adding 25px multiplied by variable --has-fill-left or --has-fill-right to the negative value of -25px.

.row {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  display:           flex;
  flex-wrap:         wrap;
  flex-direction:    row;
  align-items:       flex-start;
  margin-top:        25px;
  margin-bottom:     calc(25px * var(--has-fill-bottom));
  margin-left:       calc(-25px + 25px * var(--has-fill-left));
  margin-right:      calc(-25px + 25px * var(--has-fill-right));
  padding-top:       calc(25px * 2 * var(--has-fill-top));
  padding-left:      calc(25px * var(--has-fill-left));
  padding-right:     calc(25px * var(--has-fill-right));
  padding-bottom:    calc(25px * var(--has-fill-bottom));
}

Let's add spacing for columns that have a background color. This is pretty straightforward:

.col {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  flex-basis:        0px;
  flex-grow:         1;
  margin-left:       25px;
  margin-right:      25px;
  margin-bottom:     25px;
  max-width:         calc(100% - 50px);
  padding:           calc(25px * 2 * var(--has-fill-top))
                     calc(25px * 2 * var(--has-fill-right))
                     calc(25px * 2 * var(--has-fill-bottom))
                     calc(25px * 2 * var(--has-fill-left));
}
Visualization of a filled columnVisualization of a filled column box model

When creating advanced layouts, there is no escaping the fact that you will be using rows within rows. This creates a new layer of complexity to the CSS design system, because rows within rows need smaller spacing, and will be placed inside of columns, which can be transparent but they can also have a background color.

The first thing I decided to do was to make use of a spacing multiplier CSS variable that decreases as the row depth increases. I suggest adding the following CSS rule sets below the rule set with selector .row:

.row .row {
  --col-spacing-multiplier: 0.5;
}
.row .row .row {
  --col-spacing-multiplier: 0.25;
}

Then, I used this variable in calculations for row and column spacing and set a default value of 1 for variable --col-spacing-multiplier.

.row {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  display:           flex;
  flex-wrap:         wrap;
  flex-direction:    row;
  align-items:       flex-start;
  margin-top:        calc(25px * var(--col-spacing-multiplier));
  margin-bottom:     calc(25px * var(--has-fill-bottom) * var(--col-spacing-multiplier));
  margin-left:       calc((-25px + 25px * var(--has-fill-left)) * var(--col-spacing-multiplier));
  margin-right:      calc((-25px + 25px * var(--has-fill-right)) * var(--col-spacing-multiplier));
  padding-top:       calc(25px * 2 * var(--has-fill-top) * var(--col-spacing-multiplier));
  padding-left:      calc(25px * var(--has-fill-left) * var(--col-spacing-multiplier));
  padding-right:     calc(25px * var(--has-fill-right) * var(--col-spacing-multiplier));
  padding-bottom:    calc(25px * var(--has-fill-bottom) * var(--col-spacing-multiplier));
  --col-spacing-multiplier: 1;
}
.col {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  flex-basis:        0px;
  flex-grow:         1;
  margin-left:       calc(25px * var(--col-spacing-multiplier));
  margin-right:      calc(25px * var(--col-spacing-multiplier));
  margin-bottom:     calc(25px * var(--col-spacing-multiplier));
  max-width:         calc(100% - 50px * var(--col-spacing-multiplier));
  padding:           calc(25px * 2 * var(--has-fill-top) * var(--col-spacing-multiplier))
                     calc(25px * 2 * var(--has-fill-right) * var(--col-spacing-multiplier))
                     calc(25px * 2 * var(--has-fill-bottom) * var(--col-spacing-multiplier))
                     calc(25px * 2 * var(--has-fill-left) * var(--col-spacing-multiplier));
}

Before showing you a visualization of rows within columns of other rows, there is one more thing to do – removing the top margin of rows that are the first child in its parent element (a column) and removing the bottom margin of rows that are the last child in its parent element.

Let's start with removing the top margin of rows within another row's column, since that is the easiest part. Add the following below the rule set with selector .row .row .row:

.row:first-child {
  margin-top: 0px;
}

Now, when placing a row within a column of another row, the inner row has a bottom margin of 12.5px. For transparent inner rows you have to set the bottom margin to a negative value of -12.5px, because not not only does the inner row otherwise add a bottom margin of 12.5px, its columns inside would also add 12.5px of bottom margin.

Visualization of a column within an inner row which is the last child of a filled columnVisualization of a column within an inner row which is the last child of a filled column box model
Visualization of an inner row as the last child of a filled columnVisualization of an inner row as the last child of a filled column box model
Visualization of a filled column containing an inner row as its last childVisualization of a filled column containing an inner row as its last child box model

What about inner rows with a background color within another row's column? Well, if you apply a negative bottom margin of -12.5px, then that inner row would stick out of the column.

This is because the bottom margin of the inner row's columns are covered with the inner row's background color. This means that there is no need to negate those bottom margins anymore.

Add the following CSS rule set below the rule set with selector .row:first-child:

.row:last-child {
  margin-bottom: calc((-25px + 25px * var(--has-fill-bottom)) * var(--col-spacing-multiplier));
}
Visualization of a column within a filled inner row which is the last child of a filled columnVisualization of a column within a filled inner row which is the last child of a filled column box model
Visualization of a filled inner row as the last child of a filled columnVisualization of a filled inner row as the last child of a filled column box model
Visualization of a filled column containing a filled inner row as its last childVisualization of a filled column containing a filled inner row as its last child box model

Finally, since those spacing fixes are now in place, let's make seperate, distinct, CSS rule sets to add spacing between a row with a background color and a row below, the spacing between a paragraph and a row below (which could contain paragraphs within its columns), and the spacing between a row and a paragraph below. Instead of targeting paragraphs, I target anything but a row or column, since paragraphs were just for example's sake; it could be anything from a paragraph to an unordered list, a button, an image, etc.

Visualization of the spacing between a row with a background color and a row below:

Visualization of a filled row with a row belowVisualization of a filled row with a row below box model
Visualization of a row below a filled rowVisualization of a row below a filled row box model

Visualization of the spacing between a paragraph and a row below:

Visualization of a paragraph above an inner rowVisualization of a paragraph above an inner row box model
Visualization of an inner row below a paragraphVisualization of an inner row below a paragraph box model

Visualization of the spacing between a row and a paragraph below:

Visualization of a column within an inner row with a paragraph belowVisualization of a column within an inner row with a paragraph below box model
Visualization of an inner row with a paragraph belowVisualization of an inner row with a paragraph below box model
Visualization of a paragraph below an inner rowVisualization of a paragraph below an inner row box model

Add the following CSS rule sets below the rule set with selector [class^='bg-'], [class*=' bg-']:

.row[class^='bg-'] + .row,
.row[class*=' bg-'] + .row {
  margin-top: calc(25px * 2 * var(--col-spacing-multiplier));
}
:not(.row):not(.col) + .row[class^='bg-'],
:not(.row):not(.col) + .row[class*=' bg-'] {
  margin-top: max(12.5px, calc(25px * 2 * var(--col-spacing-multiplier)));
}
.row[class^='bg-'] + :not(.row):not(.col),
.row[class*=' bg-'] + :not(.row):not(.col) {
  margin-top: max(12.5px, calc(25px * var(--col-spacing-multiplier)));
}

The first rule set will query for rows that are below a row with a background color. A top margin of two times 25px will be applied to that row, to push it further away from the row that has a background color.

The second rule set will query for a row with a background color that is below anything but a row or column. A top margin of two times 25px will be applied to the row with the background color that is below anything but a row or column.

The reason for it to be two times 25px, is because the value for variable --col-spacing-multiplier gets halved if a row is within a column of another row. This would cause the top margin I would apply to be halved because of the lower value for variable --col-spacing-multiplier as opposed to a paragraph for example.

The third rule set will query for anything but a row or column that is below a row with a background color. A top margin of (once) 25px will be applied to anything but a row or column that is below a row that has a background color.

The last two rule sets are not necessary; it's personal preference. The reason why I added these is because I thought it would look too crowded if there is 12.5px of vertical space between a row with a background color and paragraphs above and below that row.

Visualization of 12.5px of space between a row with a background color and paragraphs:

Visualization of 12.5px of space between a filled row and surrounding paragraphs

Visualization of 25px of space between a row with a background color and paragraphs:

Visualization of 25px of space between a filled row and surrounding paragraphs

CSS function max was used in order to keep at least 12.5px of vertical space, because otherwise the spacing between a paragraph and a row within a row within a row within a row would be 6.25px, which is smaller than the spacing between paragraphs.

Styling for common elements

Let's distance ourselves from the previous chapter, and style the following elements:

  1. Rows and columns
  2. Images
  3. Buttons
  4. Form elements

Border radius for rows and columns

I decided to apply a border radius for rows and columns that have a background color. In order to make this look good, I made use of variable --col-spacing-multiplier to make radius smaller the deeper a row is nested within a column of another row.

:is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) {
  --border-radius-depth: 1;
}
:is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) {
  --border-radius-depth: 2;
}
:is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) {
  --border-radius-depth: 3;
}
:is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) {
  --border-radius-depth: 4;
}
:is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) :is([class^='bg-'], [class*=' bg-'], img) {
  --border-radius-depth: 5;
}
.row[class^='bg-'],
.row[class*=' bg-'],
.col[class^='bg-'],
.col[class*=' bg-']{
  border-radius: calc(25px / var(--border-radius-depth));
}

Images

By default, images' display value is inline. This causes space to appear underneath the image as if it had a line height like text does. To prevent this, we turn images into blocks.

Add the following below rule set with selector a:

img {
  display: block;
  max-width: 100%;
  border-radius: calc(25px / var(--border-radius-depth));
}
img:not(:last-child) {
  margin-bottom: 12.5px;
}

As you can see the image also has a border radius, because an image is basically an element with a background, so it also gets a border radius, just like rows and columns.

It is necessary to use max-width: 100%, because otherwise if the intrinsic size is larger than the desired rendered size, then the rendered size will be larger than what you want. Basically we make sure that the image adapts its width to its parent, instead of letting it affect the width of its parent.

Form elements

Labels

Labels are elements that are placed above (and sometimes beside) form fields that require input from the user. These are often smaller than regular text, because otherwise the form would look too crowded. Add the following below the rule set with selector img:

label {
  font-size: 17px;
}

In HTML you can connect a label and a form field in two ways, to be able to focus the form field when clicking the label. The first method is to place the form field after the label, and add attribute for to the label to refer to the value of the form field's id-attribute.

<label for="form-field-1">Label text</label>
<input type="text" id="form-field-1" name="form-field-1">

The second method is to wrap the form field in a <label>-element, this requires no for-attribute and no id-attribute either (but I prefer using an id-attribute anyways).

<label>
  Label text
  <input type="text" id="form-field-1" name="form-field-1">
</label>

You might wonder why doesn't attribute for just refer to the name-attribute. The reason for this is that radio input fields can share the same value for the name-attribute, whilst having a unique value for the id-attribute. Each radio option has its own label that has to be connected to it.

So, why is this important? Well, there are also two ways to display a label and a form field. The first way being next to each other, and the second way below each other.

Visualization of a label above a form fieldVisualization of a label next to a form element

I decided to make the two ways of connecting a label change how they will be displayed. The first method places the label and form field next to each other, while the second method places the label and form field below each other. The code below also includes some exclusions for specific types of form fields.

label[for] {
  display:      inline-block;
  margin-right: 18.75px;
}
label:not([for]) {
  display: block;
}
label:not([for]) > input:not([type="checkbox"]):not([type="radio"]),
label:not([for]) > select,
label:not([for]) > textarea {
  display:    block;
  margin-top: 6.25px;
}
label:not([for]) > input:not([type="checkbox"]):not([type="radio"]):not([type="image"]),
label:not([for]) > select,
label:not([for]) > textarea {
  width: 100%;
}

Simple input fields

Simple input fields are fields that are the same except for their formatting. Simple input field types are text, email, tel, date, datetime-local, month, week, password, search, <time>, url, number, and color. Some properties of these input fields overlap with select boxes and text areas.

Let's start by setting the accent color for input fields, select boxes, and text areas.

input, select, textarea {
  accent-color: var(--accent-color);
}

In the CSS shown above I refer to a CSS variable that you should define in rule set with selector .color-scheme-dark and in rule set with selector .color-scheme-light.

.color-scheme-dark {
  --accent-color:    var(--light-blue);
  --hyperlink-color: var(--dark-blue);
  color:             var(--black);
}
.color-scheme-light {
  --accent-color:    var(--lighter-blue);
  --hyperlink-color: var(--lighter-blue);
  color:             var(--white-a90);
}

The reason for the accent color to be defined as a variable --accent-color and to be used by property accent-color, is that this color will be used by checkboxes and radios which's appearance-property will be set to none. As the appearance-property is set to none, property accent-color no longer affects the color, and so I have to apply the colors myself - using the CSS variable --accent-color.

When you focus an input field (when you click it to be able to type text, for instance), it has an outline by default. Let's get rid of that outline.

input:focus, select:focus, textarea:focus {
  outline: none;
}

Input fields also have a placeholder, which is a pseudo element that has a color and opacity that differs per browser, so let's set our own values for those properties:

input::placeholder {
  color:   inherit;
  opacity: 0.6;
}

Using value inherit for property color for placeholders basically means using the color of the text field, instead of setting that color yourself. I have tried setting the color using variable --field-color, but variables do not cascade towards pseudo elements, so that wasn't going to work.

Now, let's finally add the main styling for simple input fields, text areas, and select boxes.

input[type="text"],
input[type="email"],
input[type="tel"],
input[type="date"],
input[type="datetime-local"],
input[type="month"],
input[type="week"],
input[type="password"],
input[type="search"],
input[type="time"],
input[type="url"],
input[type="number"],
input[type="color"],
textarea,
select {
  -webkit-appearance: none;
  -moz-appearance:    none;
  appearance:         none;
  color:              var(--field-color);
  background-color:   var(--field-background-color);
  border:             1.5px solid var(--field-border-color);
  padding:            12.5px;
  font-family:        inherit;
  font-weight:        inherit;
  font-size:          15px;
  line-height:        1.5;
  vertical-align:     top;
  border-radius:      6.25px;
}

As discussed earlier, we set the appearance-property to value none, along with the prefixed versions, of which I'm not sure whether they are necessary or not.

As you can see I defined variables for the text color, background color and border color for fields. Please define those in the rule set with selector .color-scheme-dark and the rule set with selector .color-scheme-light.

.color-scheme-dark {
  --accent-color:           var(--light-blue);
  --hyperlink-color:        var(--dark-blue);
  --field-color:            var(--black);
  --field-background-color: transparent;
  --field-border-color:     var(--gray);
  color:                    var(--black);
}
.color-scheme-light {
  --accent-color:           var(--lighter-blue);
  --hyperlink-color:        var(--lighter-blue);
  --field-color:            var(--white);
  --field-background-color: transparent;
  --field-border-color:     var(--light-gray);
  color:                    var(--white-a90);
}

Checkboxes and radio buttons

If you have worked with checkboxes and radio options before, I can feel your pain when it comes to vertically aligning those elements. Especially when working on WordPress websites or WooCommerce webshops that have checkbox styles applied by Bootstrap, a WordPress theme, and WooCommerce all at once; you fix one checkbox, but the other checkboxes are ruined.

But let's not think about that. Let's make checkboxes and radio buttons ourselves.

input[type="checkbox"],
input[type="radio"] {
  -webkit-appearance: none;
  -moz-appearance:    none;
  appearance:         none;
  width:              17px;
  height:             17px;
  background-color:   var(--field-background-color);
  border:             1.5px solid var(--field-border-color);
  margin:             0px 3px -2px 0px;
  position:           relative;
}
input[type="checkbox"] + label[for],
input[type="radio"] + label[for] {
  display: inline;
}
input[type="checkbox"] {
  border-radius: 3.125px;
}

I applied position: relative, because radio buttons have a circle in the center when they are chosen that has to be positioned with position: absolute. Checkboxes get a background image instead, because I can store the background image as a CSS variable that has a different image depending on the color scheme (dark or light).

input[type="checkbox"]:checked {
  background-color:    var(--accent-color);
  border-color:        var(--accent-color);
  background-image:    var(--checkbox-check-url);
  background-size:     7px;
  background-repeat:   no-repeat;
  background-position: 3.5px 2.5px;
}

As discussed earlier, since the checkbox no longer has its default browser appearance, I have to color it myself using the CSS variable --accent-color. Please define CSS variable --checkbox-check-url in the rule set with selector .color-scheme-dark and the rule set with selector .color-scheme-light as shown below.

.color-scheme-dark {
  --accent-color:           var(--light-blue);
  --hyperlink-color:        var(--dark-blue);
  --field-color:            var(--black);
  --field-background-color: transparent;
  --field-border-color:     var(--gray);
  --checkbox-check-url:     url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  color:                    var(--black);
}
.color-scheme-light {
  --accent-color:           var(--lighter-blue);
  --hyperlink-color:        var(--lighter-blue);
  --field-color:            var(--white);
  --field-background-color: transparent;
  --field-border-color:     var(--light-gray);
  --checkbox-check-url:     url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  color:                    var(--white-a90);
}

The reason why the URL looks not so appealing, is because it is the URL encoded content of an SVG image. Why? Well, instead of creating another HTTP request to the server after downloading the CSS file, I decided to include the SVG image in the CSS to save time.

If you want to URL encode a different SVG image for the check inside a checkbox, I suggest using the URL-encoder for SVG.

The checkbox is done, so let's finish the styling of the radio button.

input[type="radio"] {
  border-radius: 50%;
}
input[type="radio"]:checked::after {
  content:          '';
  width:            9px;
  height:           9px;
  border-radius:    50%;
  background-color: var(--accent-color);
  position:         absolute;
  top:              2.5px;
  left:             2.5px;
}

The checkboxes and radio buttons are now complete.

Visualization of checkboxes and radio buttons

Range input fields

As the main styling for simple input fields does not apply to range input fields, we still need to add a background color, and undo a border around the slider thumb of the range input field by applying -webkit-appearance: none, but that's about it.

input[type="range"] {
  background: var(--field-background-color);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
}

Color input fields

The main styling for simple input fields apply to color input fields, but since browsers set some default styling for color input fields in particular, I decided to do something about it.

input[type="color"] {
  height: auto;
}
input[type="color"]::-webkit-color-swatch-wrapper {
  height: 1.5em;
}
input[type="color"]::-webkit-color-swatch {
  border: none;
}

Textareas

Text areas are just like regular text input fields, but they are often taller and can contain multiple lines of text instead of one line. Text areas can also be resized by the user, but I decided to limit resizing of text areas to just the height. I also set a minimum height.

textarea {
  resize:     vertical;
  min-height: 127px;
}

Select boxes

Now, select boxes are special. They are so special, that, in order to make them look good, you have to get rid of all default browser styles and add a background image to function as a downwards caret. This is nothing new, as we did the same thing to checkboxes.

select {
  padding-right:       calc(12.5px + 1em * 85 / 141 + 25px);
  background-image:    var(--selectbox-caret-url);
  background-size:     auto 0.5em;
  background-repeat:   no-repeat;
  background-position: calc(100% - 12.5px) center;
}

Yes, I added more URL encoded SVG images into CSS variables and you can't stop me.

In fact, I suggest you define the variable --selectbox-caret-url in the rule set with selector .color-scheme-dark and in the rule set with selector .color-scheme-light.

.color-scheme-dark {
  --accent-color:           var(--light-blue);
  --hyperlink-color:        var(--dark-blue);
  --field-color:            var(--black);
  --field-background-color: transparent;
  --field-border-color:     var(--gray);
  --checkbox-check-url:     url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23000000'/%3E%3C/svg%3E");
  color:                    var(--black);
}
.color-scheme-light {
  --accent-color:           var(--lighter-blue);
  --hyperlink-color:        var(--lighter-blue);
  --field-color:            var(--white);
  --field-background-color: transparent;
  --field-border-color:     var(--light-gray);
  --checkbox-check-url:     url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23ffffff'/%3E%3C/svg%3E");
  color:                    var(--white-a90);
}

Unlike select boxes, you don't have any other option than just styling the options within select boxes by using variables --txt-color and --bg-color.

option {
  color:            var(--txt-color);
  background-color: var(--bg-color);
}

I tried setting the background color to transparent, but that doesn't work - it would just be white. So instead I used the text color of the first ancestor element that declared a color scheme, and the background color of the first ancestor element that declared a background color. It required me to rewrite some CSS, but it was all worth the effort.

The value for variable --txt-color gets set or updated whenever you set a new color scheme using classes color-scheme-dark and color-scheme-light. The value for variable --bg-color gets set or updated whenever you add a background color to any element using a class like bg-dark-blue.

.bg-black {
  --bg-color: var(--black);
}
.bg-darker-blue {
  --bg-color: var(--darker-blue);
}
.bg-dark-blue {
  --bg-color: var(--dark-blue);
}
.bg-light-blue {
  --bg-color: var(--light-blue);
}
.bg-lighter-blue {
  --bg-color: var(--lighter-blue);
}
.bg-gray {
  --bg-color: var(--gray);
}
.bg-light-gray {
  --bg-color: var(--light-gray);
}
.bg-lighter-gray {
  --bg-color: var(--lighter-gray);
}
.bg-off-white {
  --bg-color: var(--off-white);
}
.bg-white {
  --bg-color: var(--white);
}

Now, use variable --bg-color in the rule set with selector [class^='bg-'], [class*=' bg-']:

[class^='bg-'], [class*=' bg-'] {
  --has-fill-top:    1 !important;
  --has-fill-right:  1 !important;
  --has-fill-bottom: 1 !important;
  --has-fill-left:   1 !important;
  background-color:  var(--bg-color) !important;
}

Change declaration color: var(--black) to --txt-color: var(--black) in the rule set with selector .color-scheme-dark and change declaration color: var(--white-a90) to --txt-color: var(--white-a90) in the rule set with selector .color-scheme-light.

.color-scheme-dark {
  --accent-color:           var(--light-blue);
  --hyperlink-color:        var(--dark-blue);
  --field-color:            var(--black);
  --field-background-color: transparent;
  --field-border-color:     var(--gray);
  --checkbox-check-url:     url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23000000'/%3E%3C/svg%3E");
  --txt-color:              var(--black);
}
.color-scheme-light {
  --accent-color:           var(--lighter-blue);
  --hyperlink-color:        var(--lighter-blue);
  --field-color:            var(--white);
  --field-background-color: transparent;
  --field-border-color:     var(--light-gray);
  --checkbox-check-url:     url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23ffffff'/%3E%3C/svg%3E");
  --txt-color:              var(--white-a90);
}

Finally, use CSS variable --txt-color in a new CSS rule set which I suggest you place below the rule set with the selector .color-scheme-light.

[class^='txt-'], [class*=' txt-'], .color-scheme-dark, .color-scheme-light {
  color: var(--txt-color) !important;
}

Buttons

Finally, something that is a common element which only now gets some styling: the button.

button, input[type="submit"], .button {
  display:            inline-block;
  padding:            12.5px;
  -webkit-appearance: none;
  -moz-appearance:    none;
  appearance:         none;
  color:              var(--button-color);
  background-color:   var(--button-background-color);
  border:             1.5px solid var(--button-border-color);
  border-radius:      6.25px;
  font-size:          15px;
  cursor:             pointer;
  outline:            0px;
}
button:hover, input[type="submit"]:hover, .button:hover {
  color:            var(--button-background-color);
  background-color: transparent;
  border-color:     var(--button-background-color);
}

Finally, below is an overview of all form fields that we've just styled:

Visualization of form elements

Creating a simple layout

I tried to keep you hooked by adding visualizations throughout this article, but I understand that at some point you have to test and make it your own CSS design system.

Just add some stuff, I don't know. Choose a better font than Arial (please), and choose the colors that you like. There are still a lot of classes to add to make your layout more advanced and create some advanced styling, like changing the width of columns, adding a box shadow, changing colors on element-level (if necessary), changing the text alignment, the horizontal and vertical alignment of columns within rows, … Heck, you need to make it work on smaller devices too!

Utility classes for simple styling

Utility classes are classes that have a single purpose. A class called bg-dark-blue, for instance, just sets the background color to dark blue. It's as simple as that. But do not use utility classes too much, because the main styling should be done by the design system.

Utility classes should be used to define a behavior or styling that is unlike the default; if you want 90% of your hyperlinks to be red instead of blue, you should change it in the CSS design system instead of manually adding a utility class to 90 percent of your hyperlinks.

Text color, background color, border color, text color on hover, etc

You already added the classes for background colors, so let's add the utility classes for text color, text color on hover, background color on hover, border color, and border color on hover. I suggest you add the following utility classes.

.txt-black {
  --txt-color: var(--black);
}
.txt-darker-blue {
  --txt-color: var(--darker-blue);
}
.txt-dark-blue {
  --txt-color: var(--dark-blue);
}
.txt-light-blue {
  --txt-color: var(--light-blue);
}
.txt-lighter-blue {
  --txt-color: var(--lighter-blue);
}
.txt-gray {
  --txt-color: var(--gray);
}
.txt-light-gray {
  --txt-color: var(--light-gray);
}
.txt-lighter-gray {
  --txt-color: var(--lighter-gray);
}
.txt-off-white {
  --txt-color: var(--off-white);
}
.txt-white {
  --txt-color: var(--white);
}
.bg-black {
  --bg-color: var(--black);
}
.bg-darker-blue {
  --bg-color: var(--darker-blue);
}
.bg-dark-blue {
  --bg-color: var(--dark-blue);
}
.bg-light-blue {
  --bg-color: var(--light-blue);
}
.bg-lighter-blue {
  --bg-color: var(--lighter-blue);
}
.bg-gray {
  --bg-color: var(--gray);
}
.bg-light-gray {
  --bg-color: var(--light-gray);
}
.bg-lighter-gray {
  --bg-color: var(--lighter-gray);
}
.bg-off-white {
  --bg-color: var(--white);
}
.bg-white {
  --bg-color: var(--white);
}
.bor-black {
  --bor-color: var(--black);
}
.bor-darker-blue {
  --bor-color: var(--darker-blue);
}
.bor-dark-blue {
  --bor-color: var(--dark-blue);
}
.bor-light-blue {
  --bor-color: var(--light-blue);
}
.bor-lighter-blue {
  --bor-color: var(--lighter-blue);
}
.bor-gray {
  --bor-color: var(--gray);
}
.bor-light-gray {
  --bor-color: var(--light-gray);
}
.bor-lighter-gray {
  --bor-color: var(--lighter-gray);
}
.bor-off-white {
  --bor-color: var(--off-white);
}
.bor-white {
  --bor-color: var(--white);
}

Now, add the following in order to use the variable --bor-color:

[class^='bor-'],
[class*=' bor-'] {
  border-color: var(--bor-color) !important;
}

Color utility classes are handy for making specific buttons stand out, but what happens when you hover on a button is also important to be able to change. Copy and paste all rule sets of which the selector starts with either .txt-, .bg-, or .bor-, and append -hover:hover to the selector.

.txt-black-hover:hover {
  --txt-color: var(--black);
}
.txt-darker-blue-hover:hover {
  --txt-color: var(--darker-blue);
}
.txt-dark-blue-hover:hover {
  --txt-color: var(--dark-blue);
}
.txt-light-blue-hover:hover {
  --txt-color: var(--light-blue);
}
.txt-lighter-blue-hover:hover {
  --txt-color: var(--lighter-blue);
}
.txt-gray-hover:hover {
  --txt-color: var(--gray);
}
.txt-light-gray-hover:hover {
  --txt-color: var(--light-gray);
}
.txt-lighter-gray-hover:hover {
  --txt-color: var(--lighter-gray);
}
.txt-off-white-hover:hover {
  --txt-color: var(--off-white);
}
.txt-white-hover:hover {
  --txt-color: var(--white);
}
.bg-black-hover:hover {
  --bg-color: var(--black);
}
.bg-darker-blue-hover:hover {
  --bg-color: var(--darker-blue);
}
.bg-dark-blue-hover:hover {
  --bg-color: var(--dark-blue);
}
.bg-light-blue-hover:hover {
  --bg-color: var(--light-blue);
}
.bg-lighter-blue-hover:hover {
  --bg-color: var(--lighter-blue);
}
.bg-gray-hover:hover {
  --bg-color: var(--gray);
}
.bg-light-gray-hover:hover {
  --bg-color: var(--light-gray);
}
.bg-lighter-gray-hover:hover {
  --bg-color: var(--lighter-gray);
}
.bg-off-white-hover:hover {
  --bg-color: var(--off-white);
}
.bg-white-hover:hover {
  --bg-color: var(--white);
}
.bor-black-hover:hover {
  --bor-color: var(--black);
}
.bor-darker-blue-hover:hover {
  --bor-color: var(--darker-blue);
}
.bor-dark-blue-hover:hover {
  --bor-color: var(--dark-blue);
}
.bor-light-blue-hover:hover {
  --bor-color: var(--light-blue);
}
.bor-lighter-blue-hover:hover {
  --bor-color: var(--lighter-blue);
}
.bor-gray-hover:hover {
  --bor-color: var(--gray);
}
.bor-light-gray-hover:hover {
  --bor-color: var(--light-gray);
}
.bor-lighter-gray-hover:hover {
  --bor-color: var(--lighter-gray);
}
.bor-off-white-hover:hover {
  --bor-color: var(--off-white);
}
.bor-white-hover:hover {
  --bor-color: var(--white);
}

You could also extend this CSS to be able to add colors for the top, right, bottom, and left border. You could also not want to add different colors for each side.

I will not completely expand every single possibility, because otherwise the CSS file will get large very quickly when we are going to make all utility classes device-specific as well, meaning you are going to copy, paste and edit all utility classes to multiple media queries.

The design system of the website you are looking at right now, removes unused CSS classes automatically, so I don't have to worry about how many classes I add to my CSS file.

Text decoration

In some situations you want to remove the underline from a hyperlink, because the hyperlink is to be styled as a block or inline block with padding, background color, etc. Add the following below the rule set with selector .row[class^='bg-'] + :not(.row):not(.col), .row[class*=' bg-'] + :not(.row):not(.col):

.no-txt-decor {
  text-decoration: none;
}

Text transform: uppercase, lowercase, capitalize

I find it useful to be able to write in all caps without having to use caps lock, so I prefer using CSS to transform the text into uppercase. Another advantage of this, is that when a piece of uppercase text is shown in search results of Google, that it appears normally instead of full caps. Add the following below the rule with selector .no-txt-decor:

.uppercase {
  text-transform: uppercase;
}
.lowercase {
  text-transform: lowercase;
}
.capitalize {
  text-transform: capitalize;
}

Text align

Add the following below the rule set with selector .capitalize:

.txt-left {
  text-align: left;
}
.txt-center {
  text-align: center;
}
.txt-right {
  text-align: right;
}

Borders

While adding border classes for the CSS design system, I realized that borders do also need some padding between the content of the box and the border of the box.

Originally I used only one variable called --has-bg instead of the four variables --has-fill-top, --has-fill-right, --has-fill-bottom, and has-fill-left. Now, because elements that have a border also need a padding, I realized that this would also mean that it would need some padding, just like rows and columns with a background color.

As I said, I used a single variable called --has-bg for elements with a background color, but I had to separate it into four variables --has-fill-top, --has-fill-right, --has-fill-bottom, and --has-fill-left. Which might not sound hard, but the rows require a new approach.

Columns are easy, just add a padding and a border - no further changes needed after doing so. Rows without a background color, on the other hand, use a negative left and right margin to negate the left and right margins of the columns within. What happens next is that if you add a border on the left, the border will be wider than the content of other rows.

I came up with a solution that sounds ridiculous, but it works - I added pseudo-element ::before to rows which uses variables --has-fill-top, --has-fill-right, --has-fill-bottom, and --has-fill-bottom to position and size itself within the row. This was harder than I thought.

Before showing you how that works, let's add the following CSS for the border classes below the rule set with selector .txt-right:

.border:not(.row), .border-top:not(.row) {
  --has-fill-top:   1 !important;
  border-top-width: 1.5px;
  border-top-style: solid;
}
.border:not(.row), .border-right:not(.row) {
  --has-fill-right:   1 !important;
  border-right-width: 1.5px;
  border-right-style: solid;
}
.border:not(.row), .border-bottom:not(.row) {
  --has-fill-bottom:   1 !important;
  border-bottom-width: 1.5px;
  border-bottom-style: solid;
}
.border:not(.row), .border-left:not(.row) {
  --has-fill-left:   1 !important;
  border-left-width: 1.5px;
  border-left-style: solid;
}
.row.border,
.row.border-top {
  --has-fill-top:    1 !important;
  padding-top:       calc(50px * var(--col-spacing-multiplier));
}
.row.border,
.row.border-right {
  --has-fill-right:  1 !important;
  padding-right:     calc(25px * var(--col-spacing-multiplier));
}
.row.border,
.row.border-bottom {
  --has-fill-bottom: 1 !important;
  padding-bottom:    calc(25px * var(--col-spacing-multiplier));
}
.row.border,
.row.border-left {
  --has-fill-left: 1 !important;
  padding-left:    calc(25px * var(--col-spacing-multiplier));
}
.row.border::before,
.row.border-top::before {
  --has-fill-top:     1;
  border-top-width:   1.5px;
  border-top-style:   solid;
}
.row.border::before,
.row.border-right::before {
  --has-fill-right:     1;
  border-right-width:   1.5px;
  border-right-style:   solid;
}
.row.border::before,
.row.border-bottom::before {
  --has-fill-bottom:     1;
  border-bottom-width:   1.5px;
  border-bottom-style:   solid;
}
.row.border::before,
.row.border-left::before {
  --has-fill-left:     1;
  border-left-width:   1.5px;
  border-left-style:   solid;
}
.border-solid:not(.row), .border-top-solid:not(.row) {
  border-top-style: solid;
}
.border-solid:not(.row), .border-right-solid:not(.row) {
  border-right-style: solid;
}
.border-solid:not(.row), .border-bottom-solid:not(.row) {
  border-bottom-style: solid;
}
.border-solid:not(.row), .border-left-solid:not(.row) {
  border-left-style: solid;
}
.row.border-solid::before, .row.border-top-solid::before {
  border-top-style: solid;
}
.row.border-solid::before, .row.border-right-solid::before {
  border-right-style: solid;
}
.row.border-solid::before, .row.border-bottom-solid::before {
  border-bottom-style: solid;
}
.row.border-solid::before, .row.border-left-solid::before {
  border-left-style: solid;
}
.border-dashed:not(.row), .border-top-dashed:not(.row) {
  border-top-style: dashed;
}
.border-dashed:not(.row), .border-right-dashed:not(.row) {
  border-right-style: dashed;
}
.border-dashed:not(.row), .border-bottom-dashed:not(.row) {
  border-bottom-style: dashed;
}
.border-dashed:not(.row), .border-left-dashed:not(.row) {
  border-left-style: dashed;
}
.row.border-dashed::before, .row.border-top-dashed::before {
  border-top-style: dashed;
}
.row.border-dashed::before, .row.border-right-dashed::before {
  border-right-style: dashed;
}
.row.border-dashed::before, .row.border-bottom-dashed::before {
  border-bottom-style: dashed;
}
.row.border-dashed::before, .row.border-left-dashed::before {
  border-left-style: dashed;
}
.border-dotted:not(.row), .border-top-dotted:not(.row) {
  border-top-style: dotted;
}
.border-dotted:not(.row), .border-right-dotted:not(.row) {
  border-right-style: dotted;
}
.border-dotted:not(.row), .border-bottom-dotted:not(.row) {
  border-bottom-style: dotted;
}
.border-dotted:not(.row), .border-left-dotted:not(.row) {
  border-left-style: dotted;
}
.row.border-dotted::before, .row.border-top-dotted::before {
  border-top-style: dotted;
}
.row.border-dotted::before, .row.border-right-dotted::before {
  border-right-style: dotted;
}
.row.border-dotted::before, .row.border-bottom-dotted::before {
  border-bottom-style: dotted;
}
.row.border-dotted::before, .row.border-left-dotted::before {
  border-left-style: dotted;
}

The CSS code above sets variables --has-fill-top, --has-fill-right, --has-fill-bottom, and --has-fill-left for each corresponding side of the border a class refers to, because the variable's value does not cascade towards pseudo-elements.

Earlier in this article I talked about spacing between nested rows that had a background color, but were surrounded by paragraphs or any other element that's not a row or column. Since rows with borders now have padding too, just like rows with a background color, these should also be taken into account for those spacing rules in your CSS design system.

So let's update the selector of the following rule sets:

.row[class^='bg-'] + .row,
.row[class*=' bg-'] + .row,
.row.border + .row,
.row.border-bottom + .row {
  margin-top: calc(25px * 2 * var(--col-spacing-multiplier));
}
:not(.row):not(.col) + .row[class^='bg-'],
:not(.row):not(.col) + .row[class*=' bg-'],
:not(.row):not(.col) + .row.border,
:not(.row):not(.col) + .row.border-top {
  margin-top: max(12.5px, calc(25px * 2 * var(--col-spacing-multiplier)));
}
.row[class^='bg-'] + :not(.row):not(.col),
.row[class*=' bg-'] + :not(.row):not(.col),
.row.border + :not(.row):not(.col),
.row.border-bottom + :not(.row):not(.col) {
  margin-top: max(12.5px, calc(25px * var(--col-spacing-multiplier)));
}

Borders should also be taken into account for automatic border radiuses, update the following 5 rule sets:

:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) {
  --border-radius-depth: 1;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) {
  --border-radius-depth: 2;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) {
  --border-radius-depth: 3;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) {
  --border-radius-depth: 4;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right) {
  --border-radius-depth: 5;
}

After that, add 4 new rule sets below them:

.border, .border-top.border-left {
  border-top-left-radius: calc(25px / var(--border-radius-depth));
}
.border, .border-top.border-right {
  border-top-right-radius: calc(25px / var(--border-radius-depth));
}
.border, .border-bottom.border-left {
  border-bottom-left-radius: calc(25px / var(--border-radius-depth));
}
.border, .border-bottom.border-right {
  border-bottom-right-radius: calc(25px / var(--border-radius-depth));
}

Now, let's start with the pseudo-element within rows. First, let's apply position: relative to the rule set with selector .row and the rule set with selector .col.

.row {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  display:           flex;
  flex-wrap:         wrap;
  flex-direction:    row;
  align-items:       flex-start;
  position:          relative;
  margin-top:        calc(25px * var(--col-spacing-multiplier));
  margin-bottom:     calc(25px * var(--has-fill-bottom) * var(--col-spacing-multiplier));
  margin-left:       calc((-25px + 25px * var(--has-fill-left)) * var(--col-spacing-multiplier));
  margin-right:      calc((-25px + 25px * var(--has-fill-right)) * var(--col-spacing-multiplier));
  padding-top:       calc(25px * 2 * var(--has-fill-top) * var(--col-spacing-multiplier));
  padding-left:      calc(25px * var(--has-fill-left) * var(--col-spacing-multiplier));
  padding-right:     calc(25px * var(--has-fill-right) * var(--col-spacing-multiplier));
  padding-bottom:    calc(25px * var(--has-fill-bottom) * var(--col-spacing-multiplier));
  --col-spacing-multiplier: 1;
}
.col {
  --has-fill-top:    0;
  --has-fill-right:  0;
  --has-fill-bottom: 0;
  --has-fill-left:   0;
  flex-basis:        0px;
  flex-grow:         1;
  position:          relative;
  margin-left:       calc(25px * var(--col-spacing-multiplier));
  margin-right:      calc(25px * var(--col-spacing-multiplier));
  margin-bottom:     calc(25px * var(--col-spacing-multiplier));
  max-width:         calc(100% - 50px * var(--col-spacing-multiplier));
  padding:           calc(25px * 2 * var(--has-fill-top) * var(--col-spacing-multiplier))
                     calc(25px * 2 * var(--has-fill-right) * var(--col-spacing-multiplier))
                     calc(25px * 2 * var(--has-fill-bottom) * var(--col-spacing-multiplier))
                     calc(25px * 2 * var(--has-fill-left) * var(--col-spacing-multiplier));
}

The value of variable --col-spacing-multiplier does not cascade to psuedo-elements, so let's do this manually. Add the following CSS in below the rule set with selector .row .row:

.row .row::before {
  --col-spacing-multiplier: 0.5;
}

After that, add the following CSS below the rule set with selector .row .row .row:

.row .row .row::before {
  --col-spacing-multiplier: 0.25;
}

Finally, add the following CSS below the rule set with selector .row:

.row::before {
  --col-spacing-multiplier: 1;
  --has-fill-top:           0;
  --has-fill-right:         0;
  --has-fill-bottom:        0;
  --has-fill-left:          0;
  border-top-width:         0px;
  border-right-width:       0px;
  border-bottom-width:      0px;
  border-left-width:        0px;
  border-color:             inherit;
  content:                  '';
  display:                  block;
  pointer-events:           none;
  position:                 absolute;
  top:                      0px;
  left:                     calc(
                              max(
                                25px
                                *
                                (
                                  max(
                                    var(--has-fill-top),
                                    var(--has-fill-bottom)
                                  )
                                  -
                                  var(--has-fill-left)
                                ),
                                0px
                              )
                              *
                              var(--col-spacing-multiplier)
                            );
  width:                    calc(
                              100%
                              -
                              (
                                (
                                  (
                                    max(
                                      25px
                                      *
                                      (
                                        max(
                                          var(--has-fill-top),
                                          var(--has-fill-bottom)
                                        )
                                        -
                                        var(--has-fill-left)
                                      ),
                                      0px
                                    )
                                  )
                                  *
                                  var(--col-spacing-multiplier)
                                )
                                +
                                (
                                  (
                                    max(
                                      25px
                                      *
                                      (
                                        max(
                                          var(--has-fill-top),
                                          var(--has-fill-bottom)
                                        )
                                        -
                                        var(--has-fill-right)
                                      ),
                                      0px
                                    )
                                  )
                                  *
                                  var(--col-spacing-multiplier)
                                )
                              )
                            );
  
  height:                   calc(
                              100%
                              +
                              (
                                -25px
                                +
                                25px
                                *
                                var(--has-fill-bottom)
                              )
                              *
                              var(--col-spacing-multiplier)
                            );
  box-sizing:               border-box;
}

Now, I know what you're thinking. What are these calculations for, and why are they so large? Well, the thing is, there are many possible variations of what sides of a row have borders. The pseudo-element within that row has to position and resize itself accordingly.

If a row has a border on the top or bottom, but not on the left and not on the right, then the width of the pseudo-element should be 50px smaller than the row itself and should move 25px to the right.

If a row has a border on the top or bottom and on the left side, but not on the right side, then the width of the pseudo-element should be 25px smaller than the row itself, and it should not move to the right at all.

If a row has a border on the top or bottom and on the right side, but not on the left side, then the width of the pseudo-element should also be 25px smaller than the row itself, and it should move 25px to the right, instead of not moving at all, unlike a row that has only a top or bottom and a left border.

If a row has a border on the left, but not anywhere else, then the pseudo-element should not move to the bottom, but the height should be 25px smaller than the height of the row, because otherwise the border would stick out of the bottom.

These were just examples for 4 out of all 16 border combinations. I won't discuss them all, because that is a waste of time. I think you understand the complexity.

When I tried to imagine how many possible border side combinations there were, I thought there would be a ton of combinations. Which was, as I discovered whilst writing this, not true.

After calculating this by pretending the zero's and one's in binary numbers are no and yes for each border side, I ended up with 16 possible border combinations:

  • 0000 = no borders
  • 0001 = left
  • 0010 = bottom
  • 0011 = bottom, left
  • 0100 = right
  • 0101 = right, left
  • 0110 = right, bottom
  • 0111 = right, bottom, left
  • 1000 = top
  • 1001 = top, left
  • 1010 = top, bottom
  • 1011 = top, bottom, left
  • 1100 = top, right
  • 1101 = top, right, left
  • 1110 = top, right, bottom
  • 1111 = top, right, bottom, left

I did think about doing 4 to the power of 2 to end up with 16, but for some reason, in my head, that would not make sense. It was pretty late in the evening, in my defense.

Anyways, using binary numbers and ridiculous calculations just makes me think I look very smart, so I will just stick with that. If you want to write 16 rule sets instead, then go ahead!

Border radius

As shown in chapter 8.1 Border radius for rows and columns, the deeper a row or column with a background color is nested within other rows or columns with background colors, the smaller the border radius becomes. The naming for the border radius classes that you are going to add goes from border-radius-0 all the way to border-radius-5 where border-radius-1 is 25px and border-radius-5 is 5px (25px divided by 5).

Add the following below the rule set with selector .row.border-dotted::before, .row.border-left-dotted::before:

.border-radius-none, .border-radius-top-right-none {
  border-top-right-radius: 0px !important;
}
.border-radius-none, .border-radius-bottom-right-none {
  border-bottom-right-radius: 0px !important;
}
.border-radius-none, .border-radius-bottom-left-none {
  border-bottom-left-radius: 0px !important;
}
.border-radius-none, .border-radius-top-left-none {
  border-top-left-radius: 0px !important;
}
.border-rad-1, .border-rad-top-right-1 {
  border-top-right-radius: 25px !important;
}
.border-rad-1, .border-rad-bottom-right-1 {
  border-bottom-right-radius: 25px !important;
}
.border-rad-1, .border-rad-bottom-left-1 {
  border-bottom-left-radius: 25px !important;
}
.border-rad-1, .border-rad-top-left-1 {
  border-top-left-radius: 25px !important;
}
.border-rad-2, .border-rad-top-right-2 {
  border-top-right-radius: calc(25px / 2) !important;
}
.border-rad-2, .border-rad-bottom-right-2 {
  border-bottom-right-radius: calc(25px / 2) !important;
}
.border-rad-2, .border-rad-bottom-left-2 {
  border-bottom-left-radius: calc(25px / 2) !important;
}
.border-rad-2, .border-rad-top-left-2 {
  border-top-left-radius: calc(25px / 2) !important;
}
.border-rad-3, .border-rad-top-right-3 {
  border-top-right-radius: calc(25px / 3) !important;
}
.border-rad-3, .border-rad-bottom-right-3 {
  border-bottom-right-radius: calc(25px / 3) !important;
}
.border-rad-3, .border-rad-bottom-left-3 {
  border-bottom-left-radius: calc(25px / 3) !important;
}
.border-rad-3, .border-rad-top-left-3 {
  border-top-left-radius: calc(25px / 3) !important;
}
.border-rad-4, .border-rad-top-right-4 {
  border-top-right-radius: calc(25px / 4) !important;
}
.border-rad-4, .border-rad-bottom-right-4 {
  border-bottom-right-radius: calc(25px / 4) !important;
}
.border-rad-4, .border-rad-bottom-left-4 {
  border-bottom-left-radius: calc(25px / 4) !important;
}
.border-rad-4, .border-rad-top-left-4 {
  border-top-left-radius: calc(25px / 4) !important;
}
.border-rad-5, .border-rad-top-right-5 {
  border-top-right-radius: calc(25px / 5) !important;
}
.border-rad-5, .border-rad-bottom-right-5 {
  border-bottom-right-radius: calc(25px / 5) !important;
}
.border-rad-5, .border-rad-bottom-left-5 {
  border-bottom-left-radius: calc(25px / 5) !important;
}
.border-rad-5, .border-rad-top-left-5 {
  border-top-left-radius: calc(25px / 5) !important;
}

Margins and paddings

Add the following below the rule set with selector .border-rad-5, .border-rad-top-left-5:

.mar-auto, .mar-top-auto {
  margin-top: auto !important;
}
.mar-auto, .mar-right-auto {
  margin-right: auto !important;
}
.mar-auto, .mar-bottom-auto {
  margin-bottom: auto !important;
}
.mar-auto, .mar-left-auto {
  margin-left: auto !important;
}
.mar-0, .mar-top-0 {
  margin-top: 0px !important;
}
.mar-0, .mar-right-0 {
  margin-right: 0px !important;
}
.mar-0, .mar-bottom-0 {
  margin-bottom: 0px !important;
}
.mar-0, .mar-left-0 {
  margin-left: 0px !important;
}
.mar-1, .mar-top-1 {
  margin-top: 50px !important;
}
.mar-1, .mar-right-1 {
  margin-right: 50px !important;
}
.mar-1, .mar-bottom-1 {
  margin-bottom: 50px !important;
}
.mar-1, .mar-left-1 {
  margin-left: 50px !important;
}
.mar-2, .mar-top-2 {
  margin-top: 25px !important;
}
.mar-2, .mar-right-2 {
  margin-right: 25px !important;
}
.mar-2, .mar-bottom-2 {
  margin-bottom: 25px !important;
}
.mar-2, .mar-left-2 {
  margin-left: 25px !important;
}
.mar-3, .mar-top-3 {
  margin-top: 12.5px !important;
}
.mar-3, .mar-right-3 {
  margin-right: 12.5px !important;
}
.mar-3, .mar-bottom-3 {
  margin-bottom: 12.5px !important;
}
.mar-3, .mar-left-3 {
  margin-left: 12.5px !important;
}
.mar-4, .mar-top-4 {
  margin-top: 6.25px !important;
}
.mar-4, .mar-right-4 {
  margin-right: 6.25px !important;
}
.mar-4, .mar-bottom-4 {
  margin-bottom: 6.25px !important;
}
.mar-4, .mar-left-4 {
  margin-left: 6.25px !important;
}
.mar-n1,
.mar-top-n1 {
  margin-top: -50px !important;
}
.mar-n1,
.mar-right-n1 {
  margin-right: -50px !important;
}
.mar-n1,
.mar-bottom-n1 {
  margin-bottom: -50px !important;
}
.mar-n1,
.mar-left-n1 {
  margin-left: -50px !important;
}
.mar-n2,
.mar-top-n2 {
  margin-top: -25px !important;
}
.mar-n2,
.mar-right-n2 {
  margin-right: -25px !important;
}
.mar-n2,
.mar-bottom-n2 {
  margin-bottom: -25px !important;
}
.mar-n2,
.mar-left-n2 {
  margin-left: -25px !important;
}
.mar-n3,
.mar-top-n3 {
  margin-top: -12.5px !important;
}
.mar-n3,
.mar-right-n3 {
  margin-right: -12.5px !important;
}
.mar-n3,
.mar-bottom-n3 {
  margin-bottom: -12.5px !important;
}
.mar-n3,
.mar-left-n3 {
  margin-left: -12.5px !important;
}
.mar-n4,
.mar-top-n4 {
  margin-top: -6.25px !important;
}
.mar-n4,
.mar-right-n4 {
  margin-right: -6.25px !important;
}
.mar-n4,
.mar-bottom-n4 {
  margin-bottom: -6.25px !important;
}
.mar-n4,
.mar-left-n4 {
  margin-left: -6.25px !important;
}
.pad-0, .pad-top-0 {
  padding-top: 0px !important;
}
.pad-0, .pad-right-0 {
  padding-right: 0px !important;
}
.pad-0, .pad-bottom-0 {
  padding-bottom: 0px !important;
}
.pad-0, .pad-left-0 {
  padding-left: 0px !important;
}
.pad-1, .pad-top-1 {
  padding-top: 50px !important;
}
.pad-1, .pad-right-1 {
  padding-right: 50px !important;
}
.pad-1, .pad-bottom-1 {
  padding-bottom: 50px !important;
}
.pad-1, .pad-left-1 {
  padding-left: 50px !important;
}
.pad-2, .pad-top-2 {
  padding-top: 25px !important;
}
.pad-2, .pad-right-2 {
  padding-right: 25px !important;
}
.pad-2, .pad-bottom-2 {
  padding-bottom: 25px !important;
}
.pad-2, .pad-left-2 {
  padding-left: 25px !important;
}
.pad-3, .pad-top-3 {
  padding-top: 12.5px !important;
}
.pad-3, .pad-right-3 {
  padding-right: 12.5px !important;
}
.pad-3, .pad-bottom-3 {
  padding-bottom: 12.5px !important;
}
.pad-3, .pad-left-3 {
  padding-left: 12.5px !important;
}
.pad-4, .pad-top-4 {
  padding-top: 6.25px !important;
}
.pad-4, .pad-right-4 {
  padding-right: 6.25px !important;
}
.pad-4, .pad-bottom-4 {
  padding-bottom: 6.25px !important;
}
.pad-4, .pad-left-4 {
  padding-left: 6.25px !important;
}

Full-width rows

First, let's address a problem with full-width rows. Full-width rows require JavaScript to get the correct width when placed inside of a container. You can't just set the width of full-width rows to 100vw, because 100vw includes the width of the scrollbar.

That's why I decided to measure the width of the scrollbar and redefine the CSS unit vw as a CSS variable called --vw that takes the width of the scrollbar into account.

First, force a scrollbar to appear and define CSS variable --scrollbar-width by adding the following style-attribute to the <html>-element:

<html style="min-height: 101vh; --scrollbar-width: 0px;">

Now, let's add a little bit of JavaScript all the way in the top of the <head>-element:

<script type="text/javascript">
var scrollbarWidth = 0;
function measureScrollbarWidth() {
  var newScrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
  if(newScrollbarWidth !== scrollbarWidth) {
    document.documentElement.style.setProperty('--scrollbar-width', newScrollbarWidth + 'px');
    scrollbarWidth = newScrollbarWidth;
  }
}
measureScrollbarWidth();
window.addEventListener('resize', measureScrollbarWidth);
</script>

The script above measures the width of the scrollbar and updates CSS variable --scrollbar-width if the width of the scrollbar is different from its last width, which is 0px by default.

Now, define variable --vw in a new rule set below the rule set with selector :root:

:root {
  --vw: calc((100vw - var(--scrollbar-width)) / 100);
}

Now you can make use of the CSS variable --vw to set the width of full-width rows and make sure that they are positioned on the left of the screen instead of at the left of the container. Add the following below the rule set with selector .pad-4, .pad-left-4:

.row.full-width {
  margin-left:  calc(50% - 50 * var(--vw));
  margin-right: calc(50% - 50 * var(--vw));
}

Another important detail is that full-width rows do not need a border radius, because that would look strange, so extend the following selectors:

:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) {
  --border-radius-depth: 1;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) {
  --border-radius-depth: 2;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) {
  --border-radius-depth: 3;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) {
  --border-radius-depth: 4;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right):not(.full-width) {
  --border-radius-depth: 5;
}
.border:not(.full-width), .border-top.border-left:not(.full-width) {
  border-top-left-radius: calc(25px / var(--border-radius-depth));
}
.border:not(.full-width), .border-top.border-right:not(.full-width) {
  border-top-right-radius: calc(25px / var(--border-radius-depth));
}
.border:not(.full-width), .border-bottom.border-left:not(.full-width) {
  border-bottom-left-radius: calc(25px / var(--border-radius-depth));
}
.border:not(.full-width), .border-bottom.border-right:not(.full-width) {
  border-bottom-right-radius: calc(25px / var(--border-radius-depth));
}
.row[class^='bg-']:not(.full-width),
.row[class*=' bg-']:not(.full-width),
.col[class^='bg-'],
.col[class*=' bg-'] {
  border-radius: calc(25px / var(--border-radius-depth));
}

You might also want rows of which the width of its contents are as wide as the container, so add the following below the rule set with selector .row.full-width:

.row.full-width.content-in-container {
  padding-left:  calc(50 * var(--vw) - 50% - 25px * var(--col-spacing-multiplier));
  padding-right: calc(50 * var(--vw) - 50% - 25px * var(--col-spacing-multiplier));
}

Also, if the first row is full width and has a background color, then make sure there is no space between the header and that row. Add the following below the rule set with selector .row.full-width.content-in-container:

main > article > .container > .row.full-width:first-child {
  margin-top: calc(-50px * var(--has-fill-top));
}

There is a scenario where a full width row with a background color is followed by another full width row by a background color. In this case, there should be no margin between them.

Add the following CSS rule set below the rule set with selector main > article > .container > .row.full-width:first-child:

.row.full-width[class^='bg-'] + .row.full-width[class^='bg-'],
.row.full-width[class^='bg-'] + .row.full-width[class*=' bg-'],
.row.full-width[class*=' bg-'] + .row.full-width[class^='bg-'],
.row.full-width[class*=' bg-'] + .row.full-width[class*=' bg-'] {
  margin-top: calc(-25px * var(--col-spacing-multiplier));
}

Full height for rows

Making a row full height is easy, just add the following below rule set with selector .row.full-width[class^='bg-'] + .row.full-width[class^='bg-'], .row.full-width[class^='bg-'] + .row.full-width[class*=' bg-'], .row.full-width[class*=' bg-'] + .row.full-width[class^='bg-'], .row.full-width[class*=' bg-'] + .row.full-width[class*=' bg-']:

.row.full-height {
  min-height: calc(100vh - var(--header-height) - var(--top-bar-height));
}

In chapter 16 Creating a header we define the height of the header as CSS variable --header-height for every breakpoint. For now, set variable --header-height and --top-bar-height to 0px in the rule set with selector :root:

:root {
  --vw:              calc((100vw - var(--scrollbar-width)) / 100);
  --header-height:   0px;
  --top-bar-height:  0px;
}

Box shadow

Add the following below the rule set:

.box-shadow {
  --has-fill-top:    1 !important;
  --has-fill-right:  1 !important;
  --has-fill-bottom: 1 !important;
  --has-fill-left:   1 !important;
  box-shadow:        0px 0px 6.25px 6.25px rgba(0, 0, 0, 0.03);
}

If you want to use this class, please extend the following selectors:

:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 1;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 2;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 3;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 4;
}
:is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 5;
}
.row[class^='bg-']:not(.full-width),
.row[class*=' bg-']:not(.full-width),
.col[class^='bg-'],
.col[class*=' bg-'],
.row.box-shadow:not(.full-width),
.col.box-shadow {
  border-radius: calc(25px / var(--border-radius-depth));
}
.row[class^='bg-'] + .row,
.row[class*=' bg-'] + .row,
.row.border + .row,
.row.border-bottom + .row,
.row.box-shadow + .row {
  margin-top: calc(25px * 2 * var(--col-spacing-multiplier));
}
:not(.row):not(.col) + .row[class^='bg-'],
:not(.row):not(.col) + .row[class*=' bg-'],
:not(.row):not(.col) + .row.border,
:not(.row):not(.col) + .row.border-top,
:not(.row):not(.col) + .row.box-shadow {
  margin-top: max(12.5px, calc(25px * 2 * var(--col-spacing-multiplier)));
}
.row[class^='bg-'] + :not(.row):not(.col),
.row[class*=' bg-'] + :not(.row):not(.col),
.row.border + :not(.row):not(.col),
.row.border-bottom + :not(.row):not(.col),
.row.box-shadow + :not(.row):not(.col) {
  margin-top: max(12.5px, calc(25px * var(--col-spacing-multiplier)));
}

Positioning

CSS has 5 types of positioning:

  1. absolute
  2. fixed
  3. relative
  4. static (default)
  5. sticky

Add the following below the rule set with selector .row.full-width.content-in-container:

.absolute {
  position: absolute !important;
}
.fixed {
  position: fixed !important;
}
.relative {
  position: relative !important;
}
.static {
  position: static !important;
}
.sticky {
  position: sticky !important;
}
.top {
  top:    0px;
  bottom: auto;
}
.right {
  right: 0px;
  left:  auto;
}
.bottom {
  bottom: 0px;
  top:    auto;
}
.left {
  left:  0px;
  right: auto;
}
.top-out {
  bottom: 100%;
  top:    auto;
}
.right-out {
  left:  100%;
  right: auto;
}
.bottom-out {
  top:    100%;
  bottom: auto;
}
.left-out {
  right: 100%;
  left:  auto;
}

Move element out of the container on the left or right

A nice effect can be to have an image fill the entire available width on the right side, while the left column contains text that is aligned with the container. Before we can do this, however, we need to know the width of the container.

We can't just use 100% to refer to the width of the container, because the elements that you want to move out of the container on the left or right side, can be placed within columns.

First, let's set the default container width as a CSS variable called --container-width in the rule set with selector :root.

:root {
  --vw:              calc((100vw - var(--scrollbar-width)) / 100);
  --container-width: 1570px;
  --header-height:   0px;
  --top-bar-height:  0px;
}

Change value 1570px to var(--container-width) in the first rule set with selector .container:

/* DT - Desktop */
.container {
  width:         var(--container-width);
  margin-left:   auto;
  margin-right:  auto;
  padding-left:  25px;
  padding-right: 25px;
}

Now, override variable --container-width in the remainder of the rule sets with selector .container by simply adding --container- in front of property width:

.container {
  --container-width: 1170px;
}
.container {
  --container-width: 962px;
}
.container {
  --container-width: calc(100 * var(--vw));
}
.container {
  --container-width: calc(100 * var(--vw));
}
.container {
  --container-width: calc(100 * var(--vw));
}

Notice that the last three rule sets use value calc(100 * var(--vw)) instead of 100% for variable --container-width, because we can't just refer to 100% for the container width, because the element is most likely not a direct parent of the container.

Finally, we can use the variable --container-width to move elements out of the container on the left or on the right. Add the following below the rule set with selector .left-out:

.out-container-left {
  margin-left: calc((var(--container-width) - var(--vw) * 100) / 2 - 25px);
  max-width:   calc(100% + (var(--vw) * 100 - var(--container-width)) / 2 + 25px);
}
.out-container-right {
  margin-right: calc((var(--container-width) - var(--vw) * 100) / 2 - 25px);
  max-width:    calc(100% + (var(--vw) * 100 - var(--container-width)) / 2 + 25px);
}

Styling for less common elements

abbr

abbr is an inline HTML-element that defines an abbreviation. Each <abbr>-element has a title-attribute that shows when you hover on the element. Since mobile devices do not have a hover-function to be able to show the contents of the title-attribute, we can make use of CSS function attr to refer to attribute title and use its value for the value of the content-property of pseudo-element ::after:

@media (hover: none) {
  abbr[title] {
    text-decoration: none;
  }
  abbr[title]::after {
    content: ' (' attr(title) ')';
  }
}

address

<address> is a block-level HTML-element that defines an address. The styling is not that exciting, it's just italic:

address {
  font-style: italic;
}
address:not(:last-child) {
  margin-bottom: 12.5px;
}

b, strong

<b> is an inline HTML-element that defines bold text without adding any importance to it. <strong> is an inline HTML-element that defines important text. Both elements have the same styling, but semantically they have different use cases.

Use element <b> to draw attention to something:

By using the b-element you draw attention to a certain element.

Use element <strong> to mark something important:

Heat the oatmeal in the microwave on 600W for 4 minutes.
Remove the spoon before microwaving the oatmeal.

blockquote

<blockquote> is a block-level HTML-element that defines a text that is quoted from another source. You can refer to the source by using attribute <cite> and the URL as the value.

The <blockquote> element represents a quoted section.

blockquote {
  display:      block;
  font-style:   italic;
  padding-left: calc(3em + 25px);
  position:     relative;
  quotes:       none;
}
blockquote:not(:last-child) {
  margin-bottom: 12.5px;
}
blockquote::before {
  content:     '“';
  display:     block;
  font-size:   10em;
  margin-top:  0.32em;
  color:       var(--blockquote-decoration-color);
  font-style:  normal;
  position:    absolute;
  left:        0px;
  top:         0px;
}
blockquote::after {
  content: '';
  content: none;
}

Please define CSS variable --blockquote-decoration-color in the rule set with selector .color-scheme-dark and in the rule set with selector .color-scheme-light:

.color-scheme-dark {
  --accent-color:                var(--light-blue);
  --hyperlink-color:             var(--dark-blue);
  --field-color:                 var(--black);
  --field-background-color:      transparent;
  --field-border-color:          var(--gray);
  --checkbox-check-url:          url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:         url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23000000'/%3E%3C/svg%3E");
  --blockquote-decoration-color: var(--light-blue);
  --txt-color:                   var(--black);
}
.color-scheme-light {
  --accent-color:                var(--lighter-blue);
  --hyperlink-color:             var(--lighter-blue);
  --field-color:                 var(--white);
  --field-background-color:      transparent;
  --field-border-color:          var(--light-gray);
  --checkbox-check-url:          url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:         url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23ffffff'/%3E%3C/svg%3E");
  --blockquote-decoration-color: var(--light-blue);
  --txt-color:                   var(--white-a90);
}

cite

<cite> is an inline HTML-element that defines the title of a work.

cite {
  font-style: italic;
}
Comb Hommes by Terluin Webdesign

Comb Hommes by Terluin Webdesign.

code, pre

<code> is an inline HTML-element that defines computer code. This element can also be used within a <pre>-element. <pre> is a block-level HTML-element that defines preformatted text. This means that every single line-break and space inside of that element is taken into account. Both elements use a monospace font.

Element <code> can be placed inside <pre> to define block-level computer code, as shown in the following CSS code example:

code {
  font-size:        0.8em;
  font-family:      monospace;
  padding:          0.125em;
  background-color: var(--code-background-color);
}
pre {
  display:    block;
  margin-top: 0px;
}
pre:not(:last-child) {
  margin-bottom: 12.5px;
}
pre > code {
  display:     block;
  padding:     1em;
  white-space: pre;
  overflow-x:  auto;
}

Please define CSS variable --code-background-color in the rule set with selector .color-scheme-dark and in the rule set with selector .color-scheme-light:

.color-scheme-dark {
  --accent-color:                var(--light-blue);
  --hyperlink-color:             var(--dark-blue);
  --field-color:                 var(--black);
  --field-background-color:      transparent;
  --field-border-color:          var(--gray);
  --checkbox-check-url:          url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:         url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23000000'/%3E%3C/svg%3E");
  --blockquote-decoration-color: var(--light-blue);
  --code-background-color:       var(--lighter-gray);
  --txt-color:                   var(--black);
}
.color-scheme-light {
  --accent-color:                var(--lighter-blue);
  --hyperlink-color:             var(--lighter-blue);
  --field-color:                 var(--white);
  --field-background-color:      transparent;
  --field-border-color:          var(--light-gray);
  --checkbox-check-url:          url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:         url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23ffffff'/%3E%3C/svg%3E");
  --blockquote-decoration-color: var(--light-blue);
  --code-background-color:       var(--dark-blue);
  --txt-color:                   var(--white-a90);
}

dd, dl, dt

The definitions of the following three terms are displayed using <dd>, <dl>, and <dt>:

dd
Element <dd> defines a definition description.
dl
Element <dl> defines a definition list.
dt
Element <dt> defines a definition term.
dd {
  margin-left: 0.75em;
}
dd:not(:last-child) {
  margin-bottom: 6.25px;
}
dl:not(:last-child) {
  margin-bottom: 12.5px;
}
dt {
  font-weight: bold;
}

del, s, ins

<del> is an inline HTML-element that represents text that is deleted. This is often used to strike through the original price. <s> is an inline-element that defines text that is no longer correct. <ins> is an inline HTML-element that represents text that is inserted into the document, for instance the sale price that follows the original price.

€100 €75

del, s {
  text-decoration: line-through;
}
ins {
  text-decoration: underline;
}

details, summary

<details> is a block-level HTML-element that allows the user to open and close additional text or details on click. <summary> is a block-level HTML-element that displays text inside of the <details>-element by default, regardless of whether the <details>-element is opened or closed.

Show CSS
details:not(:last-child) {
  margin-bottom: 12.5px;
}
details summary {
  cursor: help;
  background-image: var(--selectbox-caret-url);
  background-size: 0px;
}
details summary::marker {
  content: '';
  display: none;
}
details:not(.read-more) summary::before {
  content: '';
  display: inline-block;
  width: 1em;
  height: 1em;
  background-image: inherit;
  background-size: auto 0.5em;
  background-repeat: no-repeat;
  background-position: center center;
  transform: rotate(-90deg);
  margin-bottom: -0.15em;
  margin-right: 6.25px;
}
details[open] summary::before {
  transform: rotate(0deg);
}
details[open] summary {
  margin-bottom: 6.25px;
}
details.read-more > summary {
  font-weight: 600;
}
details.read-more > summary::after {
  content: ' ...';
}
details.read-more[open] > summary {
  display: none;
}

dfn

<dfn> is an inline HTML-element that specifies a term that is going to be defined within the content. The sentence you just read started with a <dfn>-element that wrapped the word <dfn>. It is shown in italic font style.

dfn {
  font-style: italic;
}

dialog

<dialog> is a block-level HTML-element that defines a dialog box, but it can also be used to create a subwindow.

dialog {
  padding:          50px;
  color:            var(--txt-color);
  background-color: var(--bg-color);
  border:           1.5px solid currentColor;
  border-radius:    25px;
}
dialog::backdrop {
  background-color: rgba(0, 0, 0, .5);
}
dialog .row,
dialog .row::before {
  --col-spacing-multiplier: 1;
}
dialog .row .row,
dialog .row .row::before {
  --col-spacing-multiplier: 0.5;
}
dialog .row .row .row,
dialog .row .row .row::before {
  --col-spacing-multiplier: 0.25;
}
dialog:not(#_) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 2;
}
dialog:not(#_) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 3;
}
dialog:not(#_) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 4;
}
dialog:not(#_) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) :is([class^='bg-'], [class*=' bg-'], img, .border, .border-top.border-left, .border-top.border-right, .border-bottom.border-left, .border-bottom.border-right, .box-shadow):not(.full-width) {
  --border-radius-depth: 5;
}

em, i

<em> is an inline HTML-element that is used to emphasize text. <i> is an inline HTML-element that defines a part of text in an alternate voice or mood. Both elements are displayed in italic.

The <em>-element can be used to give verbal stress to a word in a sentence like Is it necessary to style all HTML-elements that exist? No, not all of them, because some elements are hidden and some elements are not supported in HTML5..

The <i>-element can be used to markup a (technical) term, a phrase in a different language, the binomial name of a plant (Helianthus annuus, for instance), and so on.

em, i {
  font-style: italic;
}

fieldset, legend

<fieldset> is a block-level HTML-element that groups elements in a form that are related to each other and is shown as a bordered box around the elements.

fieldset {
  padding:       12.5px;
  border:        1.5px solid currentColor;
  border-radius: 8.33px;
}
fieldset > legend {
  padding-left:  6.25px;
  padding-right: 6.25px;
}
fieldset > legend + .row {
  margin-top: 0px;
}

The form below contains a fieldset and is a functioning form, so if you want to share your feedback on this article, please use the form below.

"*" indicates required fields

This field is for validation purposes and should be left unchanged.

figure, figcaption

<figure> is a block-level HTML-element that defines content like images that have a caption, but removing the element itself should not affect the flow of the document. <figcaption> is a block-level HTML-element that is used within <figure>-elements to define a caption.

Pidgeon website
Figure 1. Pidgeon website

The CSS below styles <figure>-elements and <figcaption>-elements and also automatically numbers the figures using CSS' counter-function.

body {
  counter-reset: figcaption;
}
figure > figcaption::before {
  content:           "Figure " counter(figcaption) ". ";
  counter-increment: figcaption;
  font-style:        italic;
}
figure:not(:last-child) {
  margin-bottom: 12.5px;
}
figure > img ~ figcaption {
  margin-top: -6.25px;
}

hr

<hr> is a block-level HTML-element that defines a thematic change in the contents of a document (going off-topic, for instance). It is displayed as a horizontal rule to separate two pieces of content or to define a change in the HTML document.

hr {
  margin-top:          25px;
  margin-bottom:       25px;
  border-top-width:    0px;
  border-right-width:  0px;
  border-bottom-width: 1.5px;
  border-left-width:   0px;
  border-style:        solid;
  border-color:        currentColor;
  background:          currentColor;
  opacity:             0.2;
}

iframe

<iframe> is a block-level HTML-element that defines an inline frame. It can contain another document, but without having access to the document itself (for security reasons).

iframe {
  display:      block;
  width:        100%;
  aspect-ratio: 16/9;
  border:       1.5px solid var(--light-gray);
}

kbd

<kbd> is an inline HTML-element that represents a key on your keyboard, for instance: Enter, Shift, Ctrl, Alt, and F4. This is how I decided to style these elements:

kbd {
  display:          inline-block;
  font-size:        0.8em;
  font-family:      monospace;
  padding:          0.125em 0.375em;
  margin-right:     0.25em;
  background-color: var(--kbd-background-color);
  line-height:      1.25em;
  border-radius:    0.5em;
  box-shadow:       .09375em .09375em currentColor;
}

Please define CSS variable --kbd-background-color in the rule set with selector .color-scheme-dark and in the rule set with selector .color-scheme-light:

.color-scheme-dark {
  --accent-color:                var(--light-blue);
  --hyperlink-color:             var(--dark-blue);
  --field-color:                 var(--black);
  --field-background-color:      transparent;
  --field-border-color:          var(--gray);
  --checkbox-check-url:          url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:         url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23000000'/%3E%3C/svg%3E");
  --blockquote-decoration-color: var(--light-blue);
  --code-background-color:       var(--lighter-gray);
  --kbd-background-color:        var(--lighter-gray);
  --txt-color:                   var(--black);
}
.color-scheme-light {
  --accent-color:                var(--lighter-blue);
  --hyperlink-color:             var(--lighter-blue);
  --field-color:                 var(--white);
  --field-background-color:      transparent;
  --field-border-color:          var(--light-gray);
  --checkbox-check-url:          url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:         url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23ffffff'/%3E%3C/svg%3E");
  --blockquote-decoration-color: var(--light-blue);
  --code-background-color:       var(--dark-blue);
  --kbd-background-color:        var(--dark-blue);
  --txt-color:                   var(--white-a90);
}

li, ol, ul, nav

<li> is a block-level HTML-element that defines list items. <ol> is a block-level HTML-element that defines an ordered list of list items. <ul> is a block-level HTML-element that defines an unordered list of list items. <nav> is a block-level HTML-element that defines a set of navigation links. <nav>-elements are often found inside of a <header>-element (the main header, for instance) and often contain <ul>-elements with <li>-elements inside of those unordered lists. Instead of targeting the <header>-tag, I decided to target the element with ID <header>, which I will discuss later in this article.

#header nav ul,
#top-bar nav ul,
#footer nav ul {
  list-style:    none;
  margin-bottom: 0px;
  display:       flex;
  flex-wrap:     wrap;
}
#header nav ul,
#top-bar nav ul {
  align-items:   center;
}
#footer nav ul {
  flex-direction: column;
  margin-left:    0px;
}
#copyright-bar nav ul {
  flex-direction: row;
}
#header nav ul {
  margin-left:  -12.5px;
  margin-right: -12.5px;
}
#top-bar nav ul,
#copyright-bar nav ul {
  margin-left:  -6.25px;
  margin-right: -6.25px;
}
#header nav ul li,
#top-bar nav ul li,
#copyright-bar nav ul li {
  margin: 0px;
}
#header nav ul li > a,
#top-bar nav ul li > a,
#footer nav ul li > a {
  text-decoration: none;
  display:         block;
  color:           var(--txt-color);
}
#header nav ul li > a {
  padding: 6.25px 12.5px;
}
#top-bar nav ul li > a,
#copyright-bar nav ul li > a {
  padding-left:  6.25px;
  padding-right: 6.25px;
}

mark

<mark> is an inline HTML-element that marks parts of text. I decided to leave the background color as is, but I added a little bit of padding to make it stand out.

mark {
  padding: 3.125px 6.25px;
}

meter, progress

<meter> is an inline HTML-element that is used to display a scalar value within a given range (a gauge). While this is not a form element, <label>-elements are used to label <meter>-elements the same way as labels refer to their related form element.

95%

<progress> is an inline HTML-element that is used to display the progress of completing a task. This is also not a form element, but <label>-elements are also used to label these elements.

55%

samp

<samp> is an inline HTML-element that defines that that is the output of a computer program, for instance: Boot Device Not Found. It is displayed in a monospace font.

samp {
  font-family: monospace;
}

small

<small> is an inline HTML-element that defines smaller text. This can be used for copyright text and side-comments that companies want you to accidentally not read, for instance:

Payment plans renew automatically. Change plans or cancel anytime.

small {
  font-size: 0.8em;
}

sub, sup

<sub> is an inline HTML-element that defines subscripted text, for instance the zero in H2O. <sup> is an inline HTML-element that defines superscripted text, for instance the two in x2.

sub {
  font-size: 0.8em;
  vertical-align: sub;
}
sup {
  font-size: 0.8em;
  vertical-align: super;
}

svg

<svg> is an HTML-element that serves as a container for SVG graphics:

Buy Me a Coffee

svg {
  display:   block;
  max-width: 100%;
}
svg:not(:last-child) {
  margin-bottom: 12.5px;
}

table, tbody, td, tfoot, th, thead, tr

<table> is an HTML-element that defines a table, hence the name table. <tbody> is an HTML-element that defines the body of the <table>-element.

<td> is an HTML-element that defines a table data cell, for instance 114.2, to be placed inside a table row.

<tfoot> is an HTML-element that defines the footer of a <table>-element. For example, a table footer could contain the sum of the values inside the <tbody>-element.

<th> is an HTML-element that behaves the same as the <td>-element, but is used as a table heading, for instance Process, to be placed inside a table row.

<thead> is an HTML-element that serves as the header of a <table>-element. This element contains a table row that contains <th>-elements like Process and Time (ms).

<tr> is an HTML-element that defines a table row. This element can contain both <td>-elements and <th>-elements and can be placed in both <thead>, <tbody>, and in <tfoot>-elements.

ProcessTime (ms)
Loading19
Scripting92.4
Rendering114.2
Painting14.6
System51.8
Total (ms)292
table {
  border-collapse: collapse;
  border-spacing:  0px;
  border:          1.5px solid var(--table-border-color);
  box-sizing:      border-box;
}
td, th {
  padding:    6.25px 12.5px;
  text-align: left;
}
td {
  background-color: var(--table-data-cell-background-color);
  color:            var(--table-data-cell-text-color);
}
th {
  background-color: var(--table-header-cell-background-color);
  color:            var(--table-header-cell-text-color);
}
th:not(:first-child),
td:not(:first-child) {
  border-left: 1.5px solid var(--table-border-color);
}
thead:not(:last-child) > tr > td,
thead:not(:last-child) > tr > th,
tbody:not(:last-child) > tr > td,
tbody:not(:last-child) > tr > th,
tfoot:not(:last-child) > tr > td,
tfoot:not(:last-child) > tr > th,
thead:last-child > tr:not(:last-child) > td,
thead:last-child > tr:not(:last-child) > th,
tbody:last-child > tr:not(:last-child) > td,
tbody:last-child > tr:not(:last-child) > th,
tfoot:last-child > tr:not(:last-child) > td,
tfoot:last-child > tr:not(:last-child) > th {
  border-bottom: 1.5px solid var(--table-border-color);
}
tfoot > tr > :not(th) {
  background-color: var(--table-footer-cell-background-color);
  color:            var(--table-footer-cell-text-color);
}

Please define the following CSS variables in the rule set with selector .color-scheme-dark and the rule set with selector .color-scheme-light:

.color-scheme-dark {
  --accent-color:                       var(--light-blue);
  --hyperlink-color:                    var(--dark-blue);
  --field-color:                        var(--black);
  --field-background-color:             transparent;
  --field-border-color:                 var(--gray);
  --checkbox-check-url:                 url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23ffffff'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:                url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23000000'/%3E%3C/svg%3E");
  --blockquote-decoration-color:        var(--light-blue);
  --code-background-color:              var(--lighter-gray);
  --kbd-background-color:               var(--lighter-gray);
  --table-border-color:                 var(--lighter-gray);
  --table-header-cell-background-color: var(--lighter-gray);
  --table-header-cell-text-color:       var(--black);
  --table-data-cell-background-color:   transparent;
  --table-data-cell-text-color:         var(--black);
  --table-footer-cell-background-color: var(--off-white);
  --table-footer-cell-text-color:       var(--black);
  --txt-color:                          var(--black);
}
.color-scheme-light {
  --accent-color:                       var(--lighter-blue);
  --hyperlink-color:                    var(--lighter-blue);
  --field-color:                        var(--white);
  --field-background-color:             transparent;
  --field-border-color:                 var(--light-gray);
  --checkbox-check-url:                 url("data:image/svg+xml,%3Csvg width='167' height='218' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath stroke='%23000' id='svg_16' d='m0.000002,112.697894c0,0 73.64307,105.03828 73.64307,105.03828c0,0 42.63547,0 42.63547,0l50.46119,-217.73617c0.31377,0.38759 -43.87208,0.38759 -43.87208,0.38759c0,0 -36.82153,160.07678 -36.82153,160.07678c0,0 -45.73623,-63.95319 -46.05,-64.34078l-39.99612,16.5743z' stroke-opacity='null' stroke-width='0' fill='%23000000'/%3E%3C/g%3E%3C/svg%3E");
  --selectbox-caret-url:                url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='141.36422' height='84.93994' viewBox='0 0 141.36422 84.93994'%3E%3Cpolygon points='0 13.542 70.625 84.94 141.364 13.542 127.293 0 70.625 59.946 14.071 0 0 13.542' fill='%23ffffff'/%3E%3C/svg%3E");
  --blockquote-decoration-color:        var(--light-blue);
  --code-background-color:              var(--dark-blue);
  --kbd-background-color:               var(--dark-blue);
  --table-border-color:                 var(--dark-blue);
  --table-header-cell-background-color: var(--dark-blue);
  --table-header-cell-text-color:       var(--white);
  --table-data-cell-background-color:   transparent;
  --table-data-cell-text-color:         var(--white);
  --table-footer-cell-background-color: var(--darker-blue);
  --table-footer-cell-text-color:       var(--white);
  --txt-color:                          var(--white-a90);
}

u

<u> is an inline HTML-element that is used to mark up mispeld text.

u {
  text-decoration-line:  underline;
  text-decoration-style: wavy;
  text-decoration-color: red;
}

var

<var> is an inline HTML-element that is used to mark up a variable. The hypotenuse of a triangle where side A and side B are both 1 centimeter, is1.4142. It is displayed in italic.

var {
  font-style: italic;
}

video

<video> is an HTML-element that is used to display a video. This element needs a <source>-element with a valid src-attribute. In order to automatically play a video, you can add the following attributes (with a blank value): autoplay, defaultmuted, muted.

video {
  display: block;
  width:   100%;
}
video:not(:last-child) {
  margin-bottom: 12.5px;
}

Utility classes for advanced layouts

Reversing, direction, alignment, and RTL layouts

Advanced layouts are layouts that can move columns to the left, to the right, in the center, to the top, to the bottom and vertically centered all while also being able to change the direction of the columns from horizontal to vertical (from flex-direction: row to flex-direction: column) and even reverse those directions. I calculated that there are 36 possibilities (2 * 2 * 3 * 3):

  1. :not(.reverse):not(.vertical).content-left.items-top
  2. :not(.reverse):not(.vertical).content-left.items-center
  3. :not(.reverse):not(.vertical).content-left.items-bottom
  4. :not(.reverse):not(.vertical).content-center.items-top
  5. :not(.reverse):not(.vertical).content-center.items-center
  6. :not(.reverse):not(.vertical).content-center.items-bottom
  7. :not(.reverse):not(.vertical).content-right.items-top
  8. :not(.reverse):not(.vertical).content-right.items-center
  9. :not(.reverse):not(.vertical).content-right.items-bottom
  10. :not(.reverse).vertical.content-left.items-top
  11. :not(.reverse).vertical.content-left.items-center
  12. :not(.reverse).vertical.content-left.items-bottom
  13. :not(.reverse).vertical.content-center.items-top
  14. :not(.reverse).vertical.content-center.items-center
  15. :not(.reverse).vertical.content-center.items-bottom
  16. :not(.reverse).vertical.content-right.items-top
  17. :not(.reverse).vertical.content-right.items-center
  18. :not(.reverse).vertical.content-right.items-bottom
  19. .reverse:not(.vertical).content-left.items-top
  20. .reverse:not(.vertical).content-left.items-center
  21. .reverse:not(.vertical).content-left.items-bottom
  22. .reverse:not(.vertical).content-center.items-top
  23. .reverse:not(.vertical).content-center.items-center
  24. .reverse:not(.vertical).content-center.items-bottom
  25. .reverse:not(.vertical).content-right.items-top
  26. .reverse:not(.vertical).content-right.items-center
  27. .reverse:not(.vertical).content-right.items-bottom
  28. .reverse.vertical.content-left.items-top
  29. .reverse.vertical.content-left.items-center
  30. .reverse.vertical.content-left.items-bottom
  31. .reverse.vertical.content-center.items-top
  32. .reverse.vertical.content-center.items-center
  33. .reverse.vertical.content-center.items-bottom
  34. .reverse.vertical.content-right.items-top
  35. .reverse.vertical.content-right.items-center
  36. .reverse.vertical.content-right.items-bottom

In order to make all of these combinations possible, I wrote the CSS step by step.

I started by defining the row directions and reversing:

.row:not(.reverse):not(.vertical) {
  flex-direction:  row;
  justify-content: flex-start;
}
.row.reverse:not(.vertical) {
  flex-direction:  row-reverse;
  justify-content: flex-end;
}
.row:not(.reverse).vertical {
  flex-direction:  column;
  align-items:     flex-start;
}
.row.reverse.vertical {
  flex-direction:  column-reverse;
  align-items:     flex-start;
}

So the code above sets the flex-direction-property and the justify-content-property and align-items-property. Property justify-content determines the horizontal alignment, while property align-items determines the vertical alignment.

These properties swap roles when flex-direction: column is applied instead of flex-direction: row. For rows with flex-direction: column applied, the property justify-content determines the vertical alignment, while property align-items determines the horizontal alignment.

That's why I used property align-items in the last two rule sets instead of justify-content.

Using the value flex-start indicates the start of the axis, while value flex-end indicates the end of the axis. When flex-direction: row-reverse or flex-direction: column-reverse are applied, the roles of values flex-start and flex-end are also swapped.

This means that, for example, for horizontally reversed rows, I have to use value flex-end for property justify-content in order to align the columns within the rows to the left. Likewise, for horizontally reversed rows, I have to use value flex-start for property justify-content in order to align the columns with the rows to the right. That's why I used value flex-end to be the default value for property justify-content for horizontally reversed rows.

This is not the case when using property align-items, which means that properties flex-start and flex-end do not swap roles. I think the reason behind this is for RTL layouts. RTL layouts go from right to left, but not from bottom to top - they still go from top to bottom.

When translating your website to Arabic, not only should the (horizontal) order of the columns be reversed, the horizontal alignment should also be reversed, which is exactly what happens by the browser.

What does happen, but I'm not sure whether this is intentional or not, is that when you have a vertical row with property justify-content set to flex-start, that the columns are aligned at the bottom instead of at the top.

This is because values flex-start and flex-end for property justify-content swap regardless of the main axis of the row, be it horizontal or vertical.

The way these properties work is mind blowing, so I coded the utility classes in such a way that it works great for LTR layouts. If you apply direction: rtl to the body, then the browser will automatically apply the quirks of RTL layouts.

Rule sets for left alignment:

.row:not(.reverse):not(.vertical).content-left {
  justify-content: flex-start !important;
}
.row.reverse:not(.vertical).content-left {
  justify-content: flex-end !important;
}
.row:not(.reverse).vertical.content-left {
  align-items: flex-start !important;
}
.row.reverse.vertical.content-left {
  align-items: flex-start !important;
}

We could argue that I could remove the fourth rule set and remove :not(.reverse) from the selector of the third rule set, but I would like to keep these separate to avoid confusion as to why the third rule set does not take the existence of class reverse into account.

Rule sets for center alignment:

.row:not(.vertical).content-center {
  justify-content: center !important;
}
.row.vertical.content-center {
  align-items: center !important;
}

The center alignment has fewer rule sets, because, unlike values left and right, the value center does not have a polar opposite. This means that there are no values for value center to swap with.

Rule sets for right alignment:

.row:not(.reverse):not(.vertical).content-right {
  justify-content: flex-end !important;
}
.row.reverse:not(.vertical).content-right {
  justify-content: flex-start !important;
}
.row:not(.reverse).vertical.content-right {
  align-items: flex-end !important;
}
.row.reverse.vertical.content-right {
  align-items: flex-end !important;
}

Rule sets for top alignment:

.row:not(.reverse):not(.vertical).items-top {
  align-items: flex-start !important;
}
.row.reverse:not(.vertical).items-top {
  align-items: flex-start !important;
}
.row:not(.reverse).vertical.items-top {
  justify-content: flex-start !important;
}
.row.reverse.vertical.items-top {
  justify-content: flex-end !important;
}

Rule sets for vertically centered alignment:

.row:not(.vertical).items-center {
  align-items: center !important;
}
.row.vertical.items-center {
  justify-content: center !important;
}

Like as is the case for horizontally centered alignment, the value center does not have a polar opposite, which is the reason why vertically centered alignment has fewer rule sets.

Rule sets for bottom alignment:

.row:not(.reverse):not(.vertical).items-bottom {
  align-items: flex-end !important;
}
.row.reverse:not(.vertical).items-bottom {
  align-items: flex-end !important;
}
.row:not(.reverse).vertical.items-bottom {
  justify-content: flex-end !important;
}
.row.reverse.vertical.items-bottom {
  justify-content: flex-start !important;
}

Now, let's add a class that indicates an RTL layout:

.rtl {
  direction: rtl;
}

Let's also add a class that indicates a LTR layout, so you can switch between LTR and RTL layouts.

.ltr {
  direction: ltr;
}

You can add these classes to the body and it will be inherited by underlying elements until a different value is provided, which starts a new waterfall/cascade as explained in chapter 4.1 Why inheritance is better than directly overriding values.

Column width

A common method to define column widths in CSS is to simply hardcode all of them as a value from 1 to 12. This is not what you are going to do, but this is what I mean:

.col-12 {
  width: 100%;
}
.col-11 {
  width: 91.66666667%;
}
.col-10 {
  width: 83.33333333%;
}
.col-9 {
  width: 75%;
}
.col-8 {
  width: 66.66666667%;
}
.col-7 {
  width: 58.33333333%;
}
.col-6 {
  width: 50%;
}
.col-5 {
  width: 41.66666667%;
}
.col-4 {
  width: 33.33333333%;
}
.col-3 {
  width: 25%;
}
.col-2 {
  width: 16.66666667%;
}
.col-1 {
  width: 8.33333333%;
}

Instead, I give you the ability to perform a division to set the width of columns by writing a class in the following format: w-4//7. Why two slashes? Well, you can add lt between the two slashes to apply that column width only for breakpoint LT - Laptop & Small desktop.

For now, I will show you the classes to be used to set a column width for every device, but in chapter 14 Breakpoint-specific utility classes for responsive layouts, a lot of classes will be copied to the other containers, including the classes to set column widths.

In contrast to the example above of what we are not going to do, the approach of using a dividend and a divisor of maximum 24 to define a column width, will make it possible to set a column width that is way more accurate than 12 hardcoded widths.

You have 576 combinations of which 359 are fractions that result in a value of 1 or below of which 180 values are unique values. This is how I calculated this:

aList = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];
bList = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24];
var possibilitiesString = '';
var possibilitiesCounter = 0;
var possibilityFractionList = [];
aList.forEach(function(aListItem) {
  bList.forEach(function(bListItem) {
    if (
      aListItem <= bListItem
      &&
      !possibilityFractionList.includes(aListItem / bListItem)
    ) {
      possibilitiesString += 'w-' + aListItem + '//' + bListItem + "rn";
      possibilitiesCounter++;
      possibilityFractionList.push(aListItem / bListItem);
    }
  });
});
console.log(possibilitiesString);
console.log(possibilitiesCounter);

Finally, let me show you the CSS:

[class*='w-1//']{--a:1}
[class*='w-2//']{--a:2}
[class*='w-3//']{--a:3}
[class*='w-4//']{--a:4}
[class*='w-5//']{--a:5}
[class*='w-6//']{--a:6}
[class*='w-7//']{--a:7}
[class*='w-8//']{--a:8}
[class*='w-9//']{--a:9}
[class*='w-10//']{--a:10}
[class*='w-11//']{--a:11}
[class*='w-12//']{--a:12}
[class*='w-13//']{--a:13}
[class*='w-14//']{--a:14}
[class*='w-15//']{--a:15}
[class*='w-16//']{--a:16}
[class*='w-17//']{--a:17}
[class*='w-18//']{--a:18}
[class*='w-19//']{--a:19}
[class*='w-20//']{--a:20}
[class*='w-21//']{--a:21}
[class*='w-22//']{--a:22}
[class*='w-23//']{--a:23}
[class*='w-24//']{--a:24}
[class*='//1']{--b:1}
[class*='//2']{--b:2}
[class*='//3']{--b:3}
[class*='//4']{--b:4}
[class*='//5']{--b:5}
[class*='//6']{--b:6}
[class*='//7']{--b:7}
[class*='//8']{--b:8}
[class*='//9']{--b:9}
[class*='//10']{--b:10}
[class*='//11']{--b:11}
[class*='//12']{--b:12}
[class*='//13']{--b:13}
[class*='//14']{--b:14}
[class*='//15']{--b:15}
[class*='//16']{--b:16}
[class*='//17']{--b:17}
[class*='//18']{--b:18}
[class*='//19']{--b:19}
[class*='//20']{--b:20}
[class*='//21']{--b:21}
[class*='//22']{--b:22}
[class*='//23']{--b:23}
[class*='//24']{--b:24}
.col[class*='//'] {
  flex-grow:  0;
  flex-basis: calc(var(--a) / var(--b) * 100% - 25px * 2 * var(--col-spacing-multiplier));
  width:      calc(var(--a) / var(--b) * 100% - 25px * 2 * var(--col-spacing-multiplier));
}

Not too bad for 180 possible widths.

I also find it handy to set the width of specific columns to be automatic.

.col.w-auto {
  flex-basis: auto;
  flex-grow:  0;
}

This way you can emulate the behavior of multiple elements that have display: inline-block applied, while instead being flex items that can take full advantage of the power of flexbox.

100% width or height

Being able to set the height-property to a value of 100% can be useful if, for instance, you want to place a row within a column that holds an image, and center an element within that row which has to fill the full width and height of the column. That's a little bit specific, but I couldn't make up a better example. There must be better examples.

.w100 {
  width: 100% !important;
}
.h100 {
  height: 100% !important;
}

Please don't use class w100 to set the width of rows or columns. These classes are intended for other elements.

Z-Index

When you try to overlap an element that is placed before an element in the DOM tree, you need to set a (higher) value for property z-index to prevent that other element from overlapping it. In order for property z-index to work, the element needs to have a value other than static. The values for elements with class absolute were set with !important, so setting the value to relative without using !important won't overwrite it.

Since I want to have multiple values for the z-index property, I kept the class names short:

.z-1  {--z:1}  .z-2  {--z:2}  .z-3  {--z:3}
.z-4  {--z:4}  .z-5  {--z:5}  .z-6  {--z:6}
.z-7  {--z:7}  .z-8  {--z:8}  .z-9  {--z:9}
.z-10 {--z:10} .z-11 {--z:11} .z-12 {--z:12}
.z-13 {--z:13} .z-14 {--z:14} .z-15 {--z:15}
.z-16 {--z:16} .z-17 {--z:17} .z-18 {--z:18}
.z-19 {--z:19} .z-20 {--z:20} .z-21 {--z:21}
.z-22 {--z:22} .z-23 {--z:23} .z-24 {--z:24}
.z-25 {--z:25} .z-26 {--z:26} .z-27 {--z:27}
.z-28 {--z:28} .z-29 {--z:29} .z-30 {--z:30}
.z-31 {--z:31} .z-32 {--z:32} .z-33 {--z:33}
.z-34 {--z:34} .z-35 {--z:35} .z-36 {--z:36}
.z-37 {--z:37} .z-38 {--z:38} .z-39 {--z:39}
.z-40 {--z:40} .z-41 {--z:41} .z-42 {--z:42}
.z-43 {--z:43} .z-44 {--z:44} .z-45 {--z:45}
.z-46 {--z:46} .z-47 {--z:47} .z-48 {--z:48}
[class^='z-'], [class*=' z-'] {
  z-index:  var(--z);
  position: relative;
}

The code above provides 48 values for property z-index and sets the position property to relative to ensure that property z-index works (any value but static for property position will allow property z-index to work). Again, this won't override the position-property for elements that have classes such as absolute.

You might think to yourself, why not just apply the property z-index in attribute style? Well, you could do that, but then you also need to set property position to anything but static.

It also doesn't look great, in my opinion. It becomes easier to make mistakes. If you decide to use huge numbers as a way of please just work, then one day you feel like setting the value to 999999 and the other day you feel like setting the value to 1337 ... and before you know it, there is an element that overlaps the header when scrolling past it.

I've done something along the lines of that, I admit. In fact, when writing PHP code in WordPress child themes, I still set the priority-parameter to 9001 for some add_action function calls. Luckily, that is only to ensure it's called after all other actions that have been added, so it is not supposed to be surpassed by any reasonable number.

Hiding elements

Let's add a class to hide elements and a class to make elements invisible:

.hide {
  display: none !important;
}
.invisible {
  visibility: hidden !important;
}

The difference between display: none and visibility: hidden is that the latter renders the element, meaning the element is physically still there, and allowing user interaction, yet not visible, while the first completely disables the element.

Preventing user interaction

You can use visibility: hidden in conjunction with pointer-events: none to make sure that a cookie bar is rendered, but yet not visible and interactive until a piece of JavaScript decides to show the cookie notice after checking for a cookie in the browser that indicates that the cookie notice has not been closed yet. The advantage is that the browser has rendered the cookie notice already, allowing for a smoother process of displaying the cookie notice.

.no-pointer-events {
  pointer-events: none !important;
}

These classes get a lot more useful after we've created breakpoint-exclusive versions for in chapter 14 Breakpoint-specific utility classes for responsive layouts, since you might want to hide some elements on smaller devices if those elements aren't necessary.

Creating an advanced layout

I think it's safe to say that you have a good amount of utility classes to be able to create an advanced layout. Let's start with a full-width row of which its contents are aligned with the container, it has a lighter gray background color and the two columns that are inside are aligned at the bottom.

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    
  </div>
  <div class="col">
    
  </div>
</div>

Let's add a row within the first column that contains two columns that have an automatic width. The first column contains a paragraph that's styled like an <h6>-element and is uppercase with the following text content: Company Name.

The second column also contains a paragraph styled like an <h6>-element, but it's not uppercase and it has the following text content: Agency in some country.

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    <div class="row">
      <div class="col w-auto">
        <p class="h6 uppercase">Company Name</p>
      </div>
      <div class="col w-auto">
        <p class="h6">Agency in some country</p>
      </div>
    </div>
  </div>
  <div class="col">
    
  </div>
</div>

Below the row we just added, let's add an <h1>-element with the top margin removed with the following text content: Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum..

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    <div class="row">
      <div class="col w-auto">
        <p class="h6 uppercase">Company Name</p>
      </div>
      <div class="col w-auto">
        <p class="h6">Agency in some country</p>
      </div>
    </div>
    <h1 class="mar-top-0">Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum.</h1>
  </div>
  <div class="col">
    
  </div>
</div>

Now let's add a row with two columns below that <h1>-element and give it a white background and center the items vertically. The first column has a width of 1/7 and the second column has no width specified.

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    <div class="row">
      <div class="col w-auto">
        <p class="h6 uppercase">Company Name</p>
      </div>
      <div class="col w-auto">
        <p class="h6">Agency in some country</p>
      </div>
    </div>
    <h1 class="mar-top-0">Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum.</h1>
    <div class="row bg-white items-center">
      <div class="col w-1//7">
        
      </div>
      <div class="col">
        
      </div>
    </div>
  </div>
  <div class="col">
    
  </div>
</div>

Add an image in the first column, I used this image: https://www.terluinwebdesign.nl/en/wp-content/uploads/2020/04/wordpress-square.svg.

Add an <h2>-element with the following text content to the second column: WordPress websites. Make sure that the <h2>-element looks like an <h3>-element and remove its bottom margin.

Add a row with two columns below the <h2>-element. Both columns have an automatic width. The first column contains a paragraph with the following text content: from 749,-. The second column also contains a paragraph, but it contains a hyperlink with the following text content: View portfolio. You can set the href-attribute to # for the time being.

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    <div class="row">
      <div class="col w-auto">
        <p class="h6 uppercase">Company Name</p>
      </div>
      <div class="col w-auto">
        <p class="h6">Agency in some country</p>
      </div>
    </div>
    <h1 class="mar-top-0">Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum.</h1>
    <div class="row bg-white items-center">
      <div class="col w-1//7">
        <img src="https://www.terluinwebdesign.nl/en/wp-content/uploads/2020/04/wordpress-square.svg">
      </div>
      <div class="col">
        <h2 class="h3 mar-bottom-0">WordPress websites</h2>
        <div class="row">
          <div class="col w-auto">
            <p>from 749,-</p>
          </div>
          <div class="col w-auto">
            <p><a href="#">View portfolio</a></p>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    
  </div>
</div>

Now, duplicate the row with the white background color and change the image to: https://www.terluinwebdesign.nl/en/wp-content/uploads/2021/05/woocommerce-square.svg.

Change the text content of the <h2>-element to WooCommerce webshops and change the price to 1247,- instead of 749,-.

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    <div class="row">
      <div class="col w-auto">
        <p class="h6 uppercase">Company Name</p>
      </div>
      <div class="col w-auto">
        <p class="h6">Agency in some country</p>
      </div>
    </div>
    <h1 class="mar-top-0">Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum.</h1>
    <div class="row bg-white items-center">
      <div class="col w-1//7">
        <img src="https://www.terluinwebdesign.nl/en/wp-content/uploads/2020/04/wordpress-square.svg">
      </div>
      <div class="col">
        <h2 class="h3 mar-bottom-0">WordPress websites</h2>
        <div class="row">
          <div class="col w-auto">
            <p>from 749,-</p>
          </div>
          <div class="col w-auto">
            <p><a href="#">View portfolio</a></p>
          </div>
        </div>
      </div>
    </div>
    <div class="row bg-white items-center">
      <div class="col w-1//7">
        <img src="https://www.terluinwebdesign.nl/en/wp-content/uploads/2021/05/woocommerce-square.svg">
      </div>
      <div class="col">
        <h2 class="h3 mar-bottom-0">WordPress websites</h2>
        <div class="row">
          <div class="col w-auto">
            <p>from 749,-</p>
          </div>
          <div class="col w-auto">
            <p><a href="#">View portfolio</a></p>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    
  </div>
</div>

Now, let's add an image to the second column in the main row that we've added, I've used this image: https://www.terluinwebdesign.nl/wp-content/uploads/2022/08/2I4A5520.jpg.

Make sure that the image has a negative bottom margin of -50px, no border radius at the bottom left, no border radius at the bottom right and no border radius on the top right, and make it move itself out of the container on the right side.

<div class="row full-width content-in-container bg-lighter-gray items-bottom">
  <div class="col">
    <div class="row">
      <div class="col w-auto">
        <p class="h6 uppercase">Company Name</p>
      </div>
      <div class="col w-auto">
        <p class="h6">Agency in some country</p>
      </div>
    </div>
    <h1 class="mar-top-0">Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum.</h1>
    <div class="row bg-white items-center">
      <div class="col w-1//7">
        <img src="https://www.terluinwebdesign.nl/en/wp-content/uploads/2020/04/wordpress-square.svg">
      </div>
      <div class="col">
        <h2 class="h3 mar-bottom-0">WordPress websites</h2>
        <div class="row">
          <div class="col w-auto">
            <p>from 749,-</p>
          </div>
          <div class="col w-auto">
            <p><a href="#">View portfolio</a></p>
          </div>
        </div>
      </div>
    </div>
    <div class="row bg-white items-center">
      <div class="col w-1//7">
        <img src="https://www.terluinwebdesign.nl/en/wp-content/uploads/2021/05/woocommerce-square.svg">
      </div>
      <div class="col">
        <h2 class="h3 mar-bottom-0">WordPress websites</h2>
        <div class="row">
          <div class="col w-auto">
            <p>from 749,-</p>
          </div>
          <div class="col w-auto">
            <p><a href="#">View portfolio</a></p>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="col">
    <img src="https://www.terluinwebdesign.nl/wp-content/uploads/2022/08/2I4A5520.jpg" class="mar-bottom-n1 border-rad-bottom-left-0 border-rad-bottom-right-0 border-rad-top-right-0 out-container-right">
  </div>
</div>

The result should look like this:

First main row

Let's add another row with two columns below this row. In the first column, add an image that has a negative top margin of -50px, is moved out of the container on the left side, has no border radius on the top right, no border radius on the bottom left, and no border radius on the top left. This is the image I used: https://www.terluinwebdesign.nl/en/wp-content/uploads/tw-img/2022/08/website-webshop-and-hosting-with-1-partner-w1691.jpg.webp.

<div class="row">
  <div class="col">
    <img src="https://www.terluinwebdesign.nl/en/wp-content/uploads/tw-img/2022/08/website-webshop-and-hosting-with-1-partner-w1691.jpg.webp" class="mar-top-n1 out-container-left border-rad-top-right-0 border-rad-bottom-left-0 border-rad-top-left-0">
  </div>
  <div class="col">
  </div>
</div>

Add an <h2>-element to the second column with the following text content: Lorem ipsum dolor sit amet, consectetuer.

Add a paragraph to the second column with the following text content: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae..

Add an <h2>-element to the second column with the following text content: Lorem ipsum dolor sit amet, consectetuer.

Add a paragraph to the second column with the following text content: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim..

Add a row with three columns to the second column. Give the row a top margin of 25px. Give the first column in the row a lighter gray background color, a top padding of 12.5px and a bottom padding of 12.5px.

Inside of that column, add a row with two columns that are vertically centered. Give the first column of that row a width of 3/13. In that first column, add an image without a border radius. This is the image I used: https://www.terluinwebdesign.nl/en/wp-content/themes/terluin-webdesign/access/img/tw-magnifying-glass.svg?c=%23004578

In the second column of that row, add an <h3>-element that looks like an <h6>-element with the following, centered, text content: SEO-ready. Now, copy the row that has the lighter gray background color and paste it twice to overwrite the other two columns within that row.

Change the image in the first duplicate column to: https://www.terluinwebdesign.nl/en/wp-content/themes/terluin-webdesign/access/img/tw-stopwatch.svg?c=%23004578. Change the text content of the first duplicate column to: Performance.

Change the image in the secon