Nuxt.js app from UI kit to deployment. Part 2: Dark theme

Hello, Habr!

We are publishing the second part of a series of articles on creating a modern blog with Nuxt.js. Today we will implement the dark theme in the application that we wrote together with you in the first part.

Note that the code for each part can be found in its own branch at Githuband in master the version of the application from the last published article is available.

What is dark theme?

Dark theme Is a color scheme for any interface that displays light text and interface elements on a dark background, making it easier to view the screen on mobile phones, tablets, and computers in low light conditions. The dark theme reduces the light emitted from the screen while maintaining the minimum color contrast ratio needed for legibility.

The dark theme improves visual ergonomics by reducing eye strain by adjusting the screen to suit current lighting conditions and providing ease of use at night or in the dark.

Also, keep in mind that using the dark theme in web and mobile applications can extend the battery life of your device. Google confirmedthat dark theme on OLED screens helps a lot to extend battery life.

@ nuxtjs / color-mode

To implement the dark theme, we will use the module @ nuxtjs / color-modewhich provides the following features:

  • adds class .${color}-mode to the tag <html> to simplify the management of CSS themes;
  • works in any mode Nuxt (static, ssr or spa);
  • automatically detects the color mode of the system on the user’s device and can set the appropriate theme based on this data;
  • allows you to synchronize the selected theme between tabs and windows;
  • allows you to use the implemented themes for individual pages rather than for the entire application (ideal for incremental development);
  • the module also supports IE9 + (I’m not sure if this is still relevant in modern development, but it might be useful to someone).

First, let’s install the module:

npm i --save-dev @nuxtjs/color-mode

And then add information about this module to the section buildModules in file nuxt.config.js:

{
  buildModules: [
    '@nuxtjs/color-mode'
  ]
}

Fine! Now if we run our application and open the tab Elements in the developer console, we will see that the tag html added a class that matches the theme of the operating system, for example, in our case class="light-mode"

Theme switcher

In the next step, let’s implement a switch that will change the dark theme to the light theme and vice versa.

If you look at design of our application in Figma, then we will see that next to the theme switcher is also a language switcher, which we will implement in one of the next articles in this series.

Let’s immediately write a wrapper component that will encapsulate these switches and be responsible for margins before other components.

To do this, we will create a component AppOptions with the following content:

<template lang="pug">
section.section
  .content
    .app-options
      switcher-color-mode
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'AppOptions',
})
</script>

<style lang="scss" scoped>
.app-options {
  display: flex;
  margin-top: 24px;
}
</style>

Component on Github

As we can see, there is no logic in this component, it just sets margins for nested components. We now have only one nested component switcher-color-mode, let’s implement it.

Let’s take a look at the section script of this component:

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'SwitcherColorMode',

  computed: {
    icon() {
      return (this as any).$colorMode.value === 'light'
        ? 'assets/icons/sun.svg'
        : 'assets/icons/moon.svg'
    },
  },

  methods: {
    changeColorMode() {
      ;(this as any).$colorMode.preference =
        (this as any).$colorMode.value === 'light' ? 'dark' : 'light'
    },
  },
})
</script>

Here we are implementing the method changeColorModewhich changes theme in the object provided by the module @nuxtjs/color-mode

When changing the value $colorMode.preference the corresponding class of the tag will also be set html: class="light-mode" or class="dark-mode"

In addition, there is a computed property icon, which returns the icon we need, depending on the selected theme. Please note that for correct operation you need to add icons sun.svg and moon.svg to directory assets/icons

The component template will look like this:

<template lang="pug">
button(@click="changeColorMode")
  img(
    alt="theme-icon"
    :src="getDynamicFile(icon)"
  )
</template>

Everything is quite simple here! We have a button, when we click on which we call the method changeColorMode and change our theme. Inside the button, we show an image of the selected theme.

Component on Github

It remains only to add this component to the main page of our application. After that, the page template should look like this:

<template lang="pug">
.page
  section-header(
    title="Nuxt blog"
    subtitle="The best blog you can find on the global internet"
  )

  app-options

  post-list
</template>

Variable management

As you may remember from the first part, to define all the colors in the application, we used scss variables, and now all we have to do is change the values ​​of these variables depending on the selected theme.

But the problem is that scss variables are set once when building the application and we cannot redefine them later when changing the theme.

This limitation can be worked around with js, but there is a much simpler solution: we can use native css variables.

Now in our file with variables assets/styles/variables.scss the section with flowers looks like this:

// colors  
$text-primary:                      rgb(22, 22, 23);  
$text-secondary:                    rgb(110, 109, 122);  
$line-color:                        rgb(231, 231, 233);  
$background-color:                  rgb(243, 243, 244);  
$html-background-color:             rgb(255, 255, 255);

Let’s first define two color schemes in the same file – light and dark – using css variables:

:root {
  // light theme
  --text-primary:                   rgb(22, 22, 23);  
  --text-secondary:                 rgb(110, 109, 122);  
  --line-color:                     rgb(231, 231, 233);  
  --background-color:               rgb(243, 243, 244);  
  --html-background-color:          rgb(255, 255, 255);  
  
  // dark theme  
  &.dark-mode {
    --text-primary:                 rgb(250, 250, 250);  
    --text-secondary:               rgb(188, 187, 201);  
    --line-color:                   rgb(45, 55, 72);  
    --background-color:             rgb(45, 55, 72);  
    --html-background-color:        rgb(26, 32, 44);  
  }  
}

We defined css variables in the selector :root… Standard css the variable is set and used with a prefix --

ABOUT css pseudo class :root can be read on MDN and W3Schools… Quote from MDN:

css pseudo-class :root finds the root element of the document tree. Applies to HTML, :root finds a tag html and is identical to the selector on the html tag, but its specificity higher.

As we can see, those colors that were previously written directly in scss variables are now listed in css variables as default values, and if there is a class .dark-mode these values ​​are overridden.

Now our scss variables with colors will look like this:

$text-primary:                      var(--text-primary);  
$text-secondary:                    var(--text-secondary);  
$line-color:                        var(--line-color);  
$background-color:                  var(--background-color);  
$html-background-color:             var(--html-background-color);

When switching the theme, the color scheme will change according to the given values ​​and we do not need to change anything in the already implemented components.

Conclusion

Thanks to this article, we learned how to implement a dark theme for a Nuxt.js application.

Did you manage to complete all the steps? Do you think the dark theme is just a hype or is it a necessity? Share your thoughts in the comments.

Links to required materials:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *