From 680e4298a80ddb06b0381af48644124ffb0b0c4c Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 28 May 2021 02:11:58 +0800 Subject: [PATCH] feat(theme-default): support dark mode (close #29) BREAKING CHANGE: most sass variables are migrated to css variables --- docs/.vuepress/styles/palette.scss | 3 - docs/reference/default-theme/styles.md | 20 +++- docs/zh/reference/default-theme/styles.md | 20 +++- .../src/client/components/Navbar.vue | 7 ++ .../client/components/ToggleDarkButton.vue | 60 ++++++++++ .../src/client/styles/_variables.scss | 27 +---- .../src/client/styles/_wrapper.scss | 2 +- .../src/client/styles/arrow.scss | 10 +- .../src/client/styles/badge.scss | 19 ++- .../src/client/styles/code-group.scss | 7 +- .../theme-default/src/client/styles/code.scss | 37 ++---- .../src/client/styles/custom-container.scss | 39 ++++--- .../src/client/styles/dropdown.scss | 24 ++-- .../theme-default/src/client/styles/home.scss | 36 +++--- .../src/client/styles/index.scss | 5 +- .../src/client/styles/layout.scss | 42 +++---- .../src/client/styles/navbar.scss | 48 +++++--- .../{_normalize.scss => normalize.scss} | 51 +++++---- .../theme-default/src/client/styles/page.scss | 8 +- .../src/client/styles/plugins.scss | 18 --- .../src/client/styles/sidebar.scss | 12 +- .../src/client/styles/vars-dark.scss | 46 ++++++++ .../theme-default/src/client/styles/vars.scss | 108 ++++++++++++++++++ 23 files changed, 428 insertions(+), 221 deletions(-) delete mode 100644 docs/.vuepress/styles/palette.scss create mode 100644 packages/@vuepress/theme-default/src/client/components/ToggleDarkButton.vue rename packages/@vuepress/theme-default/src/client/styles/{_normalize.scss => normalize.scss} (58%) delete mode 100644 packages/@vuepress/theme-default/src/client/styles/plugins.scss create mode 100644 packages/@vuepress/theme-default/src/client/styles/vars-dark.scss create mode 100644 packages/@vuepress/theme-default/src/client/styles/vars.scss diff --git a/docs/.vuepress/styles/palette.scss b/docs/.vuepress/styles/palette.scss deleted file mode 100644 index e68c5ca2e7..0000000000 --- a/docs/.vuepress/styles/palette.scss +++ /dev/null @@ -1,3 +0,0 @@ -$codeBgColor: #1e1e1e; -$highlightLineBgColor: #3e3e3e; -$lineNumbersColor: #9e9e9e; diff --git a/docs/reference/default-theme/styles.md b/docs/reference/default-theme/styles.md index 870525ac64..5c83064666 100644 --- a/docs/reference/default-theme/styles.md +++ b/docs/reference/default-theme/styles.md @@ -8,16 +8,32 @@ Users can custom style variables via [palette file](#palette-file), and add extr ## Palette File -You can create a `.vuepress/styles/palette.scss` file to override predefined variables of default theme: +The path of palette file is `.vuepress/styles/palette.scss`. +You can make use of it to override predefined SASS variables of default theme. + +::: details Click to expand SASS variables @[code{3-} scss](@vuepress/theme-default/src/client/styles/_variables.scss) +::: ## Style File -You can override default styles or add extra styles in `.vuepress/styles/index.scss` file. For example: +The path of style file is `.vuepress/styles/index.scss`. + +You can add extra styles here, or override default styles: ```scss :root { scroll-behavior: smooth; } ``` + +You can also make use of it to override predefined CSS variables of default theme. + +::: details Click to expand CSS variables +@[code scss](@vuepress/theme-default/src/client/styles/vars.scss) +::: + +::: details Click to expand dark mode CSS variables +@[code scss](@vuepress/theme-default/src/client/styles/vars-dark.scss) +::: diff --git a/docs/zh/reference/default-theme/styles.md b/docs/zh/reference/default-theme/styles.md index 4afde7a1a7..9de408a6db 100644 --- a/docs/zh/reference/default-theme/styles.md +++ b/docs/zh/reference/default-theme/styles.md @@ -8,16 +8,32 @@ ## Palette 文件 -你可以创建一个 `.vuepress/styles/palette.scss` 文件来覆盖默认主题的预定义变量: +Palette 文件的路径是 `.vuepress/styles/palette.scss` 。 +你可以利用它来覆盖默认主题的预定义 SASS 变量。 + +::: details 点击查看 SASS 变量 @[code{3-} scss](@vuepress/theme-default/src/client/styles/_variables.scss) +::: ## Style 文件 -你可以在 `.vuepress/styles/index.scss` 文件中覆盖默认样式或者添加额外样式。例如: +Style 文件的路径是 `.vuepress/styles/index.scss` 。 + +你可以在这里添加额外的样式,或者覆盖默认样式: ```scss :root { scroll-behavior: smooth; } ``` + +你也可以利用它来覆盖默认主题的预定义 CSS 变量。 + +::: details 点击查看 CSS 变量 +@[code scss](@vuepress/theme-default/src/client/styles/vars.scss) +::: + +::: details 点击查看暗黑模式 CSS 变量 +@[code scss](@vuepress/theme-default/src/client/styles/vars-dark.scss) +::: diff --git a/packages/@vuepress/theme-default/src/client/components/Navbar.vue b/packages/@vuepress/theme-default/src/client/components/Navbar.vue index cc56a14bfd..e924d20b09 100644 --- a/packages/@vuepress/theme-default/src/client/components/Navbar.vue +++ b/packages/@vuepress/theme-default/src/client/components/Navbar.vue @@ -23,8 +23,13 @@ @@ -35,6 +40,7 @@ import { computed, defineComponent, onMounted, ref } from 'vue' import { useRouteLocale, useSiteLocaleData, withBase } from '@vuepress/client' import { useThemeLocaleData } from '../composables' import NavbarLinks from './NavbarLinks.vue' +import ToggleDarkButton from './ToggleDarkButton.vue' import ToggleSidebarButton from './ToggleSidebarButton.vue' export default defineComponent({ @@ -42,6 +48,7 @@ export default defineComponent({ components: { NavbarLinks, + ToggleDarkButton, ToggleSidebarButton, }, diff --git a/packages/@vuepress/theme-default/src/client/components/ToggleDarkButton.vue b/packages/@vuepress/theme-default/src/client/components/ToggleDarkButton.vue new file mode 100644 index 0000000000..d5cb0e0069 --- /dev/null +++ b/packages/@vuepress/theme-default/src/client/components/ToggleDarkButton.vue @@ -0,0 +1,60 @@ + + + diff --git a/packages/@vuepress/theme-default/src/client/styles/_variables.scss b/packages/@vuepress/theme-default/src/client/styles/_variables.scss index 8572c3e511..e335053251 100644 --- a/packages/@vuepress/theme-default/src/client/styles/_variables.scss +++ b/packages/@vuepress/theme-default/src/client/styles/_variables.scss @@ -1,36 +1,11 @@ @import '@vuepress/plugin-palette/palette'; -// base colors -$accentColor: #3eaf7c !default; -$textColor: #2c3e50 !default; -$borderColor: #eaecef !default; -$arrowBgColor: #ccc !default; -$tipColor: #42b983 !default; -$warningColor: #e7c000 !default; -$dangerColor: #cc0000 !default; - -// badge component colors -$badgeTipColor: $tipColor !default; -$badgeWarningColor: $warningColor !default; -$badgeDangerColor: $dangerColor !default; - -// layout -$navbarHeight: 3.6rem !default; -$sidebarWidth: 20rem !default; -$mobileSidebarWidth: $sidebarWidth * 0.82 !default; -$contentWidth: 740px !default; -$homePageWidth: 960px !default; - // responsive breakpoints $MQNarrow: 959px !default; $MQMobile: 719px !default; $MQMobileNarrow: 419px !default; -// code blocks -$codeBgColor: #282c34 !default; -$highlightLineBgColor: rgba(0, 0, 0, 66%) !default; -$lineNumbersColor: rgba(255, 255, 255, 0.3) !default; -$lineNumbersWrapperWidth: 3.5rem !default; +// code languages $codeLang: 'c' 'cpp' 'cs' 'css' 'dart' 'docker' 'fs' 'go' 'html' 'java' 'js' 'json' 'kt' 'less' 'makefile' 'md' 'php' 'py' 'rb' 'rs' 'sass' 'scss' 'sh' 'styl' 'ts' 'toml' 'vue' 'yml' !default; diff --git a/packages/@vuepress/theme-default/src/client/styles/_wrapper.scss b/packages/@vuepress/theme-default/src/client/styles/_wrapper.scss index 95d2e13154..83c80fdebe 100644 --- a/packages/@vuepress/theme-default/src/client/styles/_wrapper.scss +++ b/packages/@vuepress/theme-default/src/client/styles/_wrapper.scss @@ -1,7 +1,7 @@ @import '_variables'; %wrapper { - max-width: $contentWidth; + max-width: var(--content-width); margin: 0 auto; padding: 2rem 2.5rem; diff --git a/packages/@vuepress/theme-default/src/client/styles/arrow.scss b/packages/@vuepress/theme-default/src/client/styles/arrow.scss index 135a90c7fb..94a4f79699 100644 --- a/packages/@vuepress/theme-default/src/client/styles/arrow.scss +++ b/packages/@vuepress/theme-default/src/client/styles/arrow.scss @@ -1,5 +1,3 @@ -@import '_variables'; - .arrow { display: inline-block; width: 0; @@ -9,7 +7,7 @@ border: { left: 4px solid transparent; right: 4px solid transparent; - bottom: 6px solid $arrowBgColor; + bottom: 6px solid var(--c-bg-arrow); } } @@ -17,7 +15,7 @@ border: { left: 4px solid transparent; right: 4px solid transparent; - top: 6px solid $arrowBgColor; + top: 6px solid var(--c-bg-arrow); } } @@ -25,7 +23,7 @@ border: { top: 4px solid transparent; bottom: 4px solid transparent; - left: 6px solid $arrowBgColor; + left: 6px solid var(--c-bg-arrow); } } @@ -33,7 +31,7 @@ border: { top: 4px solid transparent; bottom: 4px solid transparent; - right: 6px solid $arrowBgColor; + right: 6px solid var(--c-bg-arrow); } } } diff --git a/packages/@vuepress/theme-default/src/client/styles/badge.scss b/packages/@vuepress/theme-default/src/client/styles/badge.scss index 318950b8c1..d256614949 100644 --- a/packages/@vuepress/theme-default/src/client/styles/badge.scss +++ b/packages/@vuepress/theme-default/src/client/styles/badge.scss @@ -1,5 +1,3 @@ -@import '_variables'; - .badge { display: inline-block; font-size: 14px; @@ -7,24 +5,23 @@ line-height: 18px; border-radius: 3px; padding: 0 6px; - color: white; - background-color: #42b983; + color: var(--c-bg); vertical-align: top; - .table-of-contents & { - vertical-align: middle; - } - &.tip { - background-color: $badgeTipColor; + background-color: var(--c-badge-tip); } &.warning { - background-color: $badgeWarningColor; + background-color: var(--c-badge-warning); } &.danger { - background-color: $badgeDangerColor; + background-color: var(--c-badge-danger); + } + + .table-of-contents & { + vertical-align: middle; } & + & { diff --git a/packages/@vuepress/theme-default/src/client/styles/code-group.scss b/packages/@vuepress/theme-default/src/client/styles/code-group.scss index 8020ce8932..39da14d5c2 100644 --- a/packages/@vuepress/theme-default/src/client/styles/code-group.scss +++ b/packages/@vuepress/theme-default/src/client/styles/code-group.scss @@ -1,5 +1,8 @@ @import '_variables'; +/** + * code-group + */ .code-group__nav { margin-top: 0.85rem; // 2 * margin + border-radius of
 tag
@@ -9,7 +12,7 @@
   padding-top: 10px;
   border-top-left-radius: 6px;
   border-top-right-radius: 6px;
-  background-color: $codeBgColor;
+  background-color: var(--code-bg-color);
 }
 
 .code-group__ul {
@@ -35,7 +38,7 @@
 }
 
 .code-group__nav-tab-active {
-  border-bottom: $accentColor 1px solid;
+  border-bottom: var(--c-brand) 1px solid;
 }
 
 @media (max-width: $MQMobileNarrow) {
diff --git a/packages/@vuepress/theme-default/src/client/styles/code.scss b/packages/@vuepress/theme-default/src/client/styles/code.scss
index 0002cbdc1a..db25e6df1e 100644
--- a/packages/@vuepress/theme-default/src/client/styles/code.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/code.scss
@@ -1,5 +1,3 @@
-@use 'sass:color';
-
 @import '_variables';
 
 // ===============================
@@ -9,7 +7,7 @@ code[class*='language-'],
 pre[class*='language-'] {
   color: #ccc;
   background: none;
-  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  font-family: var(--font-family-code);
   font-size: 1em;
   text-align: left;
   white-space: pre;
@@ -118,22 +116,11 @@ pre[class*='language-'] {
 }
 
 .token.inserted {
-  color: $accentColor;
+  color: var(--c-text-accent);
 }
 
 // ===============================
 
-.theme-default-content {
-  code {
-    color: color.scale($textColor, $lightness: 20%);
-    padding: 0.25rem 0.5rem;
-    margin: 0;
-    font-size: 0.85em;
-    background-color: rgba(27, 31, 35, 0.05);
-    border-radius: 3px;
-  }
-}
-
 .theme-default-content {
   pre,
   pre[class*='language-'] {
@@ -154,13 +141,13 @@ pre[class*='language-'] {
   }
 
   .line-number {
-    font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+    font-family: var(--font-family-code);
   }
 }
 
 div[class*='language-'] {
   position: relative;
-  background-color: $codeBgColor;
+  background-color: var(--code-bg-color);
   border-radius: 6px;
 
   &::before {
@@ -169,7 +156,7 @@ div[class*='language-'] {
     top: 0.8em;
     right: 1em;
     font-size: 0.75rem;
-    color: $lineNumbersColor;
+    color: var(--code-ln-color);
   }
 
   pre,
@@ -190,7 +177,7 @@ div[class*='language-'] {
     line-height: 1.4;
 
     .highlight-line {
-      background-color: $highlightLineBgColor;
+      background-color: var(--code-hl-bg-color);
     }
   }
 
@@ -211,13 +198,13 @@ div[class*='language-'] {
         left: 0;
         top: 0;
         display: block;
-        width: $lineNumbersWrapperWidth;
+        width: var(--code-ln-wrapper-width);
         height: 100%;
       }
     }
 
     pre {
-      margin-left: $lineNumbersWrapperWidth;
+      margin-left: var(--code-ln-wrapper-width);
       padding-left: 1rem;
       vertical-align: middle;
     }
@@ -225,9 +212,9 @@ div[class*='language-'] {
     .line-numbers {
       position: absolute;
       top: 0;
-      width: $lineNumbersWrapperWidth;
+      width: var(--code-ln-wrapper-width);
       text-align: center;
-      color: $lineNumbersColor;
+      color: var(--code-ln-color);
       padding-top: 1.25rem;
       line-height: 1.4;
 
@@ -248,10 +235,10 @@ div[class*='language-'] {
       position: absolute;
       top: 0;
       left: 0;
-      width: $lineNumbersWrapperWidth;
+      width: var(--code-ln-wrapper-width);
       height: 100%;
       border-radius: 6px 0 0 6px;
-      border-right: 1px solid $highlightLineBgColor;
+      border-right: 1px solid var(--code-hl-bg-color);
     }
   }
 }
diff --git a/packages/@vuepress/theme-default/src/client/styles/custom-container.scss b/packages/@vuepress/theme-default/src/client/styles/custom-container.scss
index a7cc87e33f..f680b148cb 100644
--- a/packages/@vuepress/theme-default/src/client/styles/custom-container.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/custom-container.scss
@@ -1,7 +1,3 @@
-@use 'sass:color';
-
-@import '_variables';
-
 .custom-container {
   .custom-container-title {
     font-weight: 600;
@@ -18,35 +14,44 @@
   }
 
   &.tip {
-    background-color: #f3f5f7;
-    border-color: $tipColor;
+    border-color: var(--c-tip);
+    background-color: var(--c-tip-bg);
+    color: var(--c-tip-text);
+
+    .custom-container-title {
+      color: var(--c-tip-title);
+    }
+
+    a {
+      color: var(--c-tip-text-accent);
+    }
   }
 
   &.warning {
-    background-color: color.scale($warningColor, $lightness: 90%);
-    border-color: $warningColor;
-    color: color.scale($warningColor, $lightness: -50%);
+    border-color: var(--c-warning);
+    background-color: var(--c-warning-bg);
+    color: var(--c-warning-text);
 
     .custom-container-title {
-      color: color.scale($warningColor, $lightness: -25%);
+      color: var(--c-warning-title);
     }
 
     a {
-      color: $textColor;
+      color: var(--c-warning-text-accent);
     }
   }
 
   &.danger {
-    background-color: color.scale($dangerColor, $lightness: 90%);
-    border-color: $dangerColor;
-    color: color.scale($dangerColor, $lightness: -50%);
+    border-color: var(--c-danger);
+    background-color: var(--c-danger-bg);
+    color: var(--c-danger-text);
 
     .custom-container-title {
-      color: color.scale($dangerColor, $lightness: -25%);
+      color: var(--c-danger-title);
     }
 
     a {
-      color: $textColor;
+      color: var(--c-danger-text-accent);
     }
   }
 
@@ -56,7 +61,7 @@
     border-radius: 2px;
     margin: 1.6em 0;
     padding: 1.6em;
-    background-color: #eee;
+    background-color: var(--c-details-bg);
 
     h4 {
       margin-top: 0;
diff --git a/packages/@vuepress/theme-default/src/client/styles/dropdown.scss b/packages/@vuepress/theme-default/src/client/styles/dropdown.scss
index e0b28134d8..a468397e92 100644
--- a/packages/@vuepress/theme-default/src/client/styles/dropdown.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/dropdown.scss
@@ -13,7 +13,7 @@
     background: transparent;
     border: none;
     font-weight: 500;
-    color: $textColor;
+    color: var(--c-text);
 
     &:hover {
       border-color: transparent;
@@ -30,11 +30,9 @@
     @extend .dropdown-title;
     display: none;
     font-weight: 600;
-
-    font-size inherit {
-      &:hover {
-        color: $accentColor;
-      }
+    font-size: inherit;
+    &:hover {
+      color: var(--c-text-accent);
     }
   }
 
@@ -45,7 +43,7 @@
 
       .dropdown-subtitle {
         margin: 0.45rem 0 0;
-        border-top: 1px solid #eee;
+        border-top: 1px solid var(--c-border);
         padding: 1rem 0 0.45rem 0;
         font-size: 0.9rem;
 
@@ -82,17 +80,17 @@
         padding: 0 1.5rem 0 1.25rem;
 
         &:hover {
-          color: $accentColor;
+          color: var(--c-text-accent);
         }
 
         &.router-link-active {
-          color: $accentColor;
+          color: var(--c-text-accent);
 
           &::after {
             content: '';
             width: 0;
             height: 0;
-            border-left: 5px solid $accentColor;
+            border-left: 5px solid var(--c-text-accent);
             border-top: 3px solid transparent;
             border-bottom: 3px solid transparent;
             position: absolute;
@@ -176,10 +174,10 @@
       position: absolute;
       top: 100%;
       right: 0;
-      background-color: #fff;
+      background-color: var(--c-bg-navbar);
       padding: 0.6rem 0;
-      border: 1px solid #ddd;
-      border-bottom-color: #ccc;
+      border: 1px solid var(--c-border);
+      border-bottom-color: var(--c-border-dark);
       text-align: left;
       border-radius: 0.25rem;
       white-space: nowrap;
diff --git a/packages/@vuepress/theme-default/src/client/styles/home.scss b/packages/@vuepress/theme-default/src/client/styles/home.scss
index a8a3d2ba83..309806d196 100644
--- a/packages/@vuepress/theme-default/src/client/styles/home.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/home.scss
@@ -1,10 +1,8 @@
-@use 'sass:color';
-
 @import '_variables';
 
 .home {
-  padding: $navbarHeight 2rem 0;
-  max-width: $homePageWidth;
+  padding: var(--navbar-height) 2rem 0;
+  max-width: var(--homepage-width);
   margin: 0px auto;
   display: block;
 
@@ -32,7 +30,7 @@
       max-width: 35rem;
       font-size: 1.6rem;
       line-height: 1.3;
-      color: color.scale($textColor, $lightness: 40%);
+      color: var(--c-text-lightest);
     }
 
     .action-button {
@@ -50,28 +48,28 @@
       }
 
       &.primary {
-        color: #fff;
-        background-color: $accentColor;
-        border-color: $accentColor;
+        color: var(--c-bg);
+        background-color: var(--c-brand);
+        border-color: var(--c-brand);
         &:hover {
-          background-color: color.scale($accentColor, $lightness: 10%);
+          background-color: var(--c-brand-light);
         }
       }
 
       &.secondary {
-        color: $accentColor;
-        background-color: #fff;
-        border-color: $accentColor;
+        color: var(--c-brand);
+        background-color: var(--c-bg);
+        border-color: var(--c-brand);
         &:hover {
-          color: #fff;
-          background-color: color.scale($accentColor, $lightness: 10%);
+          color: var(--c-bg);
+          background-color: var(--c-brand-light);
         }
       }
     }
   }
 
   .features {
-    border-top: 1px solid $borderColor;
+    border-top: 1px solid var(--c-border);
     padding: 1.2rem 0;
     margin-top: 2.5rem;
     display: flex;
@@ -91,19 +89,19 @@
       font-weight: 500;
       border-bottom: none;
       padding-bottom: 0;
-      color: color.scale($textColor, $lightness: 10%);
+      color: var(--c-text-light);
     }
 
     p {
-      color: color.scale($textColor, $lightness: 25%);
+      color: var(--c-text-lighter);
     }
   }
 
   .footer {
     padding: 2.5rem;
-    border-top: 1px solid $borderColor;
+    border-top: 1px solid var(--c-border);
     text-align: center;
-    color: color.scale($textColor, $lightness: 25%);
+    color: var(--c-text-lighter);
   }
 }
 
diff --git a/packages/@vuepress/theme-default/src/client/styles/index.scss b/packages/@vuepress/theme-default/src/client/styles/index.scss
index fced074b45..cc9ae849f1 100644
--- a/packages/@vuepress/theme-default/src/client/styles/index.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/index.scss
@@ -1,4 +1,6 @@
-@use '_normalize';
+@use 'vars';
+@use 'vars-dark';
+@use 'normalize';
 
 @use 'arrow';
 @use 'badge';
@@ -10,7 +12,6 @@
 @use 'layout';
 @use 'navbar';
 @use 'page';
-@use 'plugins';
 @use 'sidebar';
 @use 'sr-only';
 @use 'toc';
diff --git a/packages/@vuepress/theme-default/src/client/styles/layout.scss b/packages/@vuepress/theme-default/src/client/styles/layout.scss
index ebd7ca3a69..6537119c8c 100644
--- a/packages/@vuepress/theme-default/src/client/styles/layout.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/layout.scss
@@ -2,8 +2,8 @@
 @import '_wrapper';
 
 .page {
-  padding-top: $navbarHeight;
-  padding-left: $sidebarWidth;
+  padding-top: var(--navbar-height);
+  padding-left: var(--sidebar-width);
 }
 
 .navbar {
@@ -12,35 +12,37 @@
   top: 0;
   left: 0;
   right: 0;
-  height: $navbarHeight;
-  background-color: #fff;
+  height: var(--navbar-height);
   box-sizing: border-box;
-  border-bottom: 1px solid $borderColor;
+  border-bottom: 1px solid var(--c-border);
+  background-color: var(--c-bg-navbar);
+  transition: background-color ease 0.3s;
 }
 
 .sidebar {
   font-size: 16px;
-  background-color: #fff;
-  width: $sidebarWidth;
+  width: var(--sidebar-width);
   position: fixed;
   z-index: 10;
   margin: 0;
-  top: $navbarHeight;
+  top: var(--navbar-height);
   left: 0;
   bottom: 0;
   box-sizing: border-box;
-  border-right: 1px solid $borderColor;
+  border-right: 1px solid var(--c-border);
   overflow-y: auto;
   scrollbar-width: thin;
-  scrollbar-color: $accentColor $borderColor;
+  scrollbar-color: var(--c-brand) var(--c-border);
+  background-color: var(--c-bg-sidebar);
+  transition: background-color ease 0.3s;
   &::-webkit-scrollbar {
     width: 7px;
   }
   &::-webkit-scrollbar-track {
-    background-color: $borderColor;
+    background-color: var(--c-border);
   }
   &::-webkit-scrollbar-thumb {
-    background-color: $accentColor;
+    background-color: var(--c-brand);
   }
 }
 
@@ -101,8 +103,8 @@ h4,
 h5,
 h6 {
   .theme-default-content:not(.custom) > & {
-    margin-top: 0.5rem - $navbarHeight;
-    padding-top: ($navbarHeight + 1rem);
+    margin-top: calc(0.5rem - var(--navbar-height));
+    padding-top: calc(1rem + var(--navbar-height));
     margin-bottom: 0;
 
     &:first-child {
@@ -126,12 +128,6 @@ h6 {
     text-decoration: underline;
   }
 
-  p.demo {
-    padding: 1rem 1.5rem;
-    border: 1px solid #ddd;
-    border-radius: 4px;
-  }
-
   img {
     max-width: 100%;
   }
@@ -150,11 +146,11 @@ h6 {
 @media (max-width: $MQNarrow) {
   .sidebar {
     font-size: 15px;
-    width: $mobileSidebarWidth;
+    width: var(--sidebar-width-mobile);
   }
 
   .page {
-    padding-left: $mobileSidebarWidth;
+    padding-left: var(--sidebar-width-mobile);
   }
 }
 
@@ -162,7 +158,7 @@ h6 {
 @media (max-width: $MQMobile) {
   .sidebar {
     top: 0;
-    padding-top: $navbarHeight;
+    padding-top: var(--navbar-height);
     transform: translateX(-100%);
     transition: transform 0.2s ease;
   }
diff --git a/packages/@vuepress/theme-default/src/client/styles/navbar.scss b/packages/@vuepress/theme-default/src/client/styles/navbar.scss
index 605e868a3d..dd24f5efee 100644
--- a/packages/@vuepress/theme-default/src/client/styles/navbar.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/navbar.scss
@@ -1,17 +1,12 @@
-@use 'sass:color';
-
 @import '_variables';
 
-$navbar-vertical-padding: 0.7rem;
-$navbar-horizontal-padding: 1.5rem;
-
 .navbar {
-  padding: $navbar-vertical-padding $navbar-horizontal-padding;
-  line-height: $navbarHeight - 1.4rem;
+  padding: var(--navbar-padding-v) var(--navbar-padding-h);
+  line-height: calc(var(--navbar-height) - 1.4rem);
 
   .logo {
-    height: $navbarHeight - 1.4rem;
-    min-width: $navbarHeight - 1.4rem;
+    height: calc(var(--navbar-height) - 1.4rem);
+    min-width: calc(var(--navbar-height) - 1.4rem);
     margin-right: 0.8rem;
     vertical-align: top;
   }
@@ -19,19 +14,18 @@ $navbar-horizontal-padding: 1.5rem;
   .site-name {
     font-size: 1.3rem;
     font-weight: 600;
-    color: $textColor;
+    color: var(--c-text);
     position: relative;
   }
 
   .navbar-links-wrapper {
     padding-left: 1.5rem;
     box-sizing: border-box;
-    background-color: white;
     white-space: nowrap;
     font-size: 0.9rem;
     position: absolute;
-    right: $navbar-horizontal-padding;
-    top: $navbar-vertical-padding;
+    right: var(--navbar-padding-h);
+    top: var(--navbar-padding-v);
     display: flex;
 
     .search-box {
@@ -75,7 +69,7 @@ $navbar-horizontal-padding: 1.5rem;
 
     &:hover,
     &.router-link-active {
-      color: $accentColor;
+      color: var(--c-text-accent);
     }
   }
 
@@ -103,7 +97,7 @@ $navbar-horizontal-padding: 1.5rem;
   .navbar-links a {
     &:hover,
     &.router-link-active {
-      color: $textColor;
+      color: var(--c-text);
     }
   }
 
@@ -111,7 +105,7 @@ $navbar-horizontal-padding: 1.5rem;
     &:hover,
     &.router-link-active {
       margin-bottom: -2px;
-      border-bottom: 2px solid color.scale($accentColor, $lightness: 8%);
+      border-bottom: 2px solid var(--c-text-accent);
     }
   }
 }
@@ -139,3 +133,25 @@ $navbar-horizontal-padding: 1.5rem;
     display: block;
   }
 }
+
+/**
+ * toggle dark button
+ */
+.toggle-dark-button {
+  display: flex;
+  margin: auto;
+  margin-left: 1rem;
+  border: 0;
+  outline: none;
+  background: none;
+  color: var(--c-text);
+  opacity: 0.8;
+  cursor: pointer;
+  &:hover {
+    opacity: 1;
+  }
+  .icon {
+    width: 1.25rem;
+    height: 1.25rem;
+  }
+}
diff --git a/packages/@vuepress/theme-default/src/client/styles/_normalize.scss b/packages/@vuepress/theme-default/src/client/styles/normalize.scss
similarity index 58%
rename from packages/@vuepress/theme-default/src/client/styles/_normalize.scss
rename to packages/@vuepress/theme-default/src/client/styles/normalize.scss
index fd07e1d854..48e7b7bdf4 100644
--- a/packages/@vuepress/theme-default/src/client/styles/_normalize.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/normalize.scss
@@ -1,44 +1,53 @@
-@import '_variables';
-
 html,
 body {
   padding: 0;
   margin: 0;
-  background-color: #fff;
+  background-color: var(--c-bg);
+  transition: background-color ease 0.3s;
 }
 
 body {
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
-    Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  font-family: var(--font-family);
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   font-size: 16px;
-  color: $textColor;
+  color: var(--c-text);
 }
 
 a {
   font-weight: 500;
-  color: $accentColor;
+  color: var(--c-text-accent);
   text-decoration: none;
 }
 
 p a code {
   font-weight: 400;
-  color: $accentColor;
+  color: var(--c-text-accent);
 }
 
 kbd {
-  background: #eee;
-  border: solid 0.15rem #ddd;
-  border-bottom: solid 0.25rem #ddd;
+  font-family: var(--font-family-code);
+  background: var(--c-bg-lighter);
+  border: solid 0.15rem var(--c-border-dark);
+  border-bottom: solid 0.25rem var(--c-border-dark);
   border-radius: 0.15rem;
   padding: 0 0.15em;
 }
 
+code {
+  font-family: var(--font-family-code);
+  color: var(--c-text-lighter);
+  padding: 0.25rem 0.5rem;
+  margin: 0;
+  font-size: 0.85em;
+  background-color: var(--c-bg-light);
+  border-radius: 3px;
+}
+
 blockquote {
   font-size: 1rem;
-  color: #999;
-  border-left: 0.2rem solid #dfe2e5;
+  color: var(--c-text-quote);
+  border-left: 0.2rem solid var(--c-border-dark);
   margin: 1rem 0;
   padding: 0.25rem 0 0.25rem 1rem;
 
@@ -77,7 +86,7 @@ h1 {
 h2 {
   font-size: 1.65rem;
   padding-bottom: 0.3rem;
-  border-bottom: 1px solid $borderColor;
+  border-bottom: 1px solid var(--c-border);
 }
 
 h3 {
@@ -113,12 +122,6 @@ a.header-anchor {
   }
 }
 
-code,
-kbd {
-  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-    monospace;
-}
-
 p,
 ul,
 ol {
@@ -127,7 +130,7 @@ ol {
 
 hr {
   border: 0;
-  border-top: 1px solid $borderColor;
+  border-top: 1px solid var(--c-border);
 }
 
 table {
@@ -138,15 +141,15 @@ table {
 }
 
 tr {
-  border-top: 1px solid #dfe2e5;
+  border-top: 1px solid var(--c-border-dark);
 
   &:nth-child(2n) {
-    background-color: #f6f8fa;
+    background-color: var(--c-bg-light);
   }
 }
 
 th,
 td {
-  border: 1px solid #dfe2e5;
+  border: 1px solid var(--c-border-dark);
   padding: 0.6em 1em;
 }
diff --git a/packages/@vuepress/theme-default/src/client/styles/page.scss b/packages/@vuepress/theme-default/src/client/styles/page.scss
index 2a14ef8981..419ade9144 100644
--- a/packages/@vuepress/theme-default/src/client/styles/page.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/page.scss
@@ -1,5 +1,3 @@
-@use 'sass:color';
-
 @import '_variables';
 @import '_wrapper';
 
@@ -20,12 +18,12 @@
 
     .meta-item-label {
       font-weight: 500;
-      color: color.scale($textColor, $lightness: 25%);
+      color: var(--c-text-lighter);
     }
 
     .meta-item-info {
       font-weight: 400;
-      color: #767676;
+      color: var(--c-text-quote);
     }
   }
 
@@ -60,7 +58,7 @@
   .inner {
     min-height: 2rem;
     margin-top: 0;
-    border-top: 1px solid $borderColor;
+    border-top: 1px solid var(--c-border);
     padding-top: 1rem;
     overflow: auto;
   }
diff --git a/packages/@vuepress/theme-default/src/client/styles/plugins.scss b/packages/@vuepress/theme-default/src/client/styles/plugins.scss
deleted file mode 100644
index 3bd839ecd9..0000000000
--- a/packages/@vuepress/theme-default/src/client/styles/plugins.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-@use 'sass:color';
-
-@import '_variables';
-
-.back-to-top {
-  --back-to-top-color: #{$accentColor};
-  --back-to-top-color-hover: #{color.scale($accentColor, $lightness: 30%)};
-}
-
-#nprogress {
-  --nprogress-color: #{$accentColor};
-}
-
-.DocSearch {
-  --docsearch-primary-color: #{$accentColor};
-  --docsearch-highlight-color: var(--docsearch-primary-color);
-  --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);
-}
diff --git a/packages/@vuepress/theme-default/src/client/styles/sidebar.scss b/packages/@vuepress/theme-default/src/client/styles/sidebar.scss
index 02cb4ef18b..63c865dd9b 100644
--- a/packages/@vuepress/theme-default/src/client/styles/sidebar.scss
+++ b/packages/@vuepress/theme-default/src/client/styles/sidebar.scss
@@ -13,7 +13,7 @@
 
   .navbar-links {
     display: none;
-    border-bottom: 1px solid $borderColor;
+    border-bottom: 1px solid var(--c-border);
     padding: 0.5rem 0 0.75rem 0;
 
     a {
@@ -81,7 +81,7 @@
 }
 
 .sidebar-heading {
-  color: $textColor;
+  color: var(--c-text);
   transition: color 0.15s ease;
   cursor: default;
   font-size: 1.1em;
@@ -113,7 +113,7 @@
   font-size: 1em;
   font-weight: 400;
   display: inline-block;
-  color: $textColor;
+  color: var(--c-text);
   border-left: 0.25rem solid transparent;
   margin: 0;
   padding: 0.35rem 1rem 0.35rem 1.25rem;
@@ -142,11 +142,11 @@ a.sidebar-link {
 
   &.active {
     font-weight: 600;
-    color: $accentColor;
-    border-left-color: $accentColor;
+    color: var(--c-text-accent);
+    border-left-color: var(--c-text-accent);
   }
 
   &:hover {
-    color: $accentColor;
+    color: var(--c-text-accent);
   }
 }
diff --git a/packages/@vuepress/theme-default/src/client/styles/vars-dark.scss b/packages/@vuepress/theme-default/src/client/styles/vars-dark.scss
new file mode 100644
index 0000000000..58e02fc5cb
--- /dev/null
+++ b/packages/@vuepress/theme-default/src/client/styles/vars-dark.scss
@@ -0,0 +1,46 @@
+html.dark {
+  // brand colors
+  --c-brand: #3aa675;
+  --c-brand-light: #349469;
+
+  // background colors
+  --c-bg: #22272e;
+  --c-bg-light: #2b313a;
+  --c-bg-lighter: #262c34;
+
+  // text colors
+  --c-text: #adbac7;
+  --c-text-light: #96a7b7;
+  --c-text-lighter: #8b9eb0;
+  --c-text-lightest: #8094a8;
+
+  // border colors
+  --c-border: #3e4c5a;
+  --c-border-dark: #34404c;
+
+  // custom container colors
+  --c-tip: #318a62;
+  --c-warning: #ceab00;
+  --c-warning-bg: #7e755b;
+  --c-warning-title: #ceac03;
+  --c-warning-text: #362e00;
+  --c-danger: #940000;
+  --c-danger-bg: #806161;
+  --c-danger-title: #610000;
+  --c-danger-text: #3a0000;
+  --c-details-bg: #323843;
+
+  // code blocks vars
+  --code-hl-bg-color: #363b46;
+}
+
+// plugin-docsearch
+html.dark .DocSearch {
+  --docsearch-logo-color: var(--c-text);
+  --docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;
+  --docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d,
+    0 2px 2px 0 rgba(3, 4, 9, 0.3);
+  --docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21);
+  --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5),
+    0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+}
diff --git a/packages/@vuepress/theme-default/src/client/styles/vars.scss b/packages/@vuepress/theme-default/src/client/styles/vars.scss
new file mode 100644
index 0000000000..0e3a6a2d1b
--- /dev/null
+++ b/packages/@vuepress/theme-default/src/client/styles/vars.scss
@@ -0,0 +1,108 @@
+:root {
+  // brand colors
+  --c-brand: #3eaf7c;
+  --c-brand-light: #4abf8a;
+
+  // background colors
+  --c-bg: #ffffff;
+  --c-bg-light: #f3f4f5;
+  --c-bg-lighter: #eeeeee;
+  --c-bg-navbar: var(--c-bg);
+  --c-bg-sidebar: var(--c-bg);
+  --c-bg-arrow: #cccccc;
+
+  // text colors
+  --c-text: #2c3e50;
+  --c-text-accent: var(--c-brand);
+  --c-text-light: #3a5169;
+  --c-text-lighter: #4e6e8e;
+  --c-text-lightest: #6a8bad;
+  --c-text-quote: #999999;
+
+  // border colors
+  --c-border: #eaecef;
+  --c-border-dark: #dfe2e5;
+
+  // custom container colors
+  --c-tip: #42b983;
+  --c-tip-bg: var(--c-bg-light);
+  --c-tip-title: var(--c-text);
+  --c-tip-text: var(--c-text);
+  --c-tip-text-accent: var(--c-text-accent);
+  --c-warning: #e7c000;
+  --c-warning-bg: #fffae3;
+  --c-warning-title: #ad9000;
+  --c-warning-text: #746000;
+  --c-warning-text-accent: var(--c-text);
+  --c-danger: #cc0000;
+  --c-danger-bg: #ffe0e0;
+  --c-danger-title: #990000;
+  --c-danger-text: #660000;
+  --c-danger-text-accent: var(--c-text);
+  --c-details-bg: #eeeeee;
+
+  // badge component colors
+  --c-badge-tip: var(--c-tip);
+  --c-badge-warning: var(--c-warning);
+  --c-badge-danger: var(--c-danger);
+
+  // code blocks vars
+  --code-bg-color: #282c34;
+  --code-hl-bg-color: rgba(0, 0, 0, 0.66);
+  --code-ln-color: #9e9e9e;
+  --code-ln-wrapper-width: 3.5rem;
+
+  // font vars
+  --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
+    Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  --font-family-code: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+
+  // layout vars
+  --navbar-height: 3.6rem;
+  --navbar-padding-v: 0.7rem;
+  --navbar-padding-h: 1.5rem;
+  --sidebar-width: 20rem;
+  --sidebar-width-mobile: calc(var(--sidebar-width) * 0.82);
+  --content-width: 740px;
+  --homepage-width: 960px;
+}
+
+// plugin-back-to-top
+.back-to-top {
+  --back-to-top-color: var(--c-brand);
+  --back-to-top-color-hover: var(--c-brand-light);
+}
+
+// plugin-nprogress
+#nprogress {
+  --nprogress-color: var(--c-brand);
+}
+
+// plugin-search
+.search-box {
+  --search-bg-color: var(--c-bg);
+  --search-accent-color: var(--c-brand);
+  --search-text-color: var(--c-text);
+  --search-border-color: var(--c-border);
+
+  --search-item-text-color: var(--c-text-lighter);
+  --search-item-focus-bg-color: var(--c-bg-light);
+}
+
+// plugin-docsearch
+.DocSearch {
+  --docsearch-primary-color: var(--c-brand);
+  --docsearch-text-color: var(--c-text);
+  --docsearch-highlight-color: var(--c-brand);
+  --docsearch-muted-color: var(--c-text-quote);
+  --docsearch-container-background: rgba(9, 10, 17, 0.8);
+  --docsearch-modal-background: var(--c-bg-light);
+  --docsearch-searchbox-background: var(--c-bg-lighter);
+  --docsearch-searchbox-focus-background: var(--c-bg);
+  --docsearch-searchbox-shadow: inset 0 0 0 2px var(--c-brand);
+  --docsearch-hit-color: var(--c-text-light);
+  --docsearch-hit-active-color: var(--c-bg);
+  --docsearch-hit-background: var(--c-bg);
+  --docsearch-hit-shadow: 0 1px 3px 0 var(--c-border-dark);
+  --docsearch-footer-background: var(--c-bg);
+}