New Theme Selector
Posted on
Last updated on
I've added a theme selector to the top right of (almost) every page, and I think it's pretty nifty.
I set out to not use any javascript on this iteration of the site, primarily because I think that a website should be fully functional without javascript, but also partly because I just suck at javascript. As a result, I've long relied on the /themes page for web wanderers to set their theme on this little buried nook of the web. You click on the theme you want, and after a little reload (and cookie), you've got it.
That worked fine, except I don't think many people noticed there were themes there unless they went exploring or reading older posts.
A while ago I used the CSS :has selector to allow wanderers to filter the posts on the front page, and more recently to toggle summaries of the posts. I knew I could take advantage of this to set a theme, but it was a bit of a task as I had slapped together the themes quickly, each relying on elements of others that may get properties inherited and overwritten later. It was (and still is) kinda yuck.
I decided to test if I could combine :has with :selected and nest declarations. One hour later, I had a working prototype with two of my themes ported across. Some rewriting of the CSS as well as the theme application/detection logic on the server side was needed, but it wasn't as bad as I had feared.
A few days later, I finished up, tidied up some loose ends, added a little polish, did some testing (hold the phone) and merged the changes into the live site. Success! Thanks to a couple members of the 32bit.cafe discord for helping me test!
You can now use the theme selector - just select a theme from the drop down in the top right and the page should update to that theme right away. This will not be retained across pages, unless you hit the 'Save' button. This saves a cookie on your device, unless you select the default theme, which will delete the cookie.
Implementation
Using the :has CSS selector, combined with :checked (which works on dropdowns, whodathunkit?) and nesting the theme-specific declarations and selectors inside it, like so:
html:has(select#themeSelector option[value="redalert"]:checked) { /* CSS here */ }
If the 'redalert' option is selected in the drop down, it'll apply whatever CSS declarations are in the curly braces. Let's break it down a little:
html:has(
I've chosen the HTML tag here instead of the BODY so I can set a rule on the HTML (like a background colour) as well as the BODY. I do this for the Paper themes. So, if the <html> tag has...
select#themeSelector
...a <select> with the ID themeSelector (<select id="themeSelector">)...
option[value="redalert"]:checked
...with an option with the value of redalert currently "checked", which is poor grammar but in the case of a <select> dropdown list, means "chosen" or "selected" (but we can't use :selected because we already have ::selection and that's way too similar) we can...
/* CSS here */
...Apply our 'redAlert' theme CSS! We just write out our theme-specific CSS declarations like our colour variables and any other selectors and declaration blocks. This changes depending on the theme of course, with some being small and others being quite a bit larger in size and complexity.
We can embed nested CSS selectors and declarations inside this, too, allowing us to change the property of elements for particular themes. If you can hack looking at some... probably not great CSS, check out this sites .css file here.
Time to make more themes, I guess!
Problems
Let me know if you have issues.
- Clicking the Save button doesn't do anything! - yes it does, it redirects you to /themes?choice=[themeChoice] which sets a cookie, then sends you back to the page you came from. Unfortunately, or fortunately depending on how you look at it, this happens so fast that on a decent latency connection the browser doesn't even flicker. It looks like it's not doing anything at all. I don't like the idea of setting a false delay, so I need a way to signal that the theme has changed. Can I use :selected or :focus to do this... a task for the future