To simply call Kizu's Cyclic Space Toggles an upgrade to my Space Toggle technique is like calling apple computers an upgrade to apples. I'll prove it, slowly.
my Space Toggles are a checkbox
no matter how complex the downstream implications are, or how many properties the space toggle toggles, your input is a single binary on and off so there are only ever two states produced by a single toggle. (two space toggles is 4 states, three toggles is 8, 4 is 16, it's binary so it does get cooler fast! But... you're adding multiple input toggles to reach that, which means the API you expose tells your dev users to set multiple properties. And it's hard enough to tell a dev to set even a single CSS property without hiding it behind a class name. lol)
Kizzu's invention, Cyclic Space Toggles, are much more.
Instead of a one-bit checkbox, you get an N-bit radio-button set, or a dropdown select. The dev user's single input option is now determining any number of alternative states internally, and each one of those is itself a traditional (inverted) Space Toggle.
To get an intuitive perspective on what that means, think of any dropdown - a language selector - and imagine the implications of replacing it with a single checkbox.
On the surface alone, that's a good measure of the difference I'll demonstrate.
Keep in mind for this read, for both of our toggles Global User Reach is ~95-97%. Cyclic Space Toggles are only slightly less reach because of a spec-"bug" I hit with early Space Toggle research and in July 2020 managed to convince them to address it, shipping the fix cross-browser shortly after.
That means nearly everyone can benefit from and use what is shown here today.
Inverted? Space Toggle? CSS APIs? Catch-me-up!
First, a standard space toggle is two states, initial and a space .
initial
initial is effectively an undefined, falsey state that on the surface allows the fallback of a var() to be used. --x: var(--falsey, fallback). When it's used in the value of another property, without a fallback, the property using it also becomes entirely falsey.
--falsey-too: var(--falsey);
This is one of several falsey-state triggers the spec calls IACVT (invalid at computed value time).
A var(--reference) that was never defined is initial by default. A variable set to or including the keyword initial is also, equally, falsey.
(space)
(space) is effectively an inert truthy state that blocks the fallback without changing the rest of the value it's used in.
--space: ;
--just-blue: var(--space) blue;
In spec terms today, it's known as <empty> and setting a var with no value at all (not even a space) became valid, whereas it used to be invalid without the space. You should always include a space for best browser compatibility.
Space Toggle
Now, combine them into a toggle:
--space-toggle: ;
--blue-or-falsey: var(--space-toggle) blue;
/* blue */
--space-toggle: initial;
--blue-or-falsey: var(--space-toggle) blue;
/* falsey */
Now you can conditionally set any number of other properties with it.
border: 1px solid var(--blue-or-falsey, red);
background: var(--blue-or-falsey, linear-gradient(black, cyan));
and even build more half-toggled references
--rem-or-falsey: var(--space-toggle) 1rem;
--white-or-falsey: var(--space-toggle) white;
font-size: var(--rem-or-falsey, 10px);
color: var(--white-or-falsey, red);
you can add multiple space toggles into a single property and if any of them are falsey, the whole thing is falsey, giving you the binary permutations.
Inverted?
The standard space toggle is perfect for logic gates and going as far as a full fledged actually-programatic, not hardcoded at all beyond saying where the turds are located, 100% CSS implementation of minesweeper.
When you expose a space toggle as an CSS API endpoint, however, the library/component/etc will typically have implemented their CSS with the space toggle defined as a space (truthy), making their developer users set it to initial as the API endpoint to flip it.
CSS implementation:
--use-my-feature: ;
--currently-disabled: var(--use-my-feature) none;
border-style: var(--currently-disabled, solid);
background: var(--currently-disabled, url("/img/cat.gif"));
CSS API docs:
Set `--use-my-feature` to `initial` for a great time.
The API tells them to turn off a space toggle to turn on a feature. Hence, inverted.
For a long time, the only way "flip" a space toggle programmatically was with an animation, so if a library implemented it as initial by default without that animation layer, they didn't have great ways to define a default state. For example:
Wonky CSS implementation:
--use-my-feature: initial;
--currently-disabled: var(--use-my-feature, none);
border-style: var(--currently-disabled);
/* ^ technically works because `solid` is the default property value when ` ` is attempted. */
background: var(--currently-disabled) url("/img/cat.gif");
/* ^ technically works because it's invalid as "none cat" until flipped to the valid (space) cat ` cat` */
CSS API docs:
Set `--use-my-feature` to ` ` for a great time with a wonky backstory.
Inversion history
When I first invented the space toggle, it was an intentional prayer for augmented-ui v2's CSS-based API (rather than HTML/class/attr based API) that got answered by some external divine ping telling me to read the specification over and over. Once I tuned into what God was trying to show me, a fire was lit and I ran with it.
Tangent, demo, showcase, tangent, demo showcase, spreading all over CSS world on social media and gaining a much wider audience than I was accustom to. So much fun. :)
Then I got pulled into another thrilling tangent and in July 2020, I created and released css-media-vars before finishing augmented-ui v2 because the combination of space-toggles and media queries was so good that I've used css-media-vars in every website I've done since then. (until its successor, breakpoint-system) The standard space-toggle was perfect for that use case because the user was defining both of the states and I was only shipping the toggles.
So for augmented-ui, when it came time to finalize the API in August 2020, (after realizing that it was much cleaner in my code if we start with it "on" to define our "off" state by default) I had to decide either stick with the messy code and tell my users to write an empty space to turn on the feature (to which they'd surely raise an eyebrow), or tell them to ...initialize it with "initial" which seemed legit. Easy choice except... there's another trade-off that's been resolved in the spec (as previously mentioned) but it was actually very problematic at the time to default it to initial in a library that's intended to be nested, so it required an entire separate "reset" layer in between nestings to avoid. A problem css-media-vars didn't directly have because it was on :root. I was hesitant to make that trade off, but decided to move forward with it and add a huge annoying Caveat Sand Storm section to the docs to explain it.
After all that time, when Lea Verou wrote about the space toggle in October 2020, she proposed them as inverted by default (without warning about the spec nesting problem D:). So... there may not be a consensus in the wider CSS loving audience on which variant is "inverted" because she had orders of magnitude more reach than I did at the time... buuut I maintain my perspective on which is which, and I've digressed!
Cyclic Space Toggles
Imagining cyclic space toggles as a dropdown is a great mental entry.
You can programmatically respond to each option individually in your CSS code, knowing if it's selected or not.
This is a Super Saiyan space toggle to our Saiyan space toggle, for several reasons.
All of these API --option vars are inverted space toggles (the kind we prefer in making APIs - our users make them initial while we use space to set defaults internally!), and they're all interlinked, only one option resolves to initial, the rest are space. You can add any number of --options you want, and they can all have completely different effects on the broader library internals.
A key difference here is that with the standard space toggle, the toggle itself is what the library/component author uses to control their internal state AND that same var is what our user modifies by setting it to
initial(or to a space).With cyclic vars, the API is our dev user setting the
--selectto one of our--optionvars, internally we only use each of the--optionvars because--selectis always initial.
The dropdown analogy... is a veil.
One of the most heavily desired features CSS library authors have wanted since variables were introduced is the ability to make shorthand properties so our users can specify multiple related states in a single declaration.
WE WERE SO CLOSE TO GETTING THAT WITH CUSTOM FUNCTIONS, but in a gutting last moment change that was originally expected and planned to happen comma-separated var() values passed as arguments no longer spread by default like they do in standard functions rgb(var(--rgb)). So there's still no way implemented in any browser to dissect a custom property set to multiple values.
I'm not exaggerating even slightly, there are an enormous number of benefits to csvargument spreading in custom functions, removal of that, aside from being another irritating inconsistency in CSS, is the single biggest nerf I've ever witnessed to an incredible potential. Custom shorthands is a footnote on the tip of the iceberg of the potential in csvargument spreading. DEPRESSING.
BUT! We have a surprise here.
If space toggles were new-born Saiyans, cyclic space toggles are Super Saiyan God (Blue) Super Saiyans. The abilities ARE THAT MUCH MORE POWERFUL; I'M NOT KIDDING EVEN A LITTLE!
I don't even know if Kizzu realizes all of this yet, but just wait for the details here. 🥵🤤
The dropdown... is actually a multi-select. The implications of this in CSS API development are awesome!
If your dev user sets:
--select: var(--option-1) var(--option-2);
then both options are initial and the rest are still space.
This is a custom boolean only shorthand API. It's a far cry from actual shorthands I'm begging for, but man it is AWESOME for now!
CSS library/component authors can specify any number of related boolean (space toggle) options and their dev users can flip all of them on in a single declaration.
Real world use case
I've already shipped a CSS library using this!
With breakpoint-system's fluidity option I allow users to opt-in to a subset of their globally defined breakpoints.
This causes the internals to skip the missing breakpoints as if they weren't part of the set for a specific design. Among other effects, the built in fluid design vars "breakpoint units" will now scale seamlessly from the lower breakpoint up to the next one included without resetting on the skipped one.
.promotion-page-layout {
--bp-fluidity: var(--fluid-xxs)
var(--fluid-sm)
var(--fluid-lg);
}
This makes the whole system behave as if there were only those 3 breakpoints defined, while their global set remains untouched on other pages/layouts.
I've even used this multi-select idea in other features within the library that are currently undocumented!
Where else would we use it?
If a theming library had variants, you could provide an API like:
--use-theme: var(--blue-theme)
var(--button-states)
var(--text-emphasis)
var(--override-preference-to-dark-mode)
;
And internally, because space toggles can be combined logically, the library would know how to emphasize the text in the blue theme on a button, which could be different from in a paragraph or in the yellow theme.
An avatar-building CSS library like CSS-Peeps could take its current CSS type-grinding API:
--peep-head: long-straight;
--peep-face: rage;
--peep-body: striped-tee;
and instead do:
--peep: var(--head-long-straight)
var(--face-rage)
var(--body-striped-tee)
;
Tailwind could go from awful dom pollution repeated a thousand times
<button class="bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-lg transition px-4 py-2 text-sm md:px-8 md:py-4 md:text-lg">
Click Me
</button>
<button class="bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-lg transition px-4 py-2 text-sm md:px-8 md:py-4 md:text-lg">
Click Me Too
</button>
to a reusable CSS-based API
<button class="tailwind my-tw-button">
Click Me
</button>
<button class="tailwind my-tw-button">
Click Me Too
</button>
.my-tw-button {
--tailwind: var(--bg-blue-600)
var(--hover:bg-blue-700)
var(--text-white)
var(--font-bold)
var(--rounded-lg)
var(--transition)
var(--px-4)
var(--py-2)
var(--text-sm)
var(--md:px-8)
var(--md:py-4)
var(--md:text-lg)
;
}
Which is such a great idea, I might even implement it myself. Internally, inside the media queries where those responsive classes are defined, it instead would check if those specific space toggles were flipped.
I could probably port all of the base tailwind classes to this cyclic space toggle version in a single day.
Open Contact 👽
Please do reach out - I'd LOVE to hear what you think!

Top comments (0)