Yesterday I went back to my frontend experiments after a load of JavaScript work. I experimented with the CSS custom property toggle trick (more on that later) which can allow me to have if/else logic in CSS, and with a bit of experimenting I made this CSS only calendar!
(It's embedded in a CodePen, if you wanna see the real thing go over to https://experiments.siddu.tech/css-api/)
It shows the current month, year, day and date completely in CSS! And it also updates (check it tomorrow and see!). It can even handle leap years! 🤯
Before I tell you what's really happening, go over to the site and inspect to see what you can find. Once you are done with your experimenting, scroll.
Spoilers in:
3
2
1
Alright, here's how it works: I'm including an external stylesheet with the data. The data is generated by a GitHub action which runs a Deno script which creates a CSS file with the data in custom properties (see below).
(Yeah I know I kinda lied, but not really because the site is still 0 JS 😋)
This opens up a whole new door of CSS APIs which can be used to do stuff like change color themes based on the season (spooky theme in October, grass theme in March, you get the idea), or maybe even expose content with content: var(--content)
!
That explanation too quick for you? Here's some more detail on how I did it.
Now that we have the data in CSS custom properties, we need to somehow change the data into content in the HTML. To understand how I did it, it will be nice to take a look at the CSS which was generated.
:root {
--year: '2021';
--november: ;
--monday: ;
--day-1: ;
}
This is the CSS which was generated for the day I'm writing (Nov 1 2021, Monday)
With this CSS we can easily embed the year with :after
, but the other properties look strange. Those properties are totally valid according to spec (the properties contain a value which is " ") and we can use them to make if else logic.
Here's a simpler CodePen showing how the thing works:
(Open the pen in a new tab and resize to see the full effect)
Let me explain:
- There is a breakpoint setup here at 350px. This is where the variable
--color
changes from initial to an empty space. - When the browser window is wider than 350px, the value of
--color
is initial- That makes the variable
--color-when-small
contain the valueinitial red
which is invalid. - So when we actually set the color and call that variable like background-color:
var(--color-when-small, var(--color-when-big));
, the second value (the fallback) is used because the first is invalid.
- That makes the variable
- When the browser window is narrower than 350px, the value of --color is a space.
- That makes the variable
--color-when-small
contain the value(space)red
, which is valid - So when we actually set the color and call that variable like background-color:
var(--color-when-small, var(--color-when-big));
, the first value is used
- That makes the variable
So, now we can flip the color between two values by changing a placeholder variable. I hope that clicks for you.
I used the same trick in this calendar too. I first added a bunch of span
s with all my months:
<span class='january'>January</span>
<span class='february'>February</span>
<span class='march'>March</span>
<span class='april'>April</span>
<!-- ... --->
Then in the CSS I set the display value in the same way as above:
.january {
--display-when-correct: var(--january) block;
display: var(--display-when-correct, none);
}
.february {
--display-when-correct: var(--february) block;
display: var(--display-when-correct, none);
}
/* ... */
So when --january
is set to " "
, --display-when-correct
becomes (space)block
, which is valid. And since --february
, --march
all don't exist, they become invalid (and so they are display: none
.
If this trick seems confusing (or helpful) then I'll write another detailed post on it!
I did a similar thing for highlighting days and dates, here's how:
.monday {
--bg: var(--monday) #1abc9c;
--fg: var(--monday) white;
--space: var(--monday) 5px;
background: var(--bg, initial);
border-radius: var(--space, initial);
padding: var(--space, initial);
color: var(--fg, initial) !important;
}
It's the same old trick, just a few extra things to add.
And also there is a fix for leap years and some other minimal stuff which you can learn by reading the source
CSS APIs
We can apply the same trick I used to generate this data to make more stuff like a fun fact generator, or for having different color themes like a seasonal color palette which changes (I think GitHub uses this trick for their Halloween theme, if not they should 😉)
Also, if you are up for a challenge, try implementing what I just used to make a site with a different color palette every season and share it over here! It's going to be much easier than building a calendar, trust me!
If you think this can be helpful in the real world, spread the word so that more people can use it in real world situations.
If you love my content, follow me on twitter or over here to stay updated on my blog posts!
Top comments (4)
A small rectification about the initial value: When you have
--color:initial
it doesn't mean that--color-when-small
contain the valueinitial red
. initial is not stored, it's applied to--color
and it has a specific meaning described here: w3.org/TR/css-variables-1/#invalid...You can read
So the fact that your variable has
initial
will make the whole property invalid at computed time. It's not the value (initial red
) that is invalidBy the way, why instead of this
You don't use this:
?
if
--tuesday
isinitial
you get the fallback value, if it's (space) all the properties will have invalid values and will fallback to their initial (default) value as wellNow that I think of it that make sense, I guess when I was coding it didn't come to mind. Will do that next time!
That makes sense, thanks for the new info!