Not too long ago, the introduction of React Context led to a wave of color theme switchers. For a brief, glorious moment, color theme switchers dethroned TODO apps as the default tutorial.
However, these color theme switchers all shared a flaw: they were all (a little bit) broken. When you selected a non-default theme, they would either:
- Briefly flash the wrong color theme (which Chris Coyier dubbed FART, as only Chris Coyier can)
Both of these workarounds aren’t ideal, and there haven’t been many (any?) solutions offered that don’t require running a server. In fact, Chris summed up his article on this problem by saying:
I’m not convinced there is a good way to avoid FART without a server-side language or force-delayed page renders.
But — good news, everybody! — I have a solution that will allow any site, whether it’s built with a server-side language, a JS framework, or plain ol’ HTML and CSS, to add a color theme switcher that works without client-side JS and doesn’t delay the page render.
To do this, we’re going to use edge computing, which might sound like a lot, but only requires about 60 lines of code and a free Netlify account to add.
1. Start with a basic HTML and CSS site
For this tutorial, we’ll be working from this starter repo.
The solution we’re building requires the use of Netlify Edge Functions, which means we’ll need the Netlify CLI for local testing and to deploy the site to Netlify.
Clone and deploy the repo
For convenience, click here to create and deploy the repo all at once. This will take you through Netlify’s deployment flow, connect your GitHub, create a copy of the repo, and deploy it to your Netlify account.
Set up local development
Once you’ve got that set up, set up the repo locally (make sure to replace
<repo> with your own username and repo name):
This will start the site at
There is a theme selection dropdown in the sidebar. Right now, choosing a new theme doesn’t change anything — it just adds a
?theme=<selected_theme> to the URL.
Get familiar with the code
Inside the repo, you’ll see a
src folder that has two HTML files and a CSS file inside.
src/index.html and take a look at the top of the file:
data-theme="default" is the critical piece of this markup.
Below that, there’s a form allowing people to choose their theme:
The rest of the markup sets up the example page, but doesn’t have any impact on the theme switcher functionality and can be ignored.
Next, open up
src/styles.css and look at the top of the file.
By targeting the
html element using the
data-theme attribute, we’re able to update the color theme by changing CSS variable values.
To see this in action, open the inspector in your browser and edit the attribute to
data-theme="pink". This will cause the site’s theme to change.
For our color switcher to work, we need to handle form submissions from the theme chooser, store that value somewhere, then use the value to update the
2. Create an edge function
What is an edge function?
An edge function is a small piece of business logic that’s executed at the network’s edge (which is another way of saying “as close to the user as possible”).
This logic is executed between receiving the request from the user and returning the response from the network. An edge function can modify the response before it’s returned — if you’ve ever worked with middleware before, it’s similar to that.
Create a Netlify Edge Function in your project
To set up a Netlify Edge Function, create a new folder called
netlify, and another folder called
edge-functions inside of it. Edge functions will be automatically detected by Netlify when they’re stored in this folder structure.
Create a new file at
netlify/edge-functions/color-theme.ts. Inside, let’s create the “hello world” of edge functions:
Next, we need to configure which route or routes should trigger the edge function. For a color theme, we want that to affect every page on the site, so we’ll target all routes.
netlify.toml at the root of your project and add the following:
Stop the server (
ctrl + C in your terminal), then restart it using
ntl dev —
localhost:8888 is replaced by the response from our edge function.
Return the original response through the edge function
If an edge function doesn’t return anything, by default the page will remain unchanged. This can be helpful for logging or setting cookies without modifying the response itself.
In this example, however, we do want to modify the response, so we need to send the original page response back.
To do this, modify
netlify/edge-functions/color-theme.ts with the following:
Reload the page in your browser and the site will render the same as it did before the edge function was added. However, if you look in the terminal, you’ll see that URLs were logged:
The HTML file ran through the edge function, which is exactly what we wanted!
However, the CSS file also triggered the edge function, and that’s not what we wanted.
Only transform HTML files
To make sure edge functions only execute on the requests we intend to modify, we can check the
Content-Type header and bail if it’s not
netlify/edge-functions/color-theme.ts with the following:
Reload the page and you’ll see that only the URL for the HTML itself shows up in the terminal logs now:
3. Update the HTML with the correct theme
To update the HTML with our correct theme, we need to do three things:
- Check a cookie to track which theme is currently selected
- Set the cookie to a new theme value when the user makes a theme selection
- Transform the
data-themeattribute in the HTML to insert the current theme
- Update the
selectedattribute of the theme switcher to make sure the current theme is selected in the dropdown
Use a cookie to track the currently selected theme
In Netlify Edge Functions, cookie management is simplified thanks to the built in
netlify/edge-functions/color-theme.ts, make the following changes:
Reload the page and see the theme logged in the terminal:
We haven’t set the cookie yet, so it falls back to “default”.
Set a cookie with the selected theme
When someone chooses a new theme, it will submit the form to the same page and add the theme as a query string. This is the default behavior of a form that doesn’t have an
In our app, the edge function will check for a query string and — if one is set — create a cookie using the selected theme.
Add the following to
This code starts by turning the requested URL into a web standard
URL interface, then checks to see if the query parameters include
theme. If set, it checks the requested theme against the available themes, then sets a cookie with the requested theme.
Finally, to remove the query string from the URL, the code redirects to the current URL without the query string.
Now that we have the current theme name set in a cookie, we can use it to transform the request and set the displayed color theme.
To do this, we’ll use a powerful library called HTMLRewriter that allows us to update elements in the response using CSS-like selectors and a straightforward API.
netlify/edge-functions/color-theme.ts to import
HTMLRewriter and the
Element type, then set up the transform for the
html element that updates the
Save this and reload the browser. Choose a theme other than the default and see that it actually updates now.
However, the dropdown will always show “system default”, which isn’t ideal.
Update the currently selected theme in the dropdown
To update the dropdown, we need to add another transformation in our
netlify/edge-functions/color-theme.ts to transform the
option element with a
value that matches the currently selected theme and set that to
Save and reload — it all works!
Before edge functions entered the equation, there wasn’t a good solution for managing color themes without either some jank on the client side or some extra management on the server side. But today, we’re now able to get the benefits of server side HTML rendering without having to set up a server.
Edge computing gives us the capability to make small transforms on the fly, whether we’re updating static HTML or transforming SSR-generated pages in a framework like Next.js. It also means we can get some of the benefits of servers without having to pay for servers — edge functions are free!