CSS variable and :root with media query
Traditional Approach
When defining custom styles, we traditionally rely on media queries to style the elements for different viewports. The styles among different viewport differs for font-size
, margin
and padding
usually. Also, the layout between mobile and above would differ.
h1 {
font-size: 2.5rem;
line-height: 5rem;
}
h2 {
font-size: 1.6rem;
line-height: 3.2rem;
}
main,
aside {
padding: 0.5rem;
}
@media screen and (min-width: 550px) {
h1 {
font-size: 3rem;
line-height: 6rem;
}
h2 {
font-size: 2rem;
line-height: 4rem;
}
main,
aside {
padding: 1rem;
}
}
Introducing :root
The :root
CSS pseudo-class matches the root element of a tree representing the document.
NOTE: In HTML, :root
represents the <html>
element and is identical to the selector html, except that its specificity is higher.
:root {
background: yellow;
}
Introducing CSS variables (Custom Properties)
Property names that are prefixed with --
, like --font-size
, represent custom properties that contain a value that can be used in other declarations using the var()
function.
NOTE: Custom properties are scoped to the element(s) they are declared on
.
We could define the CSS variable as follows:
h1 {
--font-size: 3rem
font-size: var(--font-size);
line-height: calc(var(--font-size) * 2)
}
h2 {
--font-size: 2rem
font-size: var(--font-size);
line-height: calc(var(--font-size) * 2)
}
For global variables, :root
is where we define the CSS variables.
:root {
--font-size-1: 3rem;
--font-size-2: 2rem;
}
h1 {
font-size: var(--font-size-1);
line-height: calc(var(--font-size-1) * 2);
}
h2 {
font-size: var(--font-size-2);
line-height: calc(var(--font-size-2) * 2);
}
Using CSS variables and :root with media queries
Taking the first example here, we could do the same using CSS variables and :root
. Here instead of defining media queries on element level, we are adding it to the variables that are used within elements:
:root {
--font-size-1: 2.5rem;
--font-size-2: 1.6rem;
--padding-base: 0.5rem;
}
@media screen and (min-width: 550px) {
:root {
--font-size-1: 3rem;
--font-size-2: 2rem;
--padding-base: 1rem;
}
}
h1 {
font-size: var(--font-size-1);
line-height: calc(var(--font-size-1) * 2);
}
h2 {
font-size: var(--font-size-2);
line-height: calc(var(--font-size-2) * 2);
}
main,
aside {
padding: var(--padding-base);
}
Another example for layout styles
Say you want to style a container whose elements needs to be laid out in columns while viewed on larger screen, otherwise on mobile it should be laid out as rows.
<div class="tiles grid-box">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
<div class="cards grid-box">
<div class="card"></div>
<div class="card"></div>
<div class="card"></div>
<div class="card"></div>
</div>
In the example above, you could have added styles as follows to the .grid-box
class thats shared between these list item components .tiles
and .cards
.
.grid-box {
display: grid;
grid-template-columns: 1fr;
}
@media screen and (min-width: 550px) {
.grid-box {
grid-template-columns: 1fr 1fr;
}
}
@media screen and (min-width: 992px) {
.grid-box {
grid-template-columns: 1fr 1fr 1fr;
}
}
Depending on your use case, you could do it in :root
to make styles cleaner and simpler.
:root {
--grid-template-columns: 1fr;
}
@media screen and (min-width: 550px) {
:root {
grid-template-columns: 1fr 1fr;
}
}
@media screen and (min-width: 992px) {
:root {
grid-template-columns: 1fr 1fr 1fr;
}
}
.grid-box {
display: grid;
grid-template-columns: var(--grid-template-columns);
}
Let me know what style do you prefer and why.
CSS Variable scope
Remember that CSS Variables (custom properties) are scoped to the element(s) they are declared on
. So, its not just :root
on which variables can be defined globally. You could define variables for an element and its decendents. Here is an example:
<body>
<div class="content">
<div class="tiles">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
</div>
</body>
Considering the above template, we can define few custom properties to be shared globally on :root
. And then few custom properties that needs to be shared among many parts of the page, can be moved to the parent of those elements.
:root {
--root-content-width: 96vw;
--padding-base: 1rem;
--padding-base-2x: calc(var(--padding-base) * 2);
}
/* Layout */
.content {
--tiles-columns-count: 1;
--tiles-columns-gap: 0px;
--content-width: calc(var(--root-content-width) - var(--padding-base-2x));
--tiles-width: calc(var(--content-width) - var(--tiles-columns-gap));
--tile-width: calc(var(--tiles-width) / var(--tiles-columns-count));
}
@media screen and (min-width: 550px) {
.content {
--tiles-columns-count: 2;
--tiles-columns-gap: var(--grid-column-gap);
}
}
@media screen and (min-width: 992px) {
.content {
--tiles-columns-count: 3;
--tiles-columns-gap: calc(2 * var(--grid-column-gap));
}
}
.tiles {
display: grid;
column-gap: var(--grid-column-gap);
row-gap: var(--padding-base-2x);
grid-template-columns: repeat(var(--tiles-columns-count), var(--tile-width));
}