DEV Community

Brewhouse Digital
Brewhouse Digital

Posted on

Setting a Theme in Svelte using Hooks

This is a follow-up post to Secure Authentication in Svelte using Hooks to expand on our Hooks file. If you're looking for a way to implement authentication in your SvelteKit app, definitely check out that article.

If you've ever built a site with multiple themes, there is a chance you've run into the annoying problem of having the site flash before the theme value is calculated client side. In this tutorial, we'll setup the hooks.server.js file to allow for a dynamic theme to be processed before sending it to the client.

Initial Setup

If you don't already have a hooks.server.js file, the most basic version looks like this:

// hooks.server.js
export const handle = async({event, resolve}) => {
  const response = await resolve(event);

  return response;
}
Enter fullscreen mode Exit fullscreen mode

This is explained in the previous post, but here is a refresher for any new readers:

The event property contains all the information related to the file request. This includes user's cookies, the browser http headers, and the URL of the specific request. You can read more indepth docs here: SvelteKit Hooks.

The second item, resolve, is the function that creates the finished HTML.

Inside our handle() function is the await resolve(event) call. This is a SvelteKit thing that essentially tells the server that it is ready to build the HTML before sending it to the client. Returning that response value renders the page as normal.

In this tutorial, we'll be using a simple HTML data attribute to handle which CSS will be used. In your app.html page, go ahead and add this to your <html> tag like so:

<html lang="en" data-theme="">
Enter fullscreen mode Exit fullscreen mode

That is all the setup we need for the HTML page. Go ahead and close that and lets open up our hooks.server.js file.

Now we want to modify our resolve() function to look for the string data-theme="" and replace it with the user's theme cookie value. We can do that like this:

// hooks.server.js
export const handle = async({event, resolve}) => {
  const response = await resolve(event, {
    // Processing will go here
  });

  return response;
}
Enter fullscreen mode Exit fullscreen mode

First step is to add an object as the second property of resolve(). Inside here, we can call the Svelte object property transformPageChunk, and pass it a function. That will look like this:

// hooks.server.js
export const handle = async({event, resolve}) => {
  const response = await resolve(event, {
    transformPageChunk: ({html}) => {
      // This section will modify the HTML 
      // before being returned to the client
    }
  });

  return response;
}
Enter fullscreen mode Exit fullscreen mode

We want to first check if the user's theme cookie even exists. This could be a new user without a cookie, or an existing user with one. An easy way to do that is to check the event.cookies that is passed from the root handle() function

// hooks.server.js
export const handle = async({event, resolve}) => {
  const response = await resolve(event, {
    transformPageChunk: ({html}) => {
      // This section will modify the HTML 
      // before being returned to the client
      let currentTheme = cookies.get("theme");

      // Make sure the cookie was found, if not, set it to dark
      if(!currentTheme) {
        currentTheme = "dark";
        cookies.set("theme", currentTheme)
      }
    }
  });

  return response;
}
Enter fullscreen mode Exit fullscreen mode

This value will return null if it doesn't exist, so it is useful to add in a default. We can also use this as an opportunity to set a theme cookie for all new users. In the above code, we're setting the default to dark.

Now that we have our theme value ready, we can update the HTML before being sent to the client. The easiest way to do that is by using the replace() javascript function on the entire HTML. It is useful to note that replace() is non-destructive so if you want to do additional processing, you must save it into a new variable.

// hooks.server.js
export const handle = async({event, resolve}) => {
  const response = await resolve(event, {
    transformPageChunk: ({html}) => {
      // This section will modify the HTML 
      // before being returned to the client
      let currentTheme = cookies.get("theme");

      // Make sure the cookie was found, if not, set it to dark
      if(!currentTheme) {
        currentTheme = "dark";
        cookies.set("theme", currentTheme)
      }

      return html.replace(`data-theme=""`, `data-theme="${currentTheme}"`);
    }
  });

  return response;
}
Enter fullscreen mode Exit fullscreen mode

And just like that, your theme is now being processed before being sent to the client side. No more flashing!

CSS Example

If you're curious how to use CSS to access this, you can utilize CSS variables like so:

:root {
  --body: #fff;
  --text: #000;
}

[data-theme='dark']:root {
  --body: #000;
  --text: #fff;
}

body {
  background-color: var(--body);
  color: var(--text);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
fxmt2009 profile image
Ade

where you add the css? In +layout.svelte?

Collapse
 
fxmt2009 profile image
Ade

This is fantastic writing and simple way to add themes. You should write another article to expand on transformPageChunk(), what is it and how to use it to achieve more functionalities.