DEV Community

Nutchapon Makelai
Nutchapon Makelai

Posted on

Stop flickering theme after page refresh in Sveltekit and Daisy UI

Hi, Sveltian today I would like to share you how to stop theme flickering after we refreshing a page while we using Daisy UI with Sveltekit


New in Daisy UI 4.0

Now with Daisy UI 4.0 has been release with new component call Theme Controller it bring us to a more easier way to change between two theme in Daisy UI , for more info about what change and what new in Daisy UI 4.0 let check this link


Let see what Theme Controller can do

Now I expected you has been installing a new Sveltekit project (with a Skeleton Project option selected)

Image description

Tailwind CSS and Daisy UI , then we will starting our code

  • checking you tailwind.config.js , need to have at least 2 of any themes (how to setup themes)

I have set 2 customize theme name 'light' and 'dark'

theme name '**light**' and '**dark**'

  • adding attribute data-theme on html tag in src/app.html page

for me will set it as 'light' theme by default while page is loaded

Image description

  • build svelte component name 'ModeSwitcher' (this name is up to your decide) and in that svelte component put below code into it and using this component on +layout.svelte

I has using Theme Controller using a swap component from DaisyUI and set it value to 'dark' as it is my dark mode theme name

<label class="swap swap-rotate">
    <!-- this hidden checkbox controls the state -->
    <input
        type="checkbox"
        class="theme-controller"
        value="dark"
    />

    <!-- sun icon -->
    <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>

    <!-- moon icon -->
    <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>
Enter fullscreen mode Exit fullscreen mode

now you can running your dev server and using the that input checkbox (or now it is a sun & moon button) to changing between two themes , for me it will change between theme name 'light' and 'dark'

Image description

BUT!! you will notice that while we reload or refresh the page from a dark theme it will be reloaded back to a default theme that we has been set in html tag from previously


Made theme persistent

firstly I will introduce you to a basically way to persist a theme by using cookie and onMount function of svelte if self

let copy a code below and paste it in your current component

<script>
    import { onMount } from 'svelte';

    let isDark = false;
    onMount(() => {
        let decodedCookie = decodeURIComponent(document.cookie);
        let ca = decodedCookie.split(';');
        const cookiesObject = ca.map((cookie) => {
          const [name, value] = cookie.split('=');
          return { name, value };
        });
        const cookieIsDark = cookiesObject.find(cookie=> cookie.name==='isDark');
        isDark = JSON.parse(cookieIsDark ? cookieIsDark.value : "false") ;
    });


    function setModeCookie() {
        const expirationDate = new Date();
        expirationDate.setDate(expirationDate.getDate() + 365);
        document.cookie = `isDark=${isDark}; expires=${expirationDate.toUTCString()}`;
    }
</script>

<label class="swap swap-rotate">
    <!-- this hidden checkbox controls the state -->
    <input
        type="checkbox"
        class="theme-controller"
        value="dark"
    />

    <!-- sun icon -->
    <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>

    <!-- moon icon -->
    <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>
Enter fullscreen mode Exit fullscreen mode

In the code while the component is onMount state it will decoding and get a value of cookie name 'isDark' that data type is boolean and will tell us the page currently in dark mode or not and it value will binding with input checked state

for a first time it will be set to false because page didn't have any cookie yet

and after we changing a theme with our theme controller, it will calling setModeCookie function that will set a cookie name 'isDark' with value of our 'isDark' variable (that suppose to be true by now because we clicked a theme controller and change a page's theme to dark)

now if you try to refresh or reload the page, the theme will be back into a theme that we has been using before we refresh or reload the page

Image description

BUT AGAIN!! you will notice some flickering effect after we refresh or reload the page while on 'dark' theme that because the page will be rendered from server side as a 'light' theme and then after 'onMount' is complete the page will get a cookies and set it back to our theme controller to made the page to be in 'dark' theme again

Image description


Stop theme flickering effect!!!

So far we has been using cookie to set a value that telling us, is currently page is in dark mode or not then in Sveltekit we can accessing that cookie on server side too !!
then what we need to do is we will calling that cookie in server side and passing it value back to our page and the page will rendered on value of that passing from server side

let create new file in src folder and name it +layout.server.js and in that file put below code into it

/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies }) {
    const isDark = cookies.get('isDark') ?? "false";
    return { isDark };
}
Enter fullscreen mode Exit fullscreen mode

and put this code below into ModeSwitcher component

<script>
    import { page } from '$app/stores';

    let isDark = JSON.parse($page.data.isDark);

    function setModeCookie() {
        const expirationDate = new Date();
        expirationDate.setDate(expirationDate.getDate() + 365);
        document.cookie = `isDark=${isDark}; expires=${expirationDate.toUTCString()};`;
    }
</script>

<label class="swap swap-rotate">
    <!-- this hidden checkbox controls the state -->
    <input
        type="checkbox"
        class="theme-controller"
        value="dark"
    />

    <!-- sun icon -->
    <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>

    <!-- moon icon -->
    <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>
Enter fullscreen mode Exit fullscreen mode

then if you see a code in +layout.server.js we will try to get a value of cookie with name 'isDark' and if it not exist just passing "false" as a string and made our ModeSwitcher component receiving that value by using import { page } from '$app/stores'; this will get a store of page that we will need to subscribe and call it data by $page.data.isDark , last but not less we will set that value back to ModeSwitcher component 's variable name 'isDark' and that variable is got binding on our input checkbox of Theme Controller

that mean while page is rendered, a 'isDark' value will set into a value of our cookie that passing by server side and then it will rendered to correctly theme that suppose to be show in the page with out flickering effect at all

Top comments (1)

Collapse
 
palyfight profile image
Chris Bama

Great stuff, thank you for explaining the logic! The code snippets are incomplete but it's not too hard to fix.

I just needed to bind isDark to the input's checked attribute and also add an on:click event to the input to call the setModeCookie() function