5

I have a Vuejs single page application that I have built, and I would like to add user-switchable dynamic themes. Something where the user has a button somewhere or something and they can click a 'light' or 'dark' theme option and the whole app updates right away. I would also like to have all of my 'theme' scss files be local.

This answer here is the closest I've found so far, but the themes are not local.

const themes = {
  flatly: "https://bootswatch.com/4/flatly/bootstrap.min.css",
  materia: "https://bootswatch.com/4/materia/bootstrap.min.css",
  solar: "https://bootswatch.com/4/solar/bootstrap.min.css"
};

If I could figure out how to get this answer to work with local theme files that would be great, but switching the link paths out with a local path doesn't work.

const themes = {
  dark: "dark-theme.scss",
  light: "light-theme.scss"
};

My suspicion is that this doesn't work because of Webpack or some other compiling problem where the scss files are not included.

I have found I can get my theme files to appear in the client source if I add something like the following to my index.html <link rel="stylesheet" type="text/css" href="<%= BASE_URL %>dark-theme.scss"> But then I don't know how to reference this in my app. Maybe a link like http://localhost:8080/dark-theme.scss? But then I don't think that would work in production (and it doesn't work locally anyway).

I have also looked at:

  • this which is a similar idea to the answer linked above and runs into similar problems
  • this which only has scss variables and wouldn't be able to add attributes to a class for example
  • this which I don't find to be a very clean solution as it requires all components to know about themes and such

I've also tried doing something like import "@/path/to/theme.scss"; in my main.ts file which does work, but I don't know how I'd leverage that to make the themes switchable. It just adds an element like the following to my head, which has no id or class so I can't effectively query it to switch it out. Using require does something similar as well.

<style type="text/css" >
  ...
</style>
Montana
  • 482
  • 1
  • 4
  • 16
  • 2
    I had a similar problem with a customer some weeks ago. IMHO, the best way to achieve this is to import the two themes as you mentioned in `main.ts`, then using a JavaScript method to supply what media queries doesn’t: I mean, on browsers that don’t support them yet. It’s 3 AM here, but I’ll follow your question and possible answers from tomorrow! – Federico Moretti May 17 '20 at 01:35
  • 1
    @FedericoMoretti Thank you, I'd love to see an answer for this. Let me know if there is any ambiguity in my post that I can clear up. – Montana May 17 '20 at 01:39

2 Answers2

2

main.ts:

import "@/path/to/dark-theme.scss"
import "@/path/to/light-theme.scss"

dark-theme.scss:

body.dark {
  /* rest of the theme */
}

light-theme.scss:

body.ligh {
  /* rest of the theme */
}

index.html (or injecting it from main.ts depending on local-storage or OS's mode):

<body class="light">

Where you have your switch:

methods: {
  toggleTheme() {
    document.body.classList.toggle('dark')
    document.body.classList.toggle('light')
  }
}
Renaud
  • 1,290
  • 8
  • 8
  • 1
    This seems to work well. I'd still prefer if I didn't need the `body.dark` and could just have a scss file with all the css at the root level, but this seems perfectly acceptable. Thanks for the answer. – Montana May 19 '20 at 03:23
2

Here is a trick that came to my mind you can use. Create two empty components for your theme like this:

<template><div></div></template>
<script>export default {name: 'DarkTheme'}</script>
<style>
  @import "~@/path/to/theme.scss"
</style>

now in your app you can use v-if on these two components like

<dark-theme v-if="theme == 'dark'"/>
<light-theme v-if="theme == 'light'"/>

PS: this code is not tested

RoduanKD
  • 1,279
  • 11
  • 27