vue.jsvuejs3

What is the best way to change body background color when changing theme?


I am new to Vue.

I made a change of theme from dark to light and back. I use Pinia (currentThemeStore). Now the color of the body background changes by the function so:

//currentThemeStore.ColorForBodyBackground returns string - 'white' or 'black'
document.body.style.backgroundColor = currentThemeStore.ColorForBodyBackground;

Meanwhile, in components the styles change differently, like this:

<div :class="$style[currentThemeStore.SomeStyle]">Some text</div>

//currentThemeStore.SomeStyle returns string - 'some_style_dark' or 'some_style_light'
<style lang="scss" module>
.some_style_dark {
    //...
}

.some_style_light {
    //...
}
</style>

Question: Is there a better way to dynamically change the body background color without

document.body.style.backgroundColor = currentThemeStore.ColorForBodyBackground;

I've been racking my brains and have no solution. Which solution is the best?


Solution

  • To answer your question: no, there is no better way to change body's background color. That's the way to do it. Arguably, applying a class might be seen by some as superior, but it has the drawback of having to deal with CSS specificity and sometimes might fail, which is not the case with the method you're using.

    However, the vast majority of experienced web developers would agree that's not the right thing to do in your situation. As a general principle, your app should not interfere with body — if at all possible (some things can't be achieved without it) –, and should even be able to function in a context where document and/or body are not defined (useful is testing, or when mounting the app in an <iframe /> or other similar contexts/viewports, etc...)

    Therefore, you might want the #app element to perform the same function as the body performs, visually. And by that I mean, make sure it occupies the full size of the current viewport:

    body { margin: 0; }
    #app { 
     height: 100vh;
     width: 100vw;
     overflow: auto;
    }
    

    Now you can safely apply dynamic classes and/or styles to the #app element and, most importantly, the background color of what the user percieves as <body /> can be data driven and set without any form of direct DOM manipulation, which is the recommended way of using Vue.


    The main difference between your solution and what I'm suggesting is that your solution errors (and stops execution, breaking the app) when rendered in a context without a <body /> element whereas mine doesn't. Coding without relying on document and body is considered good practice when developing with Vue.