Dark mode has been all the rage in dev circles for years now but not everyone is on board with it. I fall into the mostly-light-mode-but-for-that-one-app camp. No shade (pun intended) on those who chose the dark side, but it's just not for my old eyes.
However, I accept that what works for me doesn't for others and vice versa, so how do you accommodate both users in your application?
In my personal project I took it as a fun exercise to add a dark mode to my app using HTMX to do all the work. The result was surprisingly simple and, I would argue, easier than a SPA.
The Objective
This is the application in its light-mode glory.
What we want to achieve is a switch between a light and dark mode theme with a simple toggle.
Consolidate the colours
The first step is to create two root CSS files - colors-light.css and colors-dark.css and put them in the wwwroot folder as static assets.
Next, all the colours that you want to change when a theme is switched, need to be consolidated into these two CSS files as variables.
/* Dark theme colour variables */
:root {
--app-bg: #1e1e2e;
--app-card-bg: #2a2a3e;
--app-surface-bg: #2a2a3e;
--app-footer-bg: #1a3a4a;
--app-text: #e0e0e0;
--app-text-muted: #a0a0b0;
--app-header-bg: #111111;
--app-hover: 50%;
}
I found the best way to identify the colours you want is to use browser dev tools to pick the elements and then get their colour data.
Do the same for the colors-light.css file (with different colours, obviously).
Now, the existing CSS files where these colours will be used need to be updated to use the CSS variables instead of explicit colours.
.note-list-item {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: space-between;
background-color: var(--app-card-bg);
padding: 0.5rem 0.8rem;
&:hover {
background-color: var(--app-hover);
}
button {
color: var(--app-text);
padding: 0;
text-align: start;
flex: 1;
}
}
This is quite elegant because it means the CSS for you application doesn't need to change, it just needs to reference a CSS variable.
The trick is to change the value of the variable.
Repeat this same process for any CSS that needs to respond to a colour change.
The final piece is to wire up the theme CSS to your wherever CSS <link..../> references are added to your application.
<link id="theme-stylesheet" rel="stylesheet" href="css/colors-@(_theme).css" />
We also need to set the default value when the page loads. I use a session variable but you could also use a cookie or some other persistence.
protected override void OnInitialized()
{
var themeSession = HttpContextAccessor.HttpContext?.Session.GetString("theme");
_theme = themeSession is "dark" ? "dark" : "light";
}
NOTE: Your server-side syntax may vary from mine (Razor), but you should be able to work out what you need
Build the switcher
Switching the theme requires a control to make the HTMX request and a server endpoint to persist the change and update the UI.
Server endpoint
We first need an endpoint that will update our session and return our updated <ThemeSwitcher/>:
// Toggle the theme session value and return the new stylesheet link for HTMX to swap in-place.
app.MapGet("/toggle", (HttpContext httpContext) =>
{
var currentTheme = httpContext.Session.GetString("theme") ?? "light";
var newTheme = currentTheme == "light" ? "dark" : "light";
httpContext.Session.SetString("theme", newTheme);
return new RazorComponentResult<ThemeSwitcher>(new {
Theme = newTheme
});
});
Theme switch control
Again, use what works for you, but this is my Blazor <ThemeSwitcher/> component:
@inject IHttpContextAccessor HttpContextAccessor
<button type="button"
class="btn btn-link"
hx-get="/theme/toggle"
hx-swap="outerHTML">
@if (_isDark)
{
<Icon Name="sun" />
}
else
{
<Icon Name="moon" />
}
</button>
<hx-partial hx-target="#theme-stylesheet" hx-swap="outerHTML">
<link id="theme-stylesheet" rel="stylesheet" href=@($"css/colors-{Theme}.css") />
</hx-partial>
@code {
[Parameter]
public string? Theme { get; set; }
private bool _isDark => Theme == "dark";
protected override void OnInitialized()
{
// If the Theme parameter is not set (i.e., we're rendering this component for the first time), load the theme from the session
if (Theme == null)
{
// Get the current theme from the session, defaulting to "light" if not set
var themeSession = HttpContextAccessor.HttpContext?.Session.GetString("theme");
Theme = themeSession ?? "light";
}
}
}
It's pretty simple in how it works. It renders a button with HTMX attributes which displays either a sun or moon icon depending on the current theme.
The current theme will default to light unless there is a theme stored in session.
The key piece that makes it work is the <hx-partial>. Because we gave our <link... /> an id attribute, we can target it with an HTMX response. In this case, the HTML fragment will be a fresh <link... /> with our updated CSS reference.
The really cool bit is the browser will detect this change and the styles are applied immediately.
Putting it all together
Now, we have our CSS themes, endpoint, and switcher component ready, we can put them together.
In my case, I just dropped it in the <footer> element:
<footer>
<ThemeSwitcher />
<form method="post" action="/auth/logout" id="logout-form">
<button type="submit" class="btn btn-link">Logout</button>
</form>
</footer>
Which renders like this:
Now, let's click the switcher:
This looks cool, but you really have to see it in action:
Next steps
Of course, this just shows two colour modes, but you could have as many colour themes as you want e.g. one for colour blindness. You just need to consolidate your colours and then make them an option in your switcher.





Top comments (0)