Using Boolean logic to conditionally apply CSS rules, no-JS
Acknowledgements
Before we begin, I must thank Lea Verou for her talk on CSS Variable Secrets at CSS Day 2022 that inspired this post, and for the slide deck she kindly made available.
In Lea’s talk, she highlights some of the lesser-known capabilities of CSS Custom Properties and reveals some future enhancements. On slide 58 Lea mentioned how Logical Operations could be performed to conditionally apply styling using some simple arithmetic. In this post I plan to explore this concept in a little more depth.
Introduction
There are three fundamental logical operations: NOT, AND and OR, all of which operate on Boolean values TRUE or FALSE. However, with the possible exception of the checked pseudo class, CSS does not support Boolean operations well. In order for us to overcome this limitation we will use Binary values (1/0) instead of the Boolean values (TRUE/FALSE) respectively.
We will explore the mechanics of how we can simulate the logical operations using Custom Properties and demonstrate how the values 1 and 0 will help us apply styling. Before that we will briefly confirm our understanding of the actual Boolean operations as can be found in most programming languages such as JavaScript.
NOT Operation (“!” in JavaScript)
The NOT operation takes a single input value and inverts it. True becomes False, False becomes True, or in our case, an input of 1 results in an output of 0, and 0 in becomes 1 out.
AND Operation (“&&” in JavaScript)
Both AND and OR take two (or more) inputs and produce a single output. With the AND operation a True is only output when all inputs are True, otherwise the output is False. With our ‘functions’ using just two inputs, only when input-A AND input-B are 1 will 1 be output.
OR Operation (“||” in JavaScript)
The OR operation produces an output of TRUE if any input is TRUE, only FALSE if all inputs are FALSE. The following truth table illustrates the simple behaviour.
CSS Logical Operations
We will need some additional values to support our operations, defined within the context of CSS classes, as follows.
Boolean (Binary) values
Define the Boolean values True and False as 1 and 0.
.not-operation, .and-operation, .or-operation {
--TRUE: 1;
--FALSE: 0;
}
Default input values
Prepare input values for the operations.
.not-operation, .and-operation, .or-operation {
--INPUT: var(--TRUE);
--INPUT-A: var(--FALSE);
--INPUT-B: var(--TRUE);
}
Logical Operations
Using the following three basic operations there are many other operations that can be derived such as NAND (NOT AND), NOR (NOT OR) and XOR etc.
NOT
Using numeric values in place of Boolean values, the NOT operation can be achieved by subtracting the input (I) from 1. When I = 1, 1 - 1 = 0, when I = 0, 1 - 0 = 1.
.not-operation {
--OUTPUT: calc(1 - var(--INPUT));
}
AND
Again using 1’s and 0’s, the AND operation is achieved through the multiplication of the two inputs (I1 and I2).
Multiplying and value by zero results in zero so only when all (both) inputs are one can the output be one.
.and-operation, {
--OUTPUT: calc(var(--INPUT-A) * var(--INPUT-B));
}
OR
Finally we have the OR operation that is additive with a clamp. Adding the two inputs (I1 & I2) will result in:
We need to clamp the output of 1 + 1 from 2 to 1, which can be done in two ways. We could deduct the output of performing an ADD operation on the same inputs but CSS can help through its ‘min’ function as follows.
.or-operation {
--OUTPUT: min(var(--INPUT-A) + var(--INPUT-B), 1);
}
Demonstration
To demonstrate the above operations we will output the result using the CSS content property of the ::after pseudo-element of a DIV element with a class of ‘result’ combined.
.result::after {
counter-reset: logic-result var(--OUTPUT);
content: counter(logic-result);
}
The [OPERATION] we want to perform will be stipulated using a CSS class. The input values will be supplied by assigning the appropriate custom property (-ise) via the style attribute.
Each test case will take the following form.
<div class="result [OPERATION]" style="--INPUT: [BOOLEAN_VALUE]"></div>
<!-- or -->
<div class="result [OPERATION]"
style="--INPUT-A: [BOOLEAN_VALUE_A] --INPUT-B: [BOOLEAN_VALUE_B]"></div>
Here are the test cases
<div class="result not-operation" style="--INPUT: var(--FALSE)"></div>
<div class="result not-operation" style="--INPUT: var(--TRUE)"></div>
<div class="result and-operation"
style="--INPUT-A: var(--FALSE); --INPUT-B: var(--FALSE)"></div>
<div class="result and-operation"
style="--INPUT-A: var(--FALSE); --INPUT-B: var(--TRUE)"></div>
<div class="result and-operation"
style="--INPUT-A: var(--TRUE); --INPUT-B: var(--FALSE)"></div>
<div class="result and-operation"
style="--INPUT-A: var(--TRUE); --INPUT-B: var(--TRUE)"></div>
<div class="result or-operation"
style="--INPUT-A: var(--FALSE); --INPUT-B: var(--FALSE)"></div>
<div class="result or-operation"
style="--INPUT-A: var(--FALSE); --INPUT-B: var(--TRUE)"></div>
<div class="result or-operation"
style="--INPUT-B: var(--FALSE); --INPUT-A: var(--TRUE)"></div>
<div class="result or-operation"
style="--INPUT-B: var(--TRUE); --INPUT-A: var(--TRUE)"></div>
The source code can be exercised in my CodePen.
Top comments (0)