1. Introduction

With “light/dark mode” switching now available on all systems, and Chrome and Edge allowing you to switch themes based on your system, as pioneers of the Internet (yes, we’re talking about you), we need to jump on the bandwagon. So in this article you can learn how to use dark mode in your own Vuepress blog. preview

Premise: If you want to do this job, you need a certain amount of knowledge:

  • Experience with Vue development
  • Learn about Stylus usage (a few minutes)
  • For a little knowledge of Vuepress custom configuration, please refer to “Vuepress Transformation Guide”.

Of course, if you simply want to make a beautiful blog site, but also want to support dark mode, it is recommended to directly use “vuepress-theme-reco” theme, easy to configure, easy to write documentation. If you want your own topic and want to exercise yourself, then this article is definitely for you.

High-powered reminder: Because I am a bit verbose, I will introduce more details below. If you have strong ability, you can use the index next to the directory to jump to see. I will also try to remind you in important places. I worked on the dark mode from 8 a.m. to 2 p.m., so it’s worth keeping track of today’s work.

How many steps does it take to make a great dark pattern?

  1. Design light and dark color combinations;
  2. Test the performance of light and dark colors separately;
  3. Add automatic and manual switch theme function;

2. Design theme color scheme

Although I am not a designer, but there is a certain pursuit of beauty, so when matching colors can not blind match, there should be rules! Otherwise the color will be ugly and kill Matt, so I learned the dark color pattern for a long time.

The night before I went to bed, I read “8 Design Essentials of Dark mode” to learn about what to look for in color matching, then read through “Text Color matching in Dark Mode” to learn how to use color shading to change themes, and also read “iOS 13 Guide for UX/UI Designers”.

I couldn’t sleep at night, but I finally decided to copy minority’s color scheme. After all, they do a good job. I’m a layman, and Minority is also a reading site (and one of my favorites).

Therefore, I first analyzed the minority’s light and dark text and background colors (one by one from the color picker). Here are several screenshots from the minority’s official website, with major colors marked on the top (it is not clear, it doesn’t matter, there are color cards at the back) :

Finally, the representative colors are extracted, and several other similar colors can be converted in stylus by the lighten and Darken built-in functions:

// dark mode scheme
$darkMainColor = #f94135 	 / / theme color
$darkPrimaryText = #ffffff   // The primary color of the text
$darkRegularText = #b8b8b8   // Regular text color
$darkSecondaryText = #7f7f7f // Secondary text color
$darkBorder = # 373737        // Border color
$darkBoundary = # 171514      // The boundary color
$darkBgColor = # 171514       // Dark background color
$darkCard = # 232222          // Dark card color
// light mode scheme
$lightMainColor = #d71a1b
$lightPrimaryText = # 292525
$lightSecondaryText = #8e8787
$lightRegularText = #4c4e4d
$lightBorder = #e5e5e5
$lightBoundary = #e5e5e5
$lightBgColor = #f4f4f4
$lightCard = #ffffff
// Mask color added later
$lightMask = rgba(255.255.255.0.9) // Mask color
$darkMask = rgba(0.0.0.0.9)
Copy the code

There is no need to start the variables in stylus with $, but I have added it out of habit.

In order to have better applicability, I added two modes of “mask color” above, of course, on this basis can also be added, such as setting different levels of border color, or adding different box-shadow to achieve better shadow and highlight effect, talented students to add their own. Here is the palette for the two themes:

By the way, as an aside, the minority uses the combination of “pure white background + gray font” and “gray background + light white font” to avoid eye fatigue due to excessive contrast, and the red theme also reduces brightness in dark mode.

3. Test for light and dark colors

Because my personal programming habit was not very good before, I used whatever color I wanted to use when WRITING the page, which led to the “global management” now, very troublesome;

Global color management

So I spent a lot of time in unifying the colors of the whole world. We can save the color of the above to the first vuepress/style/the palette styl convenient (test) inside, so that we can directly use these color in other files in the topic variable does not need to import.

The next step was to change the color of the rest of my page. I used the dumbest method, search and replace (for lack of a better method).

Here we need to replace the confused colors with the colors in the “color palette” above. If we feel that the colors are not enough, we can add appropriate colors.

When testing the styles of the two modes separately, we can’t always search for substitutions. The simplest way to do this is to use variable substitutions. For example, we have:

// .vuepress/style/palette.styl
$darkMainColor = #f94135
$lightMainColor = #d71a1b
// When testing a bright theme
$mainColor = $lightMainColor
// When testing dark themes
$mainColor = $darkMainColor
Copy the code

In this way, we can use $mainColor to indicate the theme color in other documents, which is also to pave the way for the theme switch later. Such as:

<style lang="stylus" scoped>
.tetle
    color $mainColor
</style>
Copy the code

Here are a few things to note:

  1. When it comes to color modification, for better results, it is recommended to modify in the browser “debug window” to see the effect;
  2. The two modes need to be “replaced separately”. When the light mode is tested correctly, the dark mode should be tested again. When both modes are tested separately, the theme should be switched. Avoid making too many changes at once and not knowing what went wrong.
  3. In addition to “color”, the color of the “border” is also something to note.
  4. Either use version control or operate on a copy of the project to avoid irreversible problems.

A custom theme for Element UI

Use third-party libraries in Vuepress

For those of you who use the Element UI component library, changing the colors is acceptable, a little more tedious, but not too difficult to achieve the final result. But because ELement’s UI component library has its own color palette, changing the internal color palette can be tricky. For example, I want to change the TAB border color to a dark color:

I need the following to achieve the effect:

// Does not fully follow the stylus's recommended syntax, writing is messy, please do not follow me
<style lang="stylus">
.el-tabs--card>.el-tabs__header .el-tabs__item.is-active {
  border-bottom: none;
  color: $mainColor;
  font-weight: 600;
}

.el-tabs--card>.el-tabs__header {
  border-bottom: 1px solid $bgColor;
  .el-tabs__item {
    border-left-color: $bgColor
    color: $seconaryColor;
    &:hover {
      color: $mainColor;
    }
  }
}

.el-tabs--card>.el-tabs__header .el-tabs__nav {
  border: 1px solid $bgColor;
  border-bottom: none;
}
</style>
Copy the code

As you can see, making even a small change can be very troublesome. If it involves changing the style of complex components like popovers, it can be very tiring.

So we can use Element UI’s “online Theme Editor” to generate theme files in the dark mode we need, where we can visually change the values of the various color variables. Please refer to the color card above for the color scheme here. Scroll down to see how each component looks. After we have changed the color to the theme we want, click on the upper right corner to “download” the theme profile.

After downloading, you will get a style package with the following directory structure:

Style ├── ├── ├─ ├─ element.├ ─ class.class.cssCopy the code

Because there are two themes that need to be configured, you end up with two styles files in the theme editor of ElementUI. You can put both CSS styles files in the same styles folder and then put them in the project folder. I chose.vuepress/public/style/. The config.json file is not needed for the time being.

Theme ├ ─ ─ fonts │ ├ ─ ─ element - the ICONS. The vera.ttf │ └ ─ ─ element - the ICONS. Woff └ ─ ─ light. The CSS / / we want └ ─ ─ dark. The CSS / / we wantCopy the code

Then we need to introduce the style file in.vuepress/ enhanceapp.js. Since we have not yet added the theme switch function, we will “only” introduce the dark style file when testing the dark mode, and “only” introduce the light style file when testing the light mode.

import Vue from 'vue'
import Element from 'element-ui'
import './public/style/theme/light.css' // Enabled only when testing bright color mode
import './public/style/theme/dark.css'  // Enabled only when testing dark mode
import animated from 'animate.css'

export default ({
  Vue,
}) => {
  Vue.use(Element, animated)
}
Copy the code

In this way, we can happily test the dark color mode. In fact, the test is to see if there is any color wrong and affect the perception, which is mainly reflected in the following places:

  • Contrast is too strong, that is, too dazzling, mainly in the border and font above;
  • Low contrast, fonts or components that are hard to see;
  • It looks normal on the surface, but it exists:hover :focusAfter activation, the style changes;

Here are my results, ok:

4. Realize the switching function of the theme

At this point, it means that the two colors have been tested separately, and the color match is good; Then the following steps can be used for topic switching:

CSS Media Features

A new feature of CSS is prefern-color-scheme, which is used to detect whether users have set their theme colors to bright or dark colors. It is now supported by major browsers.

However, using prefer-color-scheme alone won’t allow you to switch themes manually, nor will you be able to switch themes for ElementUI, so you’ll need to do something else.

CSS variable

I don’t have a clue about how to switch, but later I saw the theme of “vuepress-theme-reco”, the theme switch function of this theme is very good, can you use it for reference, so the theme switch method behind is based on the author’s implementation method. Custom property (–*) : CSS variable

First, we create a style file docs /. / styles/vuepress/theme mode. Styl:

:root
  --main-color $lightMainColor
  --regular-text $lightRegularText
  --secondary-text $lightSecondaryText
  --primary-text $lightPrimaryText
  --bg-color $lightbgColor
  --card-color $lightCard
  --border-color $lightBorder
  --box-shadow $lightShadow
  --mask-color $lightMaskColor
  @media (prefers-color-scheme: dark)
    --main-color $darkMainColor
    --regular-text $darkRegularText
    --secondary-text $darkSecondaryText
    --primary-text $darkPrimaryText
    --bg-color $darkbgColor
    --card-color $darkCard
    --border-color $darkBorder
    --box-shadow $darkShadow
    --mask-color $darkMaskColor
Copy the code

The first line :root is used later; Property names prefixed with — in style files, such as –main–color, represent “custom properties” with values that can be reused across the document using the var function. Such as:

<style lang="stylus" scoped>
.tetle
    color var(--main-color)
</style>
Copy the code

To note here in the docs /. / styles/index vuepress/theme. Styl style of the introduction of the new file:

... @require'./toc'
@require './mode' / / here...Copy the code

Creating a Mode component

First in the docs /. / components/Mode vuepress/theme folder under the new four files, in order not to affect reading experience, also the inside of the file code in the back of this section you can see.

├── ├.js ├─ ├.js ├─ ├.js ├─ ├.js ├.js ├─ ├.js ├.js ├─ ├.js ├.js ├─ ├.jsCopy the code

Call relationship is a bit complicated, students with basic knowledge may be easier to understand, I will briefly introduce the function and working principle of these files:

  • modeOption.jsInside are “color variables” for light and dark colors.
  • applyMode.jsThere are two functions defined thererender (mode)To “render themes”, the main function is to apply color variables to the document (mode.styl).applyMode (mode)Function is to achieve manual and automatic “switch logic”, the principle is very simple, you can read.
  • ModePickerindex.vueIs a component that implements style switching. Read it to understand how it works.

Above is my simplified description of how this works. The core of this method is to dynamically modify CSS variables defined in mode.styl. This allows you to use dynamic CSS variables to represent styles in your project. $MainColor = var(–main-color); The speed of “search and replace” in VS Code is still very fast.

var(--main-color)      <-   $MainColor
var(--regular-text)    <-   $RegularText
var(--secondary-text)  <-   $SecondaryText
var(--primary-text)    <-   $PrimaryText
var(--bg-color)        <-   $bgColor
var(--card-color)      <-   $Card
var(--border-color)    <-   $Border
var(--box-shadow)      <-   $Shadow
var(--mask-color)      <-   $MaskColor
Copy the code

However, this causes a problem, you can’t use the Stylus built-in functions like “lighten” and “darken” after using CSS variables, the lighten(var(–main-color), 30%) doesn’t work, Because the Stylus is preprocessed with variable values that are not yet determined, there is no way to use these built-in functions. (If you have a solution, please let me know.)

modeOption.js

// modeOption.js
const modeOptions = {
    light: {
        '--main-color' : '#d71a1b'.'--regular-text' : '#4c4e4d'.'--secondary-text' : '#8e8787'.'--primary-text' : '# 292525'.'--bg-color' : '#f4f4f4'.'--card-color' : '#ffffff'.'--border-color' : '#e5e5e5'.'--box-shadow' : 'rgba (34, 36, 38, 0.15)'.'--mask-color' : 'rgba (255255255,0.9)'
    },
    dark: {      
        '--main-color' : '#f94135'.'--regular-text' : '#b8b8b8'.'--secondary-text' : '#7f7f7f'.'--primary-text' : '#ffffff'.'--bg-color' : '# 171514'.'--card-color' : '# 232222'.'--border-color' : '# 373737'.'--box-shadow' : 'rgba (34, 36, 38, 0.15)'.'--mask-color' : 'rgba (0,0,0,0.9)'}}export default modeOptions
Copy the code

applyMode.js

// applyMode.js
import modeOptions from './modeOptions'

// Render the theme
function render (mode) {
  // mode.styl
  const rootElement = document.querySelector(':root')
  const options = modeOptions[mode]
  for (const k in options) {
    rootElement.style.setProperty(k, options[k])
  }
  // Modify the body class
  document.getElementsByTagName('body') [0].className = mode + '-mode'
}

/** * Sets a color scheme for the website. * If browser supports "prefers-color-scheme", 'auto' mode will respect the setting for light or dark mode * otherwise it will set a dark theme during night time */
// Apply the theme
export default function applyMode (mode) {
  if(mode ! = ='auto') {
    render(mode)
    return
  }

  const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
  const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches

  if (isDarkMode) render('dark')
  if (isLightMode) render('light')

  if(! isDarkMode && ! isLightMode) {console.log('You specified no preference for a color scheme or your browser does not support it. I schedule dark mode during night time.')
    const hour = new Date().getHours()
    if (hour < 6 || hour >= 18) render('dark')
    else render('light')}}Copy the code

ModePicker.vue

<template> <div class="mode-options"> <h4 class="title"> <ul class="color-mode-options"> <li v-for="(mode, index) in modeOptions" :key="index" :class="getClass(mode.mode)" @click="selectMode(mode.mode)" >{{ mode.title }}</li> </ul> </div> </template> <script> import applyMode from './applyMode.js' export default { name: 'ModeOptions', data () {return {ModeOptions: [{mode: 'dark', title: 'dark'}, {mode: 'auto', title: 'auto'}, {mode: 'light', title: 'light'}], currentMode: 'auto' } }, Mounted () {/ / modePicker open default user setting mode this. CurrentMode = localStorage. The getItem (' mode ') | | this. $themeConfig. Mode | | 'auto' // Dark and Light autoswitches // To avoid being executed on server side, Var that = this window.matchmedia ('(prefern-color scheme: dark)').addListener(() => { that.$data.currentMode === 'auto' && applyMode(that.$data.currentMode) }) window.matchMedia('(prefers-color-scheme: light)').addListener(() => { that.$data.currentMode === 'auto' && applyMode(that.$data.currentMode) }) applyMode(this.currentMode) }, methods: { selectMode (mode) { if (mode ! == this.currentMode) { this.currentMode = mode applyMode(mode) localStorage.setItem('mode', mode) } }, getClass (mode) { return mode ! == this.currentMode ? mode : `${mode} active` } } } </script> <style lang="stylus"> @require '.. /.. /styles/mode.styl' .mode-options background-color var(--bg-color) min-width: 125px; margin: 0; padding: 1em; box-shadow var(--box-shadow); border-radius: 3px; .title margin-top 0 margin-bottom .6rem font-weight bold color var(--regular-text) .color-mode-options display: flex; flex-wrap wrap li text-align: center; font-size 12px color var(--regular-text) line-height 18px padding 3px 6px border-top 1px solid #666 border-bottom 1px solid #666 background-color var(--bg-color) cursor pointer &.dark border-radius: 3px 0 0 3px border-left 1px solid #666 &.light border-radius: 0 3px 3px 0 border-right 1px solid #666 &.active background-color: $accentColor; color #fff &:not(.active) border-right 1px solid #666 </style>Copy the code

index.vue

<template> <div v-click-outside="hideMenu" class="color-picker"> <a class="color-button" @click.prevent="showMenu = ! ShowMenu "> Switch </a> <transition name="menu-transition" mode=" out-of-in "> <div V-show ="showMenu" class="color-picker-menu"> <ModePicker /> </div> </transition> </div> </template> <script> import ClickOutside from 'vue-click-outside' import ModePicker from './ModePicker' import applyMode from './applyMode' export default { name: 'UserSettings', directives: { 'click-outside': ClickOutside }, components: { ModePicker }, data () { return { showMenu: False}}, // To ensure that modePicker is switched correctly in SSR, and to implement management, Mode components will be responsible for modePicker off mounted () {/ / modePicker closed set the default theme pattern const themeMode = this. $themeConfig. Mode | | 'auto' Const {modePicker} = this.$themeConfig if (modePicker === false) {// Set listener for 'auto' mode if (themeMode === 'auto') { window.matchMedia('(prefers-color-scheme: dark)').addListener(() => { applyMode(themeMode) }) window.matchMedia('(prefers-color-scheme: light)').addListener(() => { applyMode(themeMode) }) } applyMode(themeMode) } }, methods: { hideMenu () { this.showMenu = false } } } </script> <style lang="stylus"> @require '.. /.. /styles/mode.styl' .color-picker { position: relative; margin-right: 1em; cursor pointer; .color-button { align-items: center; height: 100%; Iconfont {font-size 1.4rem color: $accentColor}}. Color-picker-menu {position: absolute; top: 40px; left: 50%; transform: translateX(-50%); z-index: 150; &.menu-transition-enter-active, &.menu-transition-leave-active {transition: all 0.25s ease-in-out; } &.menu-transition-enter, &.menu-transition-leave-to { top: 50px; opacity: 0; } ul { list-style-type: none; margin: 0; padding: 0; } } } @media (max-width: $MQMobile) { .color-picker { margin-right: 1rem; .color-picker-menu { left: calc(50% - 35px); &::before { left: calc(50% + 35px); } } } } </style>Copy the code

“Okay, brake, don’t slide back.”

The above file uses a tool that needs to be installed by yourself:

$ yarn add vue-click-outside -D
Copy the code

Then we need to call Mode component in project, here I chose in the docs /. / components/Navbar vuepress/theme. Vue calls inside

<template> <header class="navbar"> <! <div class="links" :style="linksWrapMaxWidth? { 'max-width': linksWrapMaxWidth + 'px' } : {}" > <Mode /> <! <AlgoliaSearchBox v-if="isAlgoliaSearch" :options="algolia" /> <! </div> </header> </template> <script> /* import Mode from '@theme/components/Mode' // import export Default {components: {SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox, Mode},Copy the code

With any luck, you should be able to switch between basic light and dark themes. If you are not so lucky, do a lot of debugging in DevTools, and then find the source of the problem.

At this point, we’re actually one step away…

Dynamically toggle the Element UI theme

The vuepress-theme-reco method allows you to manually switch themes, but does not involve the Element UI theme. The next step is to dynamically switch the Element UI theme.

I don’t know if you’ve noticed, but in applymode. js, there’s this:

Function render (mode) {// mode.styl const rootElement = document.querySelector(':root') const options = modeOptions[mode] for (const k in options) { rootElement.style.setProperty(k, Options [k])} / / modify the body of a document. The getElementsByTagName (' body ') [0]. The className = mode + '- mode'}Copy the code

After adding this sentence, the class of the body element will be changed while dynamically switching the theme. If it is in dark mode, the class of the body element will become dark-mode, and if it is in light mode, the class of the body element will become light-mode. So if we add the.dark-mode namespace to the ElementUI CSS file, we can dynamically switch themes. In other words, the original dark. CSS and light. CSS were defined as follows:

/* dark.css */
.el-button {
    color: red;
}

/* light.css */
.el-button {
    color: blue;
}
Copy the code

When we introduce two style files at the same time, there is no way to switch the theme, so we just add “prefix” to it, we add. Dark-mode to dark.css, and. Light-mode to light.css:

/* dark.css */
.dark-mode .el-button {
    color: red;
}

/* light.css */
.light-mode .el-button {
    color: blue;
}
Copy the code

So when the body class is dark-mode, the dark style will be used, otherwise the light style will be used.

So here’s the problem, prefix is easy to say, but dark. CSS has more than 10,000 lines… This is not something that most people can accomplish.

So use the “gulp” tool:

Start by installing these tools:

$ yarn global add gulp gulp-clean-css gulp-css-wrap
Copy the code

And then create the docs /. Vuepress/public/style/theme/gulpfile js:

// gulpfile.js

var path = require('path')
var gulp = require('gulp')
var cleanCSS = require('gulp-clean-css')
var cssWrap = require('gulp-css-wrap')

gulp.task('css-wrap-dark'.function() {
  return gulp.src( path.resolve('./dark.css')) // Style file
    .pipe(cssWrap({selector: '.dark-mode'}))   // Prefix name
    .pipe(cleanCSS())
    .pipe(gulp.dest('./dist')) // The path to save will generate./dist/dark.css
})

gulp.task('css-wrap-light'.function() {
  return gulp.src( path.resolve('./light.css'))
    .pipe(cssWrap({selector: '.light-mode'}))
    .pipe(cleanCSS())
    .pipe(gulp.dest('./dist'))
})

gulp.task('move-font'.function() {
  return gulp.src(['./fonts/**']).pipe(gulp.dest('./dist/fonts'));
});
Copy the code

I don’t know why there is a problem with combining tasks, just separate them! No trouble:

$ gulp css-wrap-dark
$ gulp css-wrap-light
$ gulp move-font
Copy the code

So that we can get the following directory structure docs /. Vuepress/public/style:

Style ├ ─ ─ config. Json └ ─ ─ theme ├ ─ ─ dist │ ├ ─ ─ fonts │ ├ ─ ─ light. The CSS / / converted stylesheet │ └ ─ ─ dark. The CSS / / converted stylesheet ├ ─ ─ fonts ├ ─ ─ ├─ ├─ ├─ class8 // class8 // class8 // class8 // class8 //Copy the code

Then we “import” both files in enhanceapp.js:

import Vue from 'vue'
import Element from 'element-ui'
import './public/style/theme/dist/light.css' // The transformed style sheet
import './public/style/theme/dist/dark.css'  // The transformed style sheet
import animated from 'animate.css'

export default ({
  Vue,
}) => {
  Vue.use(Element, animated)
}
Copy the code

Knock knock knock, you’re done! Scatter flowers, scatter flowers, scatter flowers

This approach is exactly suitable for our light and dark mode switch, because there are only two theme style files, if there are many styles, it will lead to a large style file, or use other methods, look at the configuration of trouble.

My blog project files can refer to www.github.com/xerrors/Sit…

Summary and references

This article is the longest one I have written since I started the blog site. It took me more than 6 hours to write each process as easily as possible and draw pictures to understand the parts that are difficult to explain. I hope readers can give me some suggestions.

Since the establishment of this blog, the page views of each article are generally in single digits, and the largest one is 100+ page views. The same article on CSDN will have better exposure and more page views. Alas, the way of writing is very long, take your time, I am just a beginner in this field, even not a beginner.

If you find this article helpful to your development, feel free to leave a comment below. What the writer needs most is encouragement and continued support.

[1] vuepress-theme-reco

[2] 8 Design essentials of Dark Mode – Zhihu

[3] Practice: Deconstructing text color matching in dark mode – ZCOOL

[4] iOS 13 Walkthrough for UX/UI Designers – Medium

[5] Minority – Work efficiently and live quality life

[6] vuepress – theme – the default – prefers – color – scheme | rain are silent

[7] Element, an online theme editor

[8] prefers – color – scheme – the CSS (cascading style sheets (CSS) | MDN

[9] VUE – Skin Based on ElementUI [Custom Theme] – CSDN Blog

[10] gulp. Js – based on stream (stream) automated build tools | gulp. Js Chinese website