saadeghi / theme-change

Change CSS theme with toggle, buttons or select using CSS custom properties and localStorage
https://codepen.io/saadeghi/pen/OJypbNM
MIT License
1.42k stars 46 forks source link

SvelteKit example #3

Closed spences10 closed 3 years ago

spences10 commented 3 years ago

Hi, is there a SvelteKit example I could take a look at anywhere?

If the only way to do it is with HTML tags then that's cool 👍

saadeghi commented 3 years ago

Sure.
First install

npm i theme-change

Your .svelte file would be like:

<button data-toggle-theme="dark,light" data-act-class="ACTIVECLASS">toggle theme</button>

<script>
import { onMount } from 'svelte';
import {themeChange} from "theme-change"
onMount(async () => {
  themeChange(false)
})
// must use onMount because we don't want it on SSR.
</script>

And your global.css file would be like:

:root {
  --my-color: white;
  background-color: var(--my-color);
}
[data-theme='dark'] {
  --my-color: black ;
}
[data-theme='light'] {
  --my-color: white;
}

Note that svelte will purge unused style from scoped CSS and styles like [data-theme='dark'] will be removed so we need to use it on a global CSS file.

Please let me know if you have any questions.

spences10 commented 3 years ago

Nope! No questions, that's awesome! Thanks

beebase commented 2 years ago

So how do I switch between existing themes like loke,retro ?

saadeghi commented 2 years ago

@beebase data-toggle-theme="dark,light" switches between dark and light
so for example data-toggle-theme="valentine,retro" will switch between valentine and retro

d3tr0id commented 2 years ago

Hi! How can I use this in svelte if I want to keep the Swap feature from daisyUI (https://daisyui.com/components/swap/)? I want to use it in this context:

` label class="swap swap-rotate class:swap-active={isDark}">

<svg class="swap-on fill-current w-6 h-6"[.....]"/>

<svg class="swap-off fill-current w-6 h-6 "[.......]"/> `

But if I do this, I need the isDark variable initialized, so where can I put this? Thanks!

myleftshoe commented 2 years ago

@d3tr0id just did this myself. Copy and paste the sun/moon swap example code from your link and add data-toggle-theme and data-act-class to the checkbox input. That's it!

<input type="checkbox" data-toggle-theme="dark,light" data-act-class="ACTIVECLASS"/>
d3tr0id commented 2 years ago

Yes I did that, but as soon I refresh the page, the wrong state is displayed from the swap icon. I have to handle the display of the correct icon by myself right? Still thanks for the response ! 👍

myleftshoe commented 2 years ago

You're right - I overlooked that - refreshing the page resets the icon. I doubt the users will even notice as I didn't, as long as the theme and icon change when they press the button.

myleftshoe commented 2 years ago

Okay, this works as a starting point:

The code in :else has just inverted swap-on and swap-off

Relevant code in __layout.svelte:

[EDIT]: this is the way - just added checked={theme === 'light'} to checkbox input

<script>
    import { browser } from '$app/env';
    const theme = browser && localStorage.getItem('theme');
</script>
...

<label class="swap swap-rotate">
    <input type="checkbox" data-toggle-theme="dark,light" data-act-class="ACTIVECLASS" checked={theme === 'light'} />
    <svg class="swap-on fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
    </svg>
    <svg class="swap-off fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
    </svg>
</label>
d3tr0id commented 2 years ago

this works thanks! I was trying stuff out with observers and mutations to check on the data-theme attributesvalue change from the html element. Still trying to get it work, but your solution is much better! But still im curious how to get this working with the swap-active class that was made for this use case?

myleftshoe commented 2 years ago

@d3tr0id I edited my comment but don't know if github notifies on edits. It's a better / more obvious solution.

d3tr0id commented 2 years ago

Saw it. Works like a charm. Thanks for your time man really appreciate it!

myleftshoe commented 2 years ago

Made me curious as well - here it is using swap-active but it's more complex.

<script>
    import { browser } from '$app/env';

    let theme = browser && localStorage.getItem('theme');

    function toggleTheme(e) {
        if (theme === 'light')
            theme = 'dark'
        else
            theme = 'light'
    }
</script>

<label data-toggle-theme="dark,light" class="swap swap-rotate { theme === 'dark' ? 'swap-active' : '' }" on:click={toggleTheme}>
    <svg class="swap-on fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
    </svg>
    <svg class="swap-off fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
    </svg>
</label>
souravjamwal77 commented 2 years ago

@myleftshoe, How would I make a component from this and import in __layout.svelte file?

spences10 commented 2 years ago

@myleftshoe, How would I make a component from this and import in __layout.svelte file?

I made an example project that I use on the regular here: https://github.com/spences10/sveltekit-theme-switch-example

souravjamwal77 commented 2 years ago

Thanks @spences10. I have two more questions.

  1. For a split second, the theme shows the default(light) theme before changing to the user's set theme, is there a workaround for this?
  2. To set the OS preferred theme the code is mentioned here but I wasn't able to set the OS preferred dark theme. Can we do this in SvelteKit?
saadeghi commented 2 years ago

That split second is because HTML and CSS load first then you see the page with default theme, then JS loads and reads the local storage and switches the theme.

If we find a way to load the JS before the page load, this will be fixed but I think it will result negative effect on page load speed. For example if you use the CDN file of theme-change and put it in the <head> before every other file, you can see the difference.

myleftshoe commented 2 years ago

Check out this youtube vid for a comprehensive solution including the initial flash.

How to: SvelteKit SSR Dark Mode Disclaimer: I haven't tried it myself

myleftshoe commented 2 years ago

Here's a ThemeManagerComponent i made for own project - you still get the flash at the start.

It's a self contained component - everything you need is in that folder. Maybe it's more than you need - it also changes the color of the surrounding UI on iOS PWA to match the appbar color.

And this is the __layout.svelte that uses it.

Feel free to copy the code, it's public.

plckr commented 1 year ago

here's my solution using CSS only. It looks forhtml data-theme attribute and changes opacities accordingly

Using DaisyUI ThemeSwitch.svelte

<label class="swap">
  <input
    type="checkbox"
    data-toggle-theme="dark,winter"
    data-act-class="ACTIVECLASS"
  />
  <svg
    class="swap-on fill-current w-10 h-10"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
  >
    <path
      d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"
    />
  </svg>
  <svg
    class="swap-off fill-current w-10 h-10"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
  >
    <path
      d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"
    />
  </svg>
</label>

<style>
  :global(html[data-theme='dark']) label > svg:first-of-type {
    opacity: 0;
  }
  :global(html[data-theme='dark']) label > svg:last-of-type {
    opacity: 1;
  }
  :global(html[data-theme='winter']) svg:first-of-type {
    opacity: 1;
  }
  :global(html[data-theme='winter']) svg:last-of-type {
    opacity: 0;
  }
</style>
wighawag commented 1 year ago

For getting rid of the flash you can inject the following in app.html's <head>...</head>:

<script>try{document.documentElement.setAttribute('data-theme',localStorage.getItem('theme'));}catch(e){}</script>
spences10 commented 1 year ago

For getting rid of the flash you can inject the following in app.html's <head>...</head>:

<script>try{document.documentElement.setAttribute('data-theme',localStorage.getItem('theme'));}catch(e){}</script>

THIS!

I wish I read this before going down the cookie route! 😅

Anyone else wanting to use a theme cookie check out the updated version of the spences10/sveltekit-theme-switch-example

Also see the blog post on how it's done: https://scottspence.com/posts/cookie-based-theme-selection-in-sveltekit-with-daisyui

saadeghi commented 1 year ago

@spences10 but doesn't this affect the lighthouse score because of blocking the HTML load? I haven't tried it myself but I thought it would block the HTML load.

spences10 commented 1 year ago

@spences10 but doesn't this affect the lighthouse score because of blocking the HTML load? I haven't tried it myself but I thought it would block the HTML load.

Using the <script> tag approach or cookie @saadeghi?

I haven't tried the scripty way so not tested it

saadeghi commented 1 year ago

@spences10 The inline script. I was afraid something like this would block the page load and lower lighthouse performance score but I can confirm that I tried it and everything's fine.