Criando o famoso dark mode com VueJS + Vuex
Objetivo dessa postagem é criar dark mode de uma forma bem simples, iremos criar uma diretiva customizada no Vuejs e fazer com que ela interaja com Vuex para manter o estado através das páginas.
O código final está disponível no repositório abaixo.
Primeiro vamos instalar o Vue CLI para que possamos criar o nosso projeto de uma forma mais simplificada.
npm install -g @vue/cli
# OU
yarn global add @vue/cli
Logo após a instalação do Vue CLI, vamos rodar o comando abaixo para criar o nosso projeto propriamente dito.
vue create vue-darkmode
Na sequência será apresentado no terminal algumas opções para inicialização do projeto.
Please pick a preset: (Use arrow keys)
Vue TS Default (babel, typescript, router, vuex, eslint, unit-mocha)
TS & Pre Processor (node-sass, babel, typescript, pwa, router, vuex, eslint, unit-mocha)
default (babel, eslint)
>Manually select features
Basta utilizar as setas do teclado(cima, baixo) pra escolher a opção e pressionar enter, nesse exemplo iremos utilizar a ultima "Manually select features".
Será exibido as opções abaixo, nesse caso para selecionar as opções deve utilizar as setas para navegar pelo menu e pressionar barra de espaço.
? Please pick a preset: Manually select features
? Check the features needed for your project:
>(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
(*) Vuex
(*) CSS Pre-processors
( ) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
Nesse exemplo iremos utilizar opções Babel, Router, Vuex e CSS Pre-processors, depois de selecionado aperte enter.
Teremos o resultado abaixo, mostrando as opções selecionadas e se você quer utilizar o history mode do VueJs.
Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
O history mode remove o "#" dar url do VueJs, para que isso funcione ele dá um aviso que irá precisar de uma configuração no servidor de produção. Bastar digitar Y e dar enter.
Agora irá perguntar qual vai ser o pré processador de css.
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
Sass/SCSS (with dart-sass)
> Sass/SCSS (with node-sass)
Less
Stylus
Vamos utilizar o Sass/SCSS (with node-sass), basta selecionar com as setas e apertar enter novamente.
Agora pergunta onde você prefere que as configurações do Babel, ESLint e etc fiquem. Vamos deixar no package.json mesmo
? Where do you prefer placing config for Babel, ESLint, etc.?
In dedicated config files
> In package.json
Selecione a opção package.json e aperte enter
Agora a ultima etapa ufffaaaa, ele pergunta se queremos salvar essas configurações para projetos futuros, ou seja não iriamos precisar fazer tudo isso novamente.
? Save this as a preset for future projects? (y/N)
Agora digite "n" e espere a finalização de instalação das dependências
Vue CLI v4.2.3
✨ Creating project in D:\estudo\blog\vue-darkmode-lab\vue-darkmode.
⚙️ Installing CLI plugins. This might take a while...
yarn install v1.22.0
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.12: The platform "win32" is incompatible with this module.
info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
success Saved lockfile.
Done in 20.57s.
�🚀 Invoking generators...
�📦 Installing additional dependencies...
yarn install v1.22.0
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.12: The platform "win32" is incompatible with this module.
info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 7.20s.
⚓ Running completion hooks...
�🚀 Generating README.md...
�🎉 Successfully created project vue-darkmode.
�👉 Get started with the following commands:
Agora já podemos executar e ver nossa aplicação funcionando.
yarn serve
OU
Vamos criar um diretório chamado "directives" dentro da pasta "src" e criar um arquivo chamado "dark_mode.js" nele vamos ter o seguinte conteudo:
import Vue from "vue";
const darkModeClass = "theme--dark";
//Função para adicionar a classe css theme--dark
function addDarkModeStyle(el, binding, vnode) {
if (vnode.context?.$store?.state?.darkMode) {
if (el.className.split(" ").filter((x) => x == darkModeClass).length == 0) {
el.className += " " + darkModeClass;
}
}
if (!vnode.context?.$store?.state?.darkMode) {
el.className = el.className.replace(darkModeClass, "").trim();
}
}
Vue.directive("dark-mode", {
update: function(el, binding, vnode) {
addDarkModeStyle(el, binding, vnode);
},
bind: function(el, binding, vnode) {
addDarkModeStyle(el, binding, vnode);
},
});
No código acima criamos a diretiva chamada "dark-mode" essa diretiva vai basicamente checar se o no Vuex está ativo o tema.
O que eu costumo fazer é criar um arquivo chamado index.js dentro da pasta directives e centralizar todas as minhas directivas personalizadas, como nesse exemplo só temos uma não faria muito sentido, porem quero compartilhar a maneira que faço.
O nosso arquivo /src/directives/index.js ficou dessa forma:
import "./dark_mode";
E vamos adicionar nossa diretiva no arquivo main.js do projeto.
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "@/directives/index";
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
Precisamos alterar o nosso arquivo public/index.html vamos mudar para ao invés do VueJS inicializar na div inicializar no body do html, para isso precisamos apagar a <div id="app"> </div> e deixar conforme abaixo.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body id="app">
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
</body>
</html>
Agora precisamos fazer um pequena alteração no arquivo src/App.vue que seria alterar o elemento do principal alterar <div id="app"> por <body id="app">, pois como mudamos o arquivo index.html para injetar o elemento raiz do VueJS no body, precisamos alterar para que fique correto.
<template>
<body id="app" v-dark-mode>
<div id="nav">
<router-link to="/">Home</router-link>|
<router-link to="/about">About</router-link>
</div>
<router-view />
</body>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
Vamos alterar o arquivo /src/store/index.js que é nosso store principal e deixar da seguinte forma:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//estado inicial do Vuex
state: {
darkMode: false,
},
mutations: {
//metodo para alterar o modo atual de template
toggleDarkMode(state) {
state.darkMode = !state.darkMode;
},
},
actions: {},
modules: {},
});
Agora no arquivo src/App.vue vamos criar um método para fazer a chamada desse método que altera o "state" no Vuex. E criar um link no topo para executar o mesmo. Adicionamos também uma declaração css utilizando essa nova classe que vai ser adicionada ao componente que está utilizando a nossa diretiva "v-dark-mode".
<template>
<body id="app" v-dark-mode>
<div id="nav">
<router-link to="/">Home</router-link>|
<router-link to="/about">About</router-link>|
<a @click="toggleDarkMode" style="cursor:pointer;">Alterar Modo do Template</a>
</div>
<router-view />
</body>
</template>
<script>
export default {
methods: {
toggleDarkMode: function() {
//chamada do metodo no toggleDarkMode criado no Vuex
this.$store.commit({ type: "toggleDarkMode" });
}
}
};
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#app.theme--dark {
color: white;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
.theme--dark #nav {
a {
font-weight: bold;
color: #476380;
&.router-link-exact-active {
color: #42b983;
}
}
}
body.theme--dark {
background-color: black;
}
</style>
A partir desse ponto já podemos visualizar as alterações conforme abaixo:


Com isso concluímos a configuração básica para o dark mode, portanto basta adicionar o css customizado quando o modo estiver ativo.
Para escrever o css do dark mode podemos fazer de duas formas, adicionar a diretiva v-dark-mode no componente desejado, ou podemos criar um css global e adicionar a diretiva somente no elemento body, igual ao que foi feito nesse post, veja o exemplo a seguir para um elemento input.
/*Css utilizado quando o elemento html contém a diretiva v-dark-mode */
input.theme--dark{
background-color:black;
}
/* ou
utilizando somente a diretiva no elemento body
*/
.theme--dark input{
background-color: black;
}
Com isso concluímos o tutorial, qualquer dúvida basta deixar nos comentários ou utilize o formulário de contato.
O repositório desse exemplo pode ser baixado em: https://github.com/hederson/vue-darkmode-lab.git/