#Expressive Code ; Astro ; Tailwind ; theme switch

28 messages · Page 1 of 1 (latest)

chrome spear
#

My theme switcher script; which is in <head> section and working

Working perfectly fine with Tailwind and Astro ;

Now added ExpressiveCode within Astro . Here is my astro config

import { defineConfig } from 'astro/config'; import tailwind from "@astrojs/tailwind"; import sitemap from "@astrojs/sitemap"; import mdx from "@astrojs/mdx"; import icon from "astro-icon"; import astroExpressiveCode from 'astro-expressive-code'; export default defineConfig({ site: 'https://santm.com', integrations: [ astroExpressiveCode({ themes: ['dracula', 'solarized-light'], }), tailwind(), sitemap(), mdx(), icon() ] });
When I switch my system theme from dark to light or vice versa the theme changes from dracula to solarized-light in locallStorage but not with the button click for theme switcher which is setting the variable darkMode true / false ; how I solve this ? Attched my theme switcher JS as well.

#

here is my theme switcher script

` <script>
const setDarkMode = () => {
if (localStorage.darkMode === 'true' || (!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
localStorage.darkMode = 'true';
} else {
document.documentElement.classList.remove('dark');
localStorage.darkMode = 'false';
}
}

        setDarkMode();
        // Runs on view transitions navigation
        document.addEventListener('astro:after-swap', setDarkMode);
    
        const setDarkModeButton = () => {
          const button = document.querySelector("#themeToggle");
          if (button) {  // Check if button is found before attempting to add event listener
            button.addEventListener('click', function () {
              document.documentElement.classList.toggle('dark');
    
              if (document.documentElement.classList.contains('dark')) {
                localStorage.darkMode = 'true';
              } else {
                localStorage.darkMode = 'false';
              }
            });
          }
        }
    
        setDarkModeButton();
        // Runs on view transitions navigation
        document.addEventListener('astro:after-swap', setDarkModeButton);
      </script>`
uncut girder
#

It looks like your theme switcher script works by setting or removing a dark class on your <html> element

#

Can you also show your CSS code that you use on your site that uses this dark class?

#

My current assumption is that your site defaults to light mode if the dark class is not there

#

If this is correct, you need to change the order of your themes option to reflect this as well: themes: ['solarized-light', 'dracula'],

#

The first theme is the default theme, and all others can be triggered through a selector. In the second step, we also need to customize this selector so that it matches what your site uses:

#
themeCssSelector: (theme) => `.${theme.type}`,
#

As theme.type can either be dark or light, adding this option to your Expressive Code config will make it react to your theme switcher by switching to the dark theme when this class is added by your JS code

#

Hope that helps! Let me know if you run into any issues

chrome spear
uncut girder
#

Nice, thanks for letting me know! Happy coding! houston_happy

digital hull
#

@uncut girder I actually didn’t realize this was possible, I was previously setting the data-theme attribute to the code theme manually when switching between dark and light.

I implemented this code and now the theme changes based on dark and light class name. I’m just wondering how this works?

Is the themecssselector setting accessing the class attribute with ‘.${theme.type}’?

I see now in the expressive code docs under configuration it says to set .theme-${theme.name} in order to use class names, isn’t that the same functionality, but different syntax?

Im still new to Astro and web dev so excuse the noob question!

uncut girder
#

The themeCssSelector config option exists to allow you to influence how theme-switching CSS is generated by Expressive Code. You pass it a function to tailor it to your site's individual needs. This function receives a theme instance, which is an ExpressiveCodeTheme with all the properties you can see in the docs (https://expressive-code.com/reference/core-api/#expressivecodetheme, then scroll down a bit until you see "Properties")

Expressive Code
#

The default setting of the themeCssSelector option accesses the theme's name property to build CSS selectors for your themes. This is the default because it allows you to use even more than 2 themes - you can have any number of themes, actually, and select all of them by name.

#

In the question that OP asked here, it was way simpler, as there were only two themes, and the existing JS/CSS code relied on a class named dark for the switching. And as the possible values of the type property of an ExpressiveCodeTheme are either light or dark, I proposed a solution using the theme.type so that OP didn't have to change their other code

#

If you have the themes option set to ['github-dark', 'github-light'], the default setting of themeCssSelector ((theme) => `[data-theme='${theme.name}']` ) generates CSS like this:

:root {
...css variables for the first (default) theme, in this case github-dark...
}
:root[data-theme='github-light'] {
...css variables for the github-light theme...
}
#

If you turn the array options in themes around so that light becomes the default theme (['github-light', 'github-dark']), it's this:

:root {
...css variables for the first (default) theme, now github-light...
}
:root[data-theme='github-dark'] {
...css variables for the github-dark theme...
}
#

And if you now switch the themeCssSelector to the one I proposed as solution here (theme) => `.${theme.type}` , the CSS becomes this:

:root {
...css variables for the first (default) theme, now github-light...
}
:root.dark {
...css variables for the github-dark theme...
}
#

The generated CSS is actually a bit more complex to allow more flexibility (for example, you can even override the theme per block, and it also includes media queries if you want), but I hope I could still get the point across and that helped!

#

Oh, and :root is just an alias for the html element, with higher specificity

#

You can also change that part using another option (themeCssRoot) if your theme switcher changes attributes of another element, e.g. body.

digital hull
#

Thats super helpful, thanks for the detailed response!!

So if I understand correctly, using ‘.theme-${theme.name}’ would look for the themes name in the class attribute right?

Using theme.type is perfect for my use case because I’m also only using two themes, one for light and one for dark.

Thanks again for your insight!

uncut girder
#

All these examples use JavaScript template string interpolation to fill "placeholders" in the template with values from an object. In your mentioned example, the template string is .theme-${theme.name}, and it contains the placeholder ${theme.name}. That placeholder gets replaced with the theme name, e.g. github-dark. So after filling in the placeholders, the resulting string would become .theme-github-dark. And as it starts with a dot ., this is targeting a class named theme-github-dark in CSS.

#

So your understanding was almost correct! It doesn't look only for the themes name in the class attribute, but for the themes name prefixed with theme-

#

But you could of course change the template string to anything else, and also omit that prefix