Throwback to Space Toggles Boolean Algebra
- Published on:
- Categories:
- Throwback Thursday, CSS Conditions 2, CSS Variables 10, CSS 92
- Current music:
- Mice Parade —
Distant
- Current drink:
- Peppermint Tea
Remember my “Cyclic Toggles”? If not, go read the first half of that article. Space toggles are cool, and I still use them more often than cyclic toggles, but occasionally, when I need more than a boolean value, I still reach out to the cyclic version of them.
Of course, soon we will have proper conditions — if() function in CSS! It is already in Chromium-based browsers, just recently was mentioned in Safari 27.3 Beta Release Notes, and we are only waiting on Firefox to get to it.
But — that’s in the future. For now, we have IACVT and friends! You can read “CSS Foundations: What is IACVT?” article by Bramus if you want to learn more about this concept.
And even though we have other hacky ways to get conditionals in CSS — Lea Verou’s “Inline conditionals in CSS, now?” outlines most of the currently available techniques — I think space and cyclic toggles can be the best.
They’re a bit verbose, but surprizingly readable!
button {
font-size: 2em;
border: 0.25em solid;
border-radius: 2lh;
/* Cyclic Toggles init! */
--state: var(--state--default);
--state--default: var(--state,);
--state--hocus: var(--state,);
--state--disabled: var(--state,);
/* Cyclic Toggles usage! */
color:
var(--state--default, deepPink)
var(--state--hocus, forestGreen)
var(--state--disabled, slateGray)
;
background:
var(--state--default, pink)
var(--state--hocus, lightGreen)
var(--state--disabled, whiteSmoke)
;
border-style:
var(--state--default, solid)
var(--state--hocus, double)
var(--state--disabled, dashed)
;
cursor:
var(--state--default, pointer)
var(--state--hocus, pointer)
var(--state--disabled, not-allowed)
;
/* Switching Cyclic Toggles values! */
&:hover,
&:focus-visible {
--state: var(--state--hocus);
}
&[disabled] {
--state: var(--state--disabled);
}
}<button type="button">I am a button!</button>
<button type="button" disabled>Disabled button!</button>Neat, right? The parts that I especially like:
-
All the possible values are in one place, self-formatting as a small table of sorts.
-
When you want to switch the state, you only toggle one single custom property, and it just works.
However, if you strive for useless perfection, you might notice that for the cursor we repeated the obviously correct value of pointer twice.
Can we somehow define this without repeating ourselves — or separating this value into a reusable variable… which then will need to be repeated? For just three states this is not the worst, but if we had like six or seven of those…
This is what the today’s throwback is to: the “Space Toggles Logic” — a section of my large article on Cyclic Toggles that, I think, many people might’ve missed. Totally because they were so fascinated by the technique itself and not because I cram too much info in my articles. For sure.
I won’t repeat most of the info that I had in the article here, but I want to focus on probably the most common logical use case for boolean algebra related to cyclic toggles — the NOT thingy.
If we return to how we defined the cursor above:
button {
cursor:
var(--state--default, pointer)
var(--state--hocus, pointer)
var(--state--disabled, not-allowed)
;
}
Then what we actually wanted to say is “if the state is disabled, use the not-allowed value, and otherwise use pointer”. How can we do it with cyclic toggles?
This is how:
button {
--not-disabled: pointer var(--state--disabled);
cursor:
var(--state--disabled, not-allowed)
var(--not-disabled,)
;
}cursor from the previous example, but still works!The two limitations of doing this kind of boolean algebra are:
-
We need an extra custom property — we can’t cram both values into the same declaration.
-
We can’t define values of other space or cyclic toggles this way — the value should be directly used by a regular property.
The gist of this technique is that var(--state--disabled, not-allowed) stays unchanged to regular usage of cyclic toggles, and all the magic goes into how the --not-disabled is defined and applied:
-
When the state is disabled, the
--state--disabledis IACVT orinitial, which makes thenot-allowedto be returned. But what happens with the other part? When browser tries to apply--not-disabled, it looks inside and seespointeralongside an IACVT value, and decides that the whole value is IACVT, thus going to the fallback ofvar(--not-disabled,)— emptiness, keeping only thenot-allowedin the end. -
When the state is anything but disabled, the
--state--disabledis emptiness due to how cyclic toggles work, and thus thevar(--state--disabled, not-allowed)also evaluates into it. And when we go inside--not-disabled, thevar(--state--disabled)inside is also empty, thus--not-disabledreturns just the other value that was defined alongside it —pointerin our case!
That’s it. That’s the throwback.
It is possible to do almost any logic with cyclic toggles this way — I recommend going through the corresponding section of my old article for the details on that. Although, that article is more than four years now already, wow! So if I were to write it today, I would likely explain/structure it differently. But also, at the time, I was just too excited to share the technique, so maybe in similar circumstances it would still end up in the same form… Who knows!
Did you know about cyclic toggles? Did you use them? On their own, or alongside logic gates? Do you still not understand something that’s going on? Feel free to ask me any questions, I will be happy to reply.