I like to think of CSS as a conditional design language. Over the years, CSS was known as a way to style web pages. Now, however, CSS has evolved a lot to the point you can see conditional rules. The interesting bit is that those CSS rules aren’t direct (i.e: there is still no if/else in CSS), but the way features in CSS work is conditional.
Design tools like Figma, Sketch, and Adobe XD made a huge improvement for us designers, but they still lack a lot of the flexibility that CSS has.
In this article, I will go over a few CSS features that we use every day, and show you how conditional they are. In addition to that, I will compare a few examples where CSS is much more powerful than design tools.
What is conditional CSS?
In simple words, it’s about design that has certain conditions. When one or more conditions are met, the design is subject to change due to that.
For example, adding a new section to a design must push the other elements underneath it. In the following figure, we have a stack of items on the left. When adding a new one, the other items below it must move down.
Logically, that sounds expected and normal. In design tools, we got this a few years ago. In Figma, we have “Auto Layout” features that do the above. On the web, we have had that from day 1, even without CSS at all.
Conditional CSS
You might be thinking about what the heck conditional CSS is. Is that even a thing? No, there hasn’t been a direct “if” statement in CSS.
The main thing to distinguish is that some CSS properties work in specific conditions or scenarios. For example, when using the CSS :empty
selector to check if an element is empty or not, it’s a conditional pseudo selector.
.alert p:empty {
display: none;
}
If I want to explain the above to my 2 years old daughter, I will do it like this:
If there is nothing here, it will disappear.
Did you notice the if statement here? This is conditional design indirectly. In the following section, I’m going to explore a few CSS features which work similarly to an if/else statement.
The goal? To have a stronger idea and expectation about the CSS you wrote. I mean, you will be able to spot conditional CSS by just looking at the CSS for a component, a section, or a page.
CSS versus Figma
Why Figma? Well, I consider it as the standard for UX design these days, I thought it’s a good idea to do my comparison based on it. I want to share a simple example. There is list of tags that are displayed horizontally.
When you think deeply about it, you will spot some major differences. For example, the CSS version:
- Can wrap into a new lines if there is no enough space.
- Works with both LTR and RTL directions.
- The
gap
will be used for rows when the items wrap.
Figma doesn’t have any of the above.
In CSS, there are three conditional rules happening:
- If
flex-wrap
is set towrap
, then the items can wrap when there is no available space. - When the items wrap into a new line, the
gap
will work for the horizontal and vertical spaces. - If the page direction is RTL (right-to-left), the items will switch their order (e.g: design will be the first one from the right).
This is just one example, and I can write a book like that. Let’s explore a few cases where CSS can be conditional.
Conditional CSS examples
Media query
We can’t talk about conditional CSS without mentioning CSS media queries. The CSS spec is named CSS Conditional Rules Module. To be honest, this is the first time that I learn about that title.
When I did my research about who asks or mentions “Conditional CSS”, I found more than one time that media queries are the closest thing to an “if” statement in CSS.
.section {
display: flex;
flex-direction: column;
}
@media (min-width: 700px) {
.section {
flex-direction: row;
}
}
If the viewport width is 700px or larger, change the
flex-direction
of.section
tocolumn
. That’s explicit if statement, isn’t it?
The same thing can apply to media queries like @media (hover: hover)
. In the following CSS, the hover style will be applied only if the user is using a mouse or a trackpad.
@media (hover: hover) {
.card:hover {
/* Add hover styles.. */
}
}
Size container query
With container queries, we can check if the parent of a component has a specific size and style the child component accordingly.
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
align-items: center;
}
}
I have written about container queries multiple times, and have a place where I share demos about it.
Style container query
At the time of writing this article, this is behind a flag in Chrome Canary and is intended to ship in Chrome stable.
With a style query, we can check if a component is placed within a wrapper that has a specific CSS variable and if yes, we style it accordingly.
In the following figure, we have an article body that is coming from a CMS. We have a default style for the figure and another style that looks featured.
To implement that with style queries, we can style the default one, and then check if the figure has a special CSS variable to allow the custom styling.
figure {
container-name: figure;
--featured: true;
}
/* Featured figure style. */
@container figure style(--featured: true) {
img {
/* Custom styling */
}
figcaption {
/* Custom styling */
}
}
And if --featured: true
isn’t there, we will default to the base figure design. We can use the not keyword to check when the figure doesn’t have that CSS variable.
/* Default figure style. */
@container figure not style(--featured: true) {
figcaption {
/* Custom styling */
}
}
That’s an if statement, but it’s implicit.
Another example is having a component styled differently based on its parent. Consider the following figure:
The card style can switch to dark if it’s placed within a container that has the --theme: dark
CSS variable.
.special-wrapper {
--theme: dark;
container-name: stats;
}
@container stats style(--theme: dark) {
.stat {
/* Add the dark styles. */
}
}
If we read the above, it feels like:
If the container stats have the variable
--theme: dark
, add the following CSS.
CSS @supports
The @supports
feature lets us test if a certain CSS feature is supported in a browser or not.
@supports (aspect-ratio: 1) {
.card-thumb {
aspect-ratio: 1;
}
}
We can also test for the support of a selector, like :has
.
@supports selector(:has(p)) {
.card-thumb {
aspect-ratio: 1;
}
}
Flexbox wrapping
According to MDN:
The flex-wrap CSS property sets whether flex items are forced onto one line or can wrap onto multiple lines. If wrapping is allowed, it sets the direction in that lines are stacked.
The flex-wrap
property allows flex items to wrap into a new line in case there is not enough space available.
Consider the following example. We have a card that contains a title and a link. When the space is small, each child item should wrap into a new line.
.card {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.card__title {
margin-right: 12px;
}
That sounds like a conditional thing to me. If no available space, wrap into a new line(s).
When each flex item wraps into a line, how do I manage the spacing between the flex items, you asked? Currently, there is a margin-right
on the heading, and when they are stacked, that should be replaced by margin-bottom
. The problem is we don’t know when the items will wrap because it depends on the content.
The good thing is that the spacing can be conditional with the gap
property. When they are in the same line, the spacing is horizontal, and with multiple, the spacing is vertical.
.card {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
}
This is one of my favorite flexbox features. Here is a visual o