A refactor with CSS variables

The Women Who Design styling didn’t get a lot of love in its first few iterations, so I set aside time for a dedicated CSS refresh

January 02, 2019

Holy moly, it’s been a hot minute since the last time I wrote about Women Who Design back in June of 2017 and a looooot has changed under the hood in the last 18 months. There’s way too much to cover in one post, but, in short:

Women Who Design is now a static site powered by Gatsby, hosted on Netlify

Gatsby’s use of React and GraphQL replaced my need for Firebase

Ditto for JQuery, Heroku, and Express.js

The Twitter data now comes from a custom Gatsby source plugin that also saves the designer’s profile image – no more broken image files when someone changes their avatar!

Gatsby’s Image component means lazy loading actually works and the page jumps less as the profile images load in

Netlify’s webhooks integration rebuilds the site automatically every night to refresh all the Twitter data

THERE’S A JOB BOARD (?!?!), powered by Seeker, and the data comes from another custom Gatsby source plugin

I’m so excited about all these new updates, improvements and features, but the one I want to write about first is CSS variables. (Actually, to be fair, most of the updates listed above happened six months ago, but the CSS variables stuff only happened last month so it’s top of mind).

CSS variables are so useful because, unlike Sass variables, they don’t need to compile so they can be redefined at any time. Need every instance of a component to have a different theme? Check. Need a component’s styles change to match the theme of its parent container? Check. What about responsive spacing? Check.

Anyway, I guess I’ve now officially been writing code long enough to feel excited about deleting code and wow, did CSS variables help me delete code.

Let’s dive in!

The profile theme variable

One of the fun Easter eggs in the site design is that each designer’s profile is themed to their chosen Twitter theme color. I designed the profiles so that the theme extends to each display name, supporting icon, Twitter bio link and profile button link.

The plugin saves the designer’s chosen hex value from the API response as a variable

The plugin runs a script to search the designer’s Twitter bio and create anchor tags for any URLs, @handles, #hashtags and email addresses found

The script adds an inline style to any new anchor tag that applies the color of profile hex variable saved above

The plugin “saves” the designer’s newly modified Twitter profile data to make it available through GraphQL (Gatsby calls this creating a node)

When the plugin finishes, the site’s index.js file runs a GraphQL query for all of the profile data generated by the plugin

The index.js file maps over each designer’s data and passes it to the profile.js component through specific props

The profile.js component adds inline styles to color all the themed elements using the hex value prop

So, why refactor?

When I started learning web development, “Hey, the thing I wrote works!” was good enough for me. Then my thinking shifted to “Do I understand why the thing I wrote actually works the way it does?” Now that I’m a couple of years in, I’m trying to think more about writing stuff that behaves predictably: “Will this thing I wrote make sense to somebody else if they need to read or modify it?”

Which is basically what happened here. To get the theming working, I repeatedly set inline styles in different files at two different stages of the build process. I didn’t even realize how annoying this was until I shared the project code with someone a few weeks ago and, when asked about unsetting it, I realized they’d have to delete or change ~15 different lines of code to actually do it.

Separately, I’ve been thinking about open sourcing the Gatsby Twitter plugin, where the Twitter bio links get created and themed. Turning plain URL strings into actual anchor tags might be okay, but does adding opinionated color styling to specific HTML elements count as predictable output? Definitely not. You want plugins to do the one thing they set out to do and leave the rest as unopinionated as possible.

Finally, it was clear that the focus states throughout the site were pretty lacking and I wanted to give them an upgrade. Since you can’t style focus states using inline styles, I originally left them untouched, which looked like this:

The default browser focus state got the job done but the padding was wonky and the CSS Grid layout was causing the outline around the buttons to be cut off.

Since CSS variables are just regular ole CSS, I realized I could create a profile theme CSS variable, use it throughout the rest of the component’s CSS (:focus pseudo-classes and all), then redefine the variable’s value with an inline style just once per designer.

The profile component

The first file I refactored was the profile.js component file, which receives the designer’s Twitter profile color through the hex value prop. For the purposes of this post, I’ve removed all markup and styling that isn’t relevant to CSS variables from the code snippets.

The source plugin

Then I turned my attention to the Gatsby source Twitter plugin, which created and styled the Twitter bio anchor links (steps 1-5 above). That section of code had 6 specific profileColor references and looked like this:

Since the CSS I wrote earlier targeted anchor tags in the .profile class, the inline styles here became unneccessary.

I deleted them from the script, simplifying the file considerably and making the profile component itself the source of all profile styling. In doing so, it should be easier and more predictable to update styles in the future.

The page theme variables

Next up I tackled page level theming. In the previous example, I applied a theme to the contents of a component. But for page level theming, I needed to theme the contents of a component based on the color scheme of its parent container.

The navigation is a good example of this. If the navigation theme is “light”, it means the navigation is sitting in a white container and should have a gray logo and gray links to stand out. If the theme is dark, it means the navigation is sitting in a gray container and should have a white logo and links to stand out.

Of course, the initial visible styles were just part of the puzzle. Like the designer profiles, I also wanted to fix the focus states in other parts of the site.

The nav component

Here’s what the JSX looked like at the start of the CSS variables refactor:

Then I revisited the nav’s container stylings. Instead of having separate containerGray and containerWhite classes, I used a single container class where the text is --text colored and the background is --background colored. Simple enough.

nav.module.scss

.container{color:var(--text);background-color:var(--background);}

I set the logo fill to match the --text color of the container, making the theme check in the SVG markup unnecessary. (To be fair, using fill: currentColor would have worked just as well here.)

The filter checkbox component

Next, I wanted to add focus states to the checkboxes in the sidebar that filter the designers on display in the main section. On mobile, the filters have dark gray text with a white background and on desktop, the color scheme reverses.

Structurally, the filter checkbox component is a list item that contains a checkbox input:

It was about this time that I realized that the CSS I wrote was causing the checkboxes to be skipped entirely by keyboard navigation because I had given the default input checkbox a class of display none.

.input {display: none;}

As it turns out, it’s better to use absolute positioning and opacity to get the same effect and keep the functionality accessible to keyboard navigators.

Since the filters appear in the .container, they now inherit the variable color changes set above in the media query.

Woohoo! After these changes, the tabbing experience improved to this:

The spacing variable

The last area that I wanted to refactor with CSS variables was the site’s spacing. There are a few places throughout the site that required the same spacing values to maintain a nice, even layout.

On the homepage, that meant using the same value for the padding of the sidebar, the padding of the main section, and the vertical gutter between the profile columns.

I started by defining my spacing variable, which I called --page-shell-padding.

global.scss

:root {/* ... */--page-shell-padding: 24px;}

Of course, desktop viewports have more screen to work with and I wanted the spacing to grow accordingly. Inside a media query, I gave the --page-shell-padding variable a larger value on desktop. In my opinion, media queries are some of the handiest places to use CSS variables.

The variable became especially useful for creating spacing relationships when I needed just a bit more or less space than the value defined. For example, in the grid of profiles, I wanted the gap between the horizontal rows to be larger than the gap between the vertical columns. Using the CSS calc function, I multiplied the variable by 1.5 and got value that worked well.

The best part here is that the relationship stays the same as the variable changes across viewports without any additional media queries.

Takeaways

All in all, I’m happy with the progress I made in this CSS refactor. As always, there’s still a million more things that need fixing, but I can safely say that it’s much clearer and cleaner than the first two versions of the site.

My favorite parts were definitely giving the focus states proper attention and creating a more cohesive feel throughout the UI. Even as someone who works on design systems full-time, I was surprised by how many divergent styles and states I was able to introduce to the site when I wasn’t paying close attention to the CSS. The process of writing the focus state styles actually helped surface this more than anything else, and it led to me tidying up lots of other little inconsistencies.

I’m also still working to suss out the balance I want to strike between writing code that is neat and writing code that is clear. Though conventional wisdom states “do not repeat yourself,” I think repetition can be valuable if it makes it easier to predict what the code’s output will be.

There are a whole bunch of other Women Who Design announcements and updates to cover in the very near future, so I’ll leave it at that for now. It’s still nutty to me that this project is still alive and kicking almost two years later, so here’s to an exciting start to 2019!