Create CSS Variables from Theme Objects with Recursion
There are many ways to solve this challenge! What counts is that the results match.
Logging the returned output
of the getCssVariableDeclarations
will help us to write the function.
In the plugin
function, add a console.log()
with a message to remind us where we are in the code. Add a comm
Transcript
0:00 To help us write our function, let's console.log the returned output in the terminal. Down here is our function definition, and then inside the addBase here, let's add a console.log that says CSS vars definition, and then consumes that function, so getCssVariableDeclarations, and as the input, we will pass our themes.base.
0:24 You can see currently, it logs an empty object because this is the default output. That should help us construct the correct output that we're looking for.
0:33 The approach I'm going to use is one of many. This is JavaScript, and you can do all sorts of different approaches. I'm going to use a forEach loop and then do a little bit of recursion inside of it.
0:44 The first thing I want to do is do Object.entries, and instead of grabbing our themes, we're going to grab the inputs, which is passed to this function. Then forEach, again, we're going to have a key and a value here.
0:57 Here, we're essentially at a crossroad where if the value is a string, we want to output a CSS variable for that string, but if it's not, if it's a nested object, we want to recursively call the same function again with a different input.
1:11 If we're here and the value is a string, a hex color, we can create the CSS variable for this, but if we're here and the value is actually a whole object, we need to call the function again. Let's try the if statement, if typeof value is not a string, we're going to call the same function, getCssVariableDeclaration, but this time, the input will be the value, and we'll also pass the output.
1:39 Otherwise, if it is a string, what we can do is take our initially empty output object and add a dynamic key to it, which will be -- for the CSS variable prefix. It's not going to work just yet, but let's pass the key here, and for now, let's set the value of that property to the value, which is a string.
2:00 We've got a start. We've got a series of variable names. They're obviously wrong, and the value is the hex value, but this is a pretty good start. Looking up here, you can see that we want the getRgbChannels output. We can fix that easily, getRgbChannels for that value. Very nice.
2:19 If you look, it goes from 50 to 900 and then jumps straight to color. Remember, if the value is not a string, we are calling the function again recursively. What happens is when we hit secondary, we call the function again, and again, and again, and eventually have this color name. That --color name is just the last part.
2:38 What we want to create is a segment which is a kebab-cased string of all the nested keys one after the other. We want this --color here to be -secondary-some-nested-color.
2:53 To do that, we need to keep track of the current segment or the current key, and so I'm going to add a new argument to the function here. Between the input and the output, we will have a path, which will be by default an empty array.
3:08 Now inside the forEach loop, I can start by updating the path so every time the function is called again, it has a new path, so const newPath = path.concat, and we will pass the key here. What this new path does here is take the path, which at the start is an empty array, and then adds the key inside of this array.
3:30 We'll collect the various nested keys inside this array, and then we'll join the array together to construct our kebab-cased variable name. Now if the type of value is not a string, and we call the function again, we will pass this newPath as the path.
3:46 This function will be called again, but the path here, instead of being an empty array, will be the first key. With that in place, we can replace here just the key, which was the last key in the object, with newPath. Because it's an array, we will .join it, and the delimiter between each element of the array will be a -.
4:07 As I hit save, I think we're going to nail the structure that we want. Three, two, one, boo. Look at this. We have our primary-50 to 900 colors, and then exactly as we wanted, we have secondary-some-nested-color.
4:24 Let me clear that output, and now look at this. I'm going to go in the theme.json file, and to show you that it works, I will add a hello, ffffff, and save this. You can see my primary-hello key added here. If the value wasn't a string but was an object, wow, it, really, works. I broke it, but I need to add a comma here, and now look at this, primary-hello-wow-it-really-works.
4:53 It looks like we've landed on a function that can generate CSS variables on the fly based on the input object shape. I will backpedal on my demonstration here, and we're now going to try to replace that addBase block with a function.
5:10 I'll remove the console.log here, and instead of a terminal output, we'll look at the UI once again. We are going to try to replace that CSS with our getCssVariableDeclarations function. Let's delete all the CSS variables inside of there, just to see that it should break the UI, and also delete these ones here. Now we have no more theme colors. It's pretty obvious.
5:33 Now, instead of this empty object here, I will call the getCssVariableDeclarations. Our input here will be themes.base for now. Check it out. It's successfully creating the CSS variable colors once again. Down here, instead of the empty object, we will call getCssVariableDeclarations one more time. This time, the input will be the value, and boom, everything is back into place. How cool is that?
6:05 One quick note here. We're still making the assumption that the first theme called base, which is not a given at all. Instead, here, I will use Object.values from our themes object, and then simply grab the first instance, which is, in our case, the base theme. Basically, the first theme that the user passes in the themes object will be used to generate the global CSS variables, and it still works.
6:31 Our plugin is becoming truly super powerful now. The next thing we need to do is dynamically generate the CSS utility classes generated by Tailwind.