Relative Units in CSS: A Comprehensive Guide

Reading time

~ 7 min read
Tags

css
Author

Mustapha Aouas
Relative Units in CSS: A Comprehensive Guide

In the ever-evolving world of web development, creating flexible, responsive, and accessible designs is crucial. One of the most powerful tools in a developer's CSS toolkit is the use of relative units. These units allow our designs to be more flexible, responsive, and accessible. In this article, we'll dive deep into the world of relative units, exploring how to use them effectively and why they're so crucial in modern web development.

Why Does It Matter?

Before we delve into the specifics, let's understand why relative units are so vital in modern web development:

Let's explore the various types of relative units and how to use them effectively.

Ems and Rems

The two most commonly used relative units are em and rem. Both are based on font size, but they behave differently in important ways.

Understanding Ems

An em is a unit relative to the font size of the element on which it's used. Here's a detailed example:

.parent {
  font-size: 16px;
}

.child {
  font-size: 1.5em;      /* 24px (16px * 1.5) */

  padding: 1em;          /* 24px */
  border-radius: 0.25em; /* 6px */
}

In this example, all the child element's properties are relative to its own font size. This creates a scalable component where all properties maintain their proportions if the font size changes.

Typically, ems are used for setting font sizes, but as you saw in the example above, their potential extends far beyond that. By using ems, you can define many properties of an element and then easily scale the entire thing up or down with a single change to the font size. This flexibility makes ems incredibly useful for various properties, not just fonts.

A Potential Pitfall

Ems can be tricky when used for defining font sizes of nested elements, as they compound. Let's look at an example:

<section>
<ul>
  <li>First level item</li>
  <ul>
    <li>Second level item</li>
    <ul>
      <li>Third level item</li>
      <ul>
        <li>Fourth level item</li>
      </ul>
    </ul>
  </ul>
</ul>
</section>

Now, let's apply some CSS using em:

section {
  font-size: 16px;
}
ul {
 font-size: 0.9em;
}

This will result in this:

img

Let's break down what happens at each level:

As you can see, the font size gets progressively smaller with each nested level. This compounding effect can lead to text becoming unreadably small in deeply nested structures. This is both a feature and a potential pitfall, depending on your design needs.

Rems: Root Ems

To avoid the compounding issue with em, we have rem units. These are always relative to the root element's font size (usually the <html> element). Let’s suppose for the next examples, the current font-size of the root element is 16px:

.deeply-nested-element {
  font-size: 1.5rem; /* Always 24px, regardless of nesting */
  padding: 1rem; /* Always 16px */
  margin: 0.5rem; /* Always 8px */
}

Rems provide consistency across your document, making them ideal for font sizes and many other properties!

Practical Example

Let's create an accessible button component that scales nicely using a combination of em and rem:

.button {
  font-size: 1rem; /* 16px (or the prefered size set by the user) */

  padding: .5em 1em; /* 8px 16px, using em to scale with the current element font-size */
  border-radius: .25em; /* 4px */
  transition: font-size .3s ease, padding .3s ease, border-radius .3s ease;
}

@media (prefers-reduced-motion: no-preference) {
  .button:hover {
    font-size: 1.1rem; /* Grows to 17.6px on hover */
  }
}

In this example, the button's padding and border-radius will scale proportionally if the user changes the font size of the html document :)

Viewport-Relative Units

While ems and rems are incredibly useful, sometimes we need units that are relative to the size of the viewport (screen). That's where viewport-relative units come in:

These units are particularly useful for creating full-screen layouts or elements that need to scale dramatically with the viewport size.

.hero-section {
  height: 100vh;
  width: 100vw;
  /* ... */
}

.hero-section-image {
  width: 50vmin;
  height: 50vmin;
  object-fit: cover;
  /* ... */
}

This creates a full-height hero section with content that scales based on the viewport size, and an image that's always square and takes up half of the smaller viewport dimension.

While these units are great, they were originally designed with desktop browsers in mind, where the viewport size remains constant unless the user manually resizes the window. On mobile, it’s an other story.

The Dynamic Nature of Mobile Viewports

Many mobile browsers implement a user experience feature where the browser's UI elements, such as the address bar and navigation buttons, can appear or disappear based on user interaction. Here's how it typically works:

This dynamic behavior creates a fluctuating viewport size, which can lead to layout issues when using viewport-relative units like vh (for overlays, side-bars, etc…).

Enter Dynamic Viewport Units

To address these issues, new viewport units were introduced:

Image description

We have the same properties for width, and for smaller and larger dimension (width or height): s(vh | vw | vmin | vmax) l(vh | vw | vmin | vmax) d(vh | vw | vmin | vmax)

Consider this example:

.side-menu {
  height: 100dvh;
}

This element will dynamically adjust its height based on the current state of the mobile browser's UI elements, providing a more consistent user experience 👍.

Do not use dvh for scrollable sections: Imagine scrolling past multiple full-screen sections sing 100dvh, then scrolling up slightly. The content suddenly jumps waaaaay up. This can cause major lag and it forces the browser to recalculate the entire page layout, which can hurt performance (layout thrashing).

Inline / Block Logical Properties

To ensure completeness, it’s important to mention an additional set of unit types: inline and block. These are known as "logical properties”. They function similarly to width and height but are designed to adapt to vertically-written languages like Mongolian, effectively transposing their behavior. These are vi , vb , svi , svb , lvi , lvb , dvi , dvb.

Browser Support and Fallbacks

Although support for dynamic viewport units is 93.21% (at the time of writing), you might want to provide fallbacks if you target older browsers:

.modal {
  height: 100vh;  /* Fallback for browsers that don't support dvh */
  height: 100dvh; /* Will be used by browsers that support it */
}

Combining Units

Using calc Function

The calc() function is a game-changer for responsive design. It allows you to perform basic math operations with different units, making it easier to create flexible layouts. Here's what you need to know:

Example:

p {
  font-size: calc(0.5em + 1svw);
}

In this example, the font size will scale with the viewport width but will never get smaller than 0.5rem.

Note: Always include em or rem units when using viewport units for font sizes. This ensures user font preferences are respected.

Using clamp Function

The clamp() function takes this concept even further, allowing you to set a preferred value with a minimum and maximum. clamp() takes three arguments:

Have a look at this example:

h1 {
  font-size: clamp(1em, 5vw, 6em);
}

This sets a font size that scales with the viewport width but is never smaller than 1em or larger than 6em.

Using Other Useful Functions

Wrapping up

Mastering relative units in CSS is a game-changer for creating flexible and accessible designs. By understanding the nuances of ems, rems, viewport units, and how to combine them effectively, you can create layouts that adapt seamlessly to different screen sizes and user preferences.

The introduction of dynamic viewport units like dvh represents an important step in adapting web design to the unique challenges of mobile browsers. By using these units, you can create responsive layouts that handle the dynamic nature of mobile viewports.

However, the goal is not to eliminate pixels entirely, but to use them judiciously. Absolute units like pixels still have their place, especially for borders or when you need pixel-perfect control. The key is knowing when to use relative units for flexibility and when to use absolute units for precision.

As you build your next project or your next UIs, challenge yourself to use relative units wherever possible. You'll likely find that your stylesheets become more concise, your layouts more flexible, and your overall design more robust across different devices and user settings.

Until next time, happy coding!