The CodyHouse Framework relies on CSS variables and em
units for a reason: when combined, they allow you to set (smart) responsive rules. These rules represent global controls: editing them means affecting the whole responsive scale, with (almost) no need to set media queries on a component level.
π First time you hear about the CodyHouse Framework?
Responsive typography #
Let's start from typography! In the π custom-style/_typography.scss file of the CodyHouse framework, we set the type scale (π pssst...we have a tool to generate custom type scales). The reason why a modular scale is useful when applied to anything in a design system is that it generates a set of harmonious values, as opposed to setting each value independently.
To control the scale, we’ve defined two variables: the --text-base-size
and the --βtext-scale-ratio
. The first one is the body font size, while the second one is the ratio used to generate the scale. The default value of --text-base-size
is 1em.
When applied to the <body>
, 1em equals to 16px in most modern browsers. Since our framework is mobile-first, we're setting the body text size equal to 16px on smaller screens.
Here's the type scale:
:root {
/* body font size */
--text-base-size: 1em;
/* type scale */
--text-scale-ratio: 1.2;
--text-xs: calc((1em / var(--text-scale-ratio)) / var(--text-scale-ratio));
--text-sm: calc(var(--text-xs) * var(--text-scale-ratio));
--text-md: calc(var(--text-sm) * var(--text-scale-ratio) * var(--text-scale-ratio));
--text-lg: calc(var(--text-md) * var(--text-scale-ratio));
--text-xl: calc(var(--text-lg) * var(--text-scale-ratio));
--text-xxl: calc(var(--text-xl) * var(--text-scale-ratio));
--text-xxxl: calc(var(--text-xxl) * var(--text-scale-ratio));
}
body {
font-size: var(--text-base-size);
}
Note that in defining each text size variable we multiply 1em by the --text-scale-ratio
. That 1em is not the --text-base-size
value. You could set a --text-base-size
different from 1em (while you shouldn’t change the 1m in the calc() function).
Since the Em unit is a relative unit equal to the current font size, if we update the --text-base-size
variable at a specific media query, we update the font-size of the body, and, with a cascade effect, all the text size variables. The whole typography is affected.
@supports(--css: variables) {
@include breakpoint(md) {
:root {
--text-base-size: 1.25em;
}
}
}
The paragraph elements inherit the base font size, while we can set a specific font size for each heading element. Besides, in our framework we create utility classes in case, for example, we want to apply the --text-xxl
font size to an element that is not an <h1>
.
body {
font-size: var(--text-base-size);
}
.text-xxxl {
font-size: var(--text-xxxl);
}
h1, .text-xxl {
font-size: var(--text-xxl);
}
h2, .text-xl {
font-size: var(--text-xl);
}
h3, .text-lg {
font-size: var(--text-lg);
}
h4, .text-md {
font-size: var(--text-md);
}
.text-sm, small {
font-size: var(--text-sm);
}
.text-xs {
font-size: var(--text-xs);
}
Why including the type scale in your CSS? In one word: control.
Say we want to increase the body font size at a specific breakpoint; for example, we increase the --text-base-size
to 1.25em past 1024px. The heading elements are all affected by the change (the 1em in the calc() function is no longer ~16px, but ~20px); therefore they all become bigger. Let’s suppose we feel like increasing the size of the <h1>
element even more. How do we do that?
One option would be increasing the --text-base-size
value, but it’s not ideal if I want to target only the heading elements, preserving the size of the body text. Here’s the advantage of storing the --text-scale-ratio
in a variable. We can edit it and affect everything but the body text:
@supports(--css: variables) {
@include breakpoint(md) {
:root {
--text-scale-ratio: 1.25;
}
}
}
With this technique, you can manage the size of all your text elements by editing only two variables. Besides, you can take advantage of the Em unit and modify all margins, paddings, and spacing as a consequence of editing the --text-base-size
at a root level.
Responsive spacing #
Similarly to how we did with typography, in our framework we include the spacing scale:
:root {
--space-unit: 1em;
--space-xxxxs: calc(0.125 * var(--space-unit));
--space-xxxs: calc(0.25 * var(--space-unit));
--space-xxs: calc(0.375 * var(--space-unit));
--space-xs: calc(0.5 * var(--space-unit));
--space-sm: calc(0.75 * var(--space-unit));
--space-md: calc(1.25 * var(--space-unit));
--space-lg: calc(2 * var(--space-unit));
--space-xl: calc(3.25 * var(--space-unit));
--space-xxl: calc(5.25 * var(--space-unit));
--space-xxxl: calc(8.5 * var(--space-unit));
--space-xxxxl: calc(13.75 * var(--space-unit));
}
The --space-unit
is equal to 1em, while the modular scale is based on the Fibonacci sequence (with a small tweak).
By setting the --space-unit
equal to 1em, we're creating a bond between typography and spacing. In the previous chapter, we explained how the --text-base-size
variable controls the whole type system. If you increase its value at a specific breakpoint, all the text size variables change accordingly.
@supports(--css: variables) {
@include breakpoint(md) {
:root {
--text-base-size: 1.25em;
}
}
}
Since the spacing unit is equal to 1em, and all other spacing values are multipliers of the unit value, when we update the --text-base-size
variable, we affect the spacing as well!
Here's the effect of updating a single CSS custom property:
No additional media queries needed so far! All you have to do is using the spacing variables to set paddings and margins on a component level:
.header__top {
background: var(--color-contrast-higher);
padding: var(--space-sm);
text-align: center;
a {
color: var(--color-white);
@include fontSmooth;
}
}
.header__main {
border-bottom: 1px solid var(--color-contrast-low);
padding-top: var(--space-sm);
padding-bottom: var(--space-sm);
background: var(--color-bg);
}
.header__nav {
ul {
display: flex;
}
li {
margin-right: var(--space-md);
&:last-child {
margin-right: 0;
}
}
}
What if you want to update all spacing values at once, without having to change the --text-base-size
variable? You can update the --space-unit
variable:
:root {
--space-unit: 1em;
--space-xxxxs: calc(0.125 * var(--space-unit));
--space-xxxs: calc(0.25 * var(--space-unit));
--space-xxs: calc(0.375 * var(--space-unit));
--space-xs: calc(0.5 * var(--space-unit));
--space-sm: calc(0.75 * var(--space-unit));
--space-md: calc(1.25 * var(--space-unit));
--space-lg: calc(2 * var(--space-unit));
--space-xl: calc(3.25 * var(--space-unit));
--space-xxl: calc(5.25 * var(--space-unit));
--space-xxxl: calc(8.5 * var(--space-unit));
--space-xxxxl: calc(13.75 * var(--space-unit));
}
@supports(--css: variables) {
@include breakpoint(md) {
:root {
--space-unit: 1.25em;
}
}
}
Conclusion #
It is true that by embracing a method like this one you lose some of your “visual” control, but it’s in favor of simplicity and maintainability. When all your components are connected (thanks to relative units), making changes becomes a pain-free process (you know what I mean π ). Accepting that we shouldn't control every single pixel of the page using plenty of media queries is the way that leads to simplifying the process of managing our web projects.