We are developers. We want to develop the most responsive layouts. We want to accommodate different devices, resolutions and user settings. We want to use consistent units across all stylesheets. We want to do as little math as possible.
When do we want this? NOW!
What do we need to know? Lots of stuff!
How do we do this? With a custom SASS function!
By using a SASS function, we can accommodate more users by understanding and accounting for their system and browser-level settings. I will go over how rem values relate to pixel values and what might affect the relation between them. 1rem almost always starts off as 16px but there are ways a user can change that. This SASS function we’ll write later is very helpful because it can apply to a larger project and it can be introduced incrementally into your existing projects.
Background
What got me looking into this topic was the article There’s No Such Thing as a Desktop Screen, Hajime Yamasaki Vukelic, where his take on responsive design is that “responsiveness is not about a simple set of arbitrary screen widths.... responsiveness is about robustness: how much can you stretch or squeeze the page before it starts falling apart”. Hajime talks about how his father is browsing the web at essentially 400% regular scale. While this may seem like an edge case, it’s important to know the different ways a user may scale their display and how the styles we’re writing are affected by these changes in settings.
Scaled display
Let’s start as small as possible with understanding exactly what a pixel is. As far as a developer is concerned, there are two types of pixels: there are device pixels which are the amount of dots of light on a given screen and there are CSS pixels which are a unit of measurement. Device pixels usually do not equal CSS pixels. It’s important to understand the difference between the two so we can figure which settings affect each value.
There are several ways that a user can change the size of the content on their screen like Hajime’s father. The most common ways that a user could scale their display:
- changing the display resolution (system setting)
- zooming in on the browser tab (on Mac pressing
⌘ and +
, windowsCtrl and +
) - increasing the font size in the browser’s settings.
The first and second options, changing the display resolution and zooming in the browser, essentially do the same thing. Both methods will scale the CSS pixels so that each CSS pixel takes up more device pixels. In this case, our layout all scales proportionally. 1rem will still equal 16px but each CSS pixel takes up more device pixels. To test this, you can zoom in on the browser tab until you trigger the “mobile” layout. This setting is quick to test and generally shouldn’t cause much issue.
Changing the browser font size will cause the most change. By default, the browser’s setting is “medium” which in Chrome means 1rem is 16px. When the user increases the font size, the value of 1rem will increase but no other values will scale. 1rem will equal more CSS pixels and therefore take up more device pixels. On Chrome, with the font size set to “very large”, 1rem will equal 24px. The size of the CSS pixels remain the same, it is the root font size that has changed. Any value that references the root font size will be affected.
In your code, If you have a mix of pixel and rem values then you might start to have layout issues since the rem values will scale but the pixel values will stay the same. The image above shows how a simple example of how drastically the layout can change when the text is set in rem values while the max-width, column width and paddings are set with pixel values. A responsive layout should scale accordingly with the root font size.
The problem with media queries
This is generally how we write a media query:
@media (min-width: 1000px) {
...declarations here
}
Since we’re trying to be responsive and adaptable to all of the users’ settings, we want to use relative units all everywhere that we can. Let’s use a rem value instead of pixels in the media query:
@media (min-width: 62.5rem) {
...declarations here
}
With the default browser settings, 1000px will equal 62.5rem
1rem = 16px
1000px / 16px/rem = 62.5rem
That looks like math I have to do every time I want to write a relative unit. We said right at the start that we don’t want to have to do math.
There is a common fix that we’ve all seen where we make 1rem = 10px. This is generally done while setting up the boilerplate of a project. You overwrite the root font size on the root element by targeting the html
selector:
html {
font-size: 62.5%
// this is math
// but it only needs to be done once for the whole project
// so I'll let it slide
}
Now any time we want to convert a pixel value from the design to a rem value in our code we just have to carry the decimal one place.
1rem = 10px
To get a value of 1000px, we use 100rem. This still involves math but is fairly minor.
Now, when using a rem value, we can safely assume that 1rem will be 10px.
Right?
Yes?
Example?
No need. We know it is true. We have used rem for widths, heights, paddings, margins, translates or any other declaration without any issue. But…will there be any issue using it in the condition for a media query?
@media (min-width: 100rem) {
// does is trigger at 1000px as expected?
...declarations here
}
Open the example in a new window and play with the width of the viewport. When the “desktop” media query triggers then the background will turn blue.
What is happening?
The values for a media query are calculated before any other rules are set, so the ruleset we applied to the html
element in the start of our project will not be applied in the condition of the media query. According to this media query, 1rem is still equal to 16px. We expected 100rem to be 1000px but it ended up being 1600px.
If you change the browser font size to “very large” and refresh the example, you will notice that 1rem = 15px and the 100rem media query won’t trigger until 2400px. If your monitor isn’t wide enough to see that change happen, zoom out on the browser with ⌘/Ctrl and + .
The condition for the media query is not scaling consistently with the browser’s desired font size.
- Font size “Medium”:
- 1rem = 10px
- Media query: 100rem = 1600px
- rem to px ratio: 0.0625
- Font size “Very Large”:
- 1rem = 15px
- Media query: 100rem = 2400px
- rem to px ratio: 0.0416666667
For content and layouts to scale consistently, we need the rem to px ratio to always remain the same.
REMPX()
This has taken a long time to get to even suggesting a solution to a problem that we maybe didn’t know we had.
Let’s create a custom SASS function that will allow us to write our code in pixel values and be rendered as rem values. I came across this function on a legacy project at work but I believe it was heavily inspired by this quick article: Convert pixel values to rem, Sass style, by Bhargav Shah. Since this article was written, CSS introduced its own rem()
function which calculates the remainder of a fraction so instead we’ll name our function rempx()
.
$html-font-size: 16px;
@function stripUnit($value) {
@return $value / ($value * 0 + 1);
}
@function rempx($pxValue) {
@return #{stripUnit($pxValue) / stripUnit($html-font-size)}rem;
}
//implementation:
.image {
height: rempx(300px);
}
Open the example in a new window and play with the width of the viewport and change the browser font size (refresh the page after you update the font size).
Notice how after we change the browser font size to “very large” (after a page a refresh), the 1rem square becomes larger and you can see that 1rem is equal to 24px.
When used in the condition of a media query, a rempx
value will scale accordingly with the browsers font size. With the font size set to “very large”, the desktop layout rempx(1000px)
will trigger at the viewport width of 1500px. The scaling of the content and layout behave the same way as when we zoom in on the browser.
A huge benefit of writing all your units with the rempx()
function is you can write pixel values from the designs and then it renders it as a rem value in the browser. This function is very easy to introduce to a project or include in the boilerplate of your future projects.
Wrapping up
This function can be written once and used everywhere.
We can take the pixel values from the design and generate a scalable rem value.
Our media query triggers relative to the root font size.
All our layout and content scales consistently.
No math necessary.
Better user experience across a wider range of user settings.
Better user existence overall.
Better developer existence overall.
This function improves our existence.
Further reading
There’s No Such Thing as a Desktop Screen Hajime Yamasaki Vukelic
Zoom, zoom, and zoom: the three types of browser (and CSS!) magnification, Miriam Suzanne
Convert pixel values to rem, Sass style, Bhargav Shah
Top comments (0)