├── src ├── script.min.js ├── runtime │ ├── composables.ts │ ├── component.vue3.vue │ ├── component.vue2.vue │ ├── types.ts │ ├── plugin.server.ts │ └── plugin.client.ts ├── script.ts └── module.ts ├── renovate.json ├── tsconfig.json ├── .husky ├── pre-push ├── pre-commit └── commit-msg ├── docs ├── static │ ├── icon.png │ ├── preview.png │ ├── preview-dark.png │ ├── logo-dark.svg │ └── logo-light.svg ├── .gitignore ├── nuxt.config.js ├── package.json ├── content │ ├── settings.json │ └── index.md └── README.md ├── commitlint.config.cjs ├── .gitignore ├── .eslintignore ├── babel.config.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.md │ ├── feature-request.md │ └── bug-report.md └── workflows │ └── ci.yml ├── playground ├── nuxt.config.js ├── pages │ ├── index.vue │ └── light.vue ├── components │ ├── IconDark.vue │ ├── IconSystem.vue │ ├── IconSepia.vue │ ├── IconLight.vue │ ├── ColorModePicker.vue │ └── NuxtLogo.vue ├── app.vue └── assets │ └── main.css ├── .editorconfig ├── jest.config.cjs ├── .eslintrc.cjs ├── LICENSE ├── test ├── csr.test.js └── ssr.test.js ├── package.json ├── README.md └── CHANGELOG.md /src/script.min.js: -------------------------------------------------------------------------------- 1 | script.ts -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /docs/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu/color-mode-module/master/docs/static/icon.png -------------------------------------------------------------------------------- /docs/static/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu/color-mode-module/master/docs/static/preview.png -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@commitlint/config-conventional' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/static/preview-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu/color-mode-module/master/docs/static/preview-dark.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Common 2 | node_modules 3 | dist 4 | .nuxt 5 | coverage 6 | lib/templates 7 | 8 | *.min.js 9 | sw.js 10 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', { 5 | targets: { 6 | esmodules: true 7 | } 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Nuxt Community Discord 4 | url: https://discord.nuxtjs.org/ 5 | about: Consider asking questions about the module here. 6 | -------------------------------------------------------------------------------- /src/runtime/composables.ts: -------------------------------------------------------------------------------- 1 | import type { ColorModeInstance } from './types' 2 | import { useState } from '#imports' 3 | 4 | export const useColorMode = () => { 5 | return useState('color-mode').value as ColorModeInstance 6 | } 7 | -------------------------------------------------------------------------------- /playground/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt' 2 | import colorModeModule from '..' 3 | 4 | export default defineNuxtConfig({ 5 | components: { global: true, dirs: ['~/components'] }, 6 | css: ['~/assets/main.css'], 7 | modules: [colorModeModule] 8 | }) 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { withDocus } from 'docus' 2 | 3 | export default withDocus({ 4 | docus: { 5 | colors: { 6 | primary: '#00CD82' 7 | } 8 | }, 9 | buildModules: ['vue-plausible'], 10 | plausible: { 11 | domain: 'color-mode.nuxtjs.org' 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils', 3 | collectCoverage: true, 4 | collectCoverageFrom: [ 5 | 'lib/module.js', 6 | 'lib/utils.js' 7 | ], 8 | moduleNameMapper: { 9 | '^~/(.*)$': '/lib/$1', 10 | '^~~$': '', 11 | '^@@$': '', 12 | '^@/(.*)$': '/lib/$1' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: '@typescript-eslint/parser', 5 | sourceType: 'module' 6 | }, 7 | rules: { 8 | 'no-use-before-define': 'off', 9 | 'vue/require-default-prop': 'off', 10 | 'vue/multi-word-component-names': 'off' 11 | }, 12 | extends: [ 13 | '@nuxtjs/eslint-config-typescript' 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-color-mode-docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "start": "nuxt start", 9 | "generate": "nuxt generate" 10 | }, 11 | "dependencies": { 12 | "docus": "^0.0.10", 13 | "nuxt": "^2.15.3" 14 | }, 15 | "devDependencies": { 16 | "vue-plausible": "^1.1.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /playground/components/IconDark.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /docs/content/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Nuxt Color Mode", 3 | "url": "https://color-mode.nuxtjs.org", 4 | "logo": { 5 | "light": "/logo-light.svg", 6 | "dark": "/logo-dark.svg" 7 | }, 8 | "header": { 9 | "title": false, 10 | "logo": true 11 | }, 12 | "github": { 13 | "repo": "nuxt-community/color-mode-module", 14 | "branch": "master" 15 | }, 16 | "twitter": "@nuxt_js", 17 | "layout": "readme" 18 | } 19 | -------------------------------------------------------------------------------- /src/runtime/component.vue3.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about the module. 4 | title: '' 5 | labels: 'question' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | -------------------------------------------------------------------------------- /playground/pages/light.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /playground/components/IconSystem.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /playground/components/IconSepia.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # nuxt-color-mode-docs 2 | 3 | ## Setup 4 | 5 | Install dependencies: 6 | 7 | ```bash 8 | yarn install 9 | ``` 10 | 11 | ## Development 12 | 13 | ```bash 14 | yarn dev 15 | ``` 16 | 17 | ## Static Generation 18 | 19 | This will create the `dist/` directory for publishing to static hosting: 20 | 21 | ```bash 22 | yarn generate 23 | ``` 24 | 25 | To preview the static generated app, run `yarn start` 26 | 27 | For detailed explanation on how things work, checkout [nuxt/content](https://content.nuxtjs.org) and [@nuxt/content theme docs](https://content.nuxtjs.org/themes-docs). 28 | -------------------------------------------------------------------------------- /src/runtime/component.vue2.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /playground/components/IconLight.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or enhancement for the module. 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Is your feature request related to a problem? Please describe. 11 | 12 | 13 | ### Describe the solution you'd like 14 | 15 | 16 | ### Describe alternatives you've considered 17 | 18 | 19 | ### Additional context 20 | 21 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug report to help us improve the module. 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 12 | 13 | ### Version 14 | @nuxtjs/color-mode: 15 | nuxt: 16 | 17 | ### Reproduction Link 18 | 23 | 24 | ### Steps to reproduce 25 | 26 | 27 | ### What is Expected? 28 | 29 | 30 | ### What is actually happening? 31 | -------------------------------------------------------------------------------- /playground/assets/main.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color: #243746; 3 | --color-primary: #158876; 4 | --color-secondary: #0e2233; 5 | --bg: #f3f5f4; 6 | --bg-secondary: #fff; 7 | --border-color: #ddd; 8 | } 9 | 10 | .dark-mode { 11 | --color: #ebf4f1; 12 | --color-primary: #41b38a; 13 | --color-secondary: #fdf9f3; 14 | --bg: #091a28; 15 | --bg-secondary: #071521; 16 | --border-color: #0d2538; 17 | } 18 | .sepia-mode { 19 | --color: #433422; 20 | --color-secondary: #504231; 21 | --bg: #f1e7d0; 22 | --bg-secondary: #eae0c9; 23 | --border-color: #ded0bf; 24 | } 25 | 26 | body { 27 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 28 | background-color: var(--bg); 29 | color: var(--color); 30 | transition: background-color .3s; 31 | } 32 | a { 33 | color: var(--color-primary) 34 | } 35 | -------------------------------------------------------------------------------- /src/runtime/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | export interface ColorModeInstance { 3 | preference: string 4 | value: string 5 | unknown: boolean 6 | forced: boolean 7 | } 8 | 9 | // @ts-ignore 10 | declare module 'vue/types/vue' { 11 | interface Vue { 12 | $colorMode: ColorModeInstance 13 | } 14 | } 15 | 16 | // @ts-ignore 17 | declare module 'vue/types/options' { 18 | interface ComponentOptions { 19 | /** 20 | * Forces a color mode for current page 21 | * @see https://color-mode.nuxtjs.org/#force-a-color-mode 22 | */ 23 | colorMode?: string 24 | } 25 | } 26 | 27 | // Nuxt Bridge & Nuxt 3 28 | declare module '#app' { 29 | interface NuxtApp extends PluginInjection { } 30 | } 31 | 32 | // Nuxt 3 33 | // @ts-ignore 34 | declare module 'vue-router' { 35 | interface RouteMeta { 36 | colorMode?: string 37 | } 38 | } 39 | 40 | interface PluginInjection { 41 | $colorMode: ColorModeInstance 42 | } 43 | 44 | declare module '@vue/runtime-core' { 45 | interface ComponentCustomProperties extends PluginInjection { } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nuxt Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | node: [14, 16] 19 | 20 | steps: 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - name: checkout 26 | uses: actions/checkout@master 27 | 28 | - name: cache node_modules 29 | uses: actions/cache@v3 30 | with: 31 | path: node_modules 32 | key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }} 33 | 34 | - name: Install dependencies 35 | run: yarn 36 | 37 | - name: Prepare TypeScript environment 38 | run: yarn dev:prepare 39 | 40 | - name: Lint 41 | run: yarn lint 42 | 43 | - name: Test 44 | run: yarn test 45 | 46 | - name: Coverage 47 | uses: codecov/codecov-action@v3 48 | -------------------------------------------------------------------------------- /playground/components/ColorModePicker.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 61 | -------------------------------------------------------------------------------- /test/csr.test.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { readFile } from 'fs-extra' 3 | import { setupTest, get, getContext } from '@nuxt/test-utils' 4 | 5 | describe('ssr: false, dev mode', () => { 6 | const rootDir = join(__dirname, '..', 'playground') 7 | 8 | setupTest({ 9 | server: true, 10 | rootDir, 11 | config: { 12 | ssr: false 13 | } 14 | }) 15 | 16 | test('render', async () => { 17 | const { body } = await get('/') 18 | expect(body).toContain('nuxt-color-mode-script') 19 | }) 20 | }) 21 | 22 | describe('ssr: false, target: server, prod mode', () => { 23 | const rootDir = join(__dirname, '..', 'playground') 24 | 25 | setupTest({ 26 | server: true, 27 | build: true, 28 | rootDir, 29 | config: { 30 | ssr: false, 31 | target: 'server' 32 | } 33 | }) 34 | 35 | test('render', async () => { 36 | const { body } = await get('/') 37 | expect(body).toContain('nuxt-color-mode-script') 38 | }) 39 | }) 40 | 41 | describe('ssr: false, target: static, generated files', () => { 42 | const rootDir = join(__dirname, '..', 'playground') 43 | 44 | setupTest({ 45 | generate: true, 46 | rootDir, 47 | config: { 48 | ssr: false 49 | } 50 | }) 51 | 52 | test('generated file', async () => { 53 | const { nuxt } = getContext() 54 | const generateDir = nuxt.options.generate.dir 55 | const files = ['index.html', '200.html'] 56 | for (const file of files) { 57 | const contents = await readFile(join(generateDir, file), 'utf-8') 58 | expect(contents).toMatch('nuxt-color-mode-script') 59 | } 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/color-mode", 3 | "version": "3.1.3", 4 | "description": "Dark and Light mode for Nuxt with auto detection", 5 | "repository": "nuxt-community/color-mode-module", 6 | "license": "MIT", 7 | "contributors": [ 8 | { 9 | "name": "Nuxt Team" 10 | } 11 | ], 12 | "type": "module", 13 | "exports": { 14 | ".": { 15 | "import": "./dist/module.mjs", 16 | "require": "./dist/module.cjs" 17 | } 18 | }, 19 | "main": "./dist/module.cjs", 20 | "types": "./dist/types.d.ts", 21 | "files": [ 22 | "dist" 23 | ], 24 | "scripts": { 25 | "prepack": "nuxt-module-build && esbuild --minify dist/script.mjs --outfile=dist/script.min.js", 26 | "dev": "nuxi dev playground", 27 | "dev:build": "nuxi build playground", 28 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", 29 | "lint": "eslint --ext .js,.vue,.ts .", 30 | "release": "yarn test && standard-version && git push --follow-tags && npm publish", 31 | "test": "yarn lint" 32 | }, 33 | "dependencies": { 34 | "@nuxt/kit": "^3.0.0-rc.3", 35 | "lodash.template": "^4.5.0", 36 | "pathe": "^0.3.0" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.0.1", 40 | "@commitlint/config-conventional": "^17.0.0", 41 | "@nuxt/module-builder": "^0.1.7", 42 | "@nuxt/schema": "^3.0.0-rc.3", 43 | "@nuxtjs/eslint-config-typescript": "^10.0.0", 44 | "@types/lodash.template": "^4.5.0", 45 | "@typescript-eslint/parser": "^5.27.0", 46 | "babel-jest": "^28.1.0", 47 | "eslint": "^8.16.0", 48 | "husky": "8.0.1", 49 | "jest": "^28.1.0", 50 | "nuxt": "npm:nuxt3@latest", 51 | "standard-version": "^9.5.0", 52 | "typescript": "^4.7.2" 53 | }, 54 | "build": { 55 | "entries": [ 56 | "src/script" 57 | ] 58 | }, 59 | "publishConfig": { 60 | "access": "public" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/script.ts: -------------------------------------------------------------------------------- 1 | // Add dark / light detection that runs before loading Nuxt 2 | 3 | // Global variable minimizers 4 | const w = window 5 | const de = document.documentElement 6 | 7 | const knownColorSchemes = ['dark', 'light'] 8 | 9 | const preference = window.localStorage.getItem('<%= options.storageKey %>') || '<%= options.preference %>' 10 | let value = preference === 'system' ? getColorScheme() : preference 11 | // Applied forced color mode 12 | const forcedColorMode = de.getAttribute('data-color-mode-forced') 13 | if (forcedColorMode) { 14 | value = forcedColorMode 15 | } 16 | 17 | addColorScheme(value) 18 | 19 | w['<%= options.globalName %>'] = { 20 | preference, 21 | value, 22 | getColorScheme, 23 | addColorScheme, 24 | removeColorScheme 25 | } 26 | 27 | function addColorScheme (value) { 28 | const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>' 29 | const dataValue = '<%= options.dataValue %>' 30 | if (de.classList) { 31 | de.classList.add(className) 32 | } else { 33 | de.className += ' ' + className 34 | } 35 | if (dataValue) { 36 | de.setAttribute('data-' + dataValue, value) 37 | } 38 | } 39 | 40 | function removeColorScheme (value) { 41 | const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>' 42 | const dataValue = '<%= options.dataValue %>' 43 | if (de.classList) { 44 | de.classList.remove(className) 45 | } else { 46 | de.className = de.className.replace(new RegExp(className, 'g'), '') 47 | } 48 | if (dataValue) { 49 | de.removeAttribute('data-' + dataValue) 50 | } 51 | } 52 | 53 | function prefersColorScheme (suffix) { 54 | return w.matchMedia('(prefers-color-scheme' + suffix + ')') 55 | } 56 | 57 | function getColorScheme () { 58 | if (w.matchMedia && prefersColorScheme('').media !== 'not all') { 59 | for (const colorScheme of knownColorSchemes) { 60 | if (prefersColorScheme(':' + colorScheme).matches) { 61 | return colorScheme 62 | } 63 | } 64 | } 65 | 66 | return '<%= options.fallback %>' 67 | } 68 | -------------------------------------------------------------------------------- /src/runtime/plugin.server.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | import type { ColorModeInstance } from './types' 4 | import { defineNuxtPlugin, isVue2, isVue3, useHead, useState, useRouter } from '#imports' 5 | import { preference, hid, script, dataValue } from '#color-mode-options' 6 | 7 | const addScript = (head) => { 8 | head.script = head.script || [] 9 | head.script.push({ 10 | hid, 11 | innerHTML: script 12 | }) 13 | const serializeProp = '__dangerouslyDisableSanitizersByTagID' 14 | head[serializeProp] = head[serializeProp] || {} 15 | head[serializeProp][hid] = ['innerHTML'] 16 | } 17 | 18 | export default defineNuxtPlugin((nuxtApp) => { 19 | const colorMode = useState('color-mode', () => reactive({ 20 | preference, 21 | value: preference, 22 | unknown: true, 23 | forced: false 24 | })).value 25 | 26 | const htmlAttrs: Record = {} 27 | 28 | if (isVue2) { 29 | const app = nuxtApp.nuxt2Context.app 30 | 31 | if (typeof app.head === 'function') { 32 | const originalHead = app.head 33 | app.head = function () { 34 | const head = originalHead.call(this) || {} 35 | addScript(head) 36 | head.htmlAttrs = htmlAttrs 37 | return head 38 | } 39 | } else { 40 | addScript(app.head) 41 | app.head.htmlAttrs = htmlAttrs 42 | } 43 | } 44 | 45 | if (isVue3) { 46 | useHead({ 47 | htmlAttrs, 48 | script: [{ children: script }] 49 | }) 50 | } 51 | 52 | useRouter().afterEach((to) => { 53 | const forcedColorMode = isVue2 54 | ? (to.matched[0]?.components.default as any)?.options.colorMode 55 | : to.meta.colorMode 56 | 57 | if (forcedColorMode && forcedColorMode !== 'system') { 58 | colorMode.value = htmlAttrs['data-color-mode-forced'] = forcedColorMode 59 | if (dataValue) { 60 | htmlAttrs[`data-${dataValue}`] = colorMode.value 61 | } 62 | colorMode.forced = true 63 | } else if (forcedColorMode === 'system') { 64 | // eslint-disable-next-line no-console 65 | console.warn('You cannot force the colorMode to system at the page level.') 66 | } 67 | }) 68 | 69 | nuxtApp.provide('colorMode', colorMode) 70 | }) 71 | -------------------------------------------------------------------------------- /test/ssr.test.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { readFile } from 'fs-extra' 3 | import { setupTest, get, getContext } from '@nuxt/test-utils' 4 | 5 | describe('ssr: true, dev mode', () => { 6 | const rootDir = join(__dirname, '..', 'playground') 7 | 8 | setupTest({ 9 | server: true, 10 | rootDir, 11 | config: { 12 | ssr: true, 13 | target: 'static' 14 | } 15 | }) 16 | 17 | test('render', async () => { 18 | const { body } = await get('/') 19 | expect(body).toContain('nuxt-color-mode-script') 20 | }) 21 | }) 22 | 23 | describe('ssr: true, target: server, prod mode', () => { 24 | const rootDir = join(__dirname, '..', 'playground') 25 | 26 | setupTest({ 27 | server: true, 28 | build: true, 29 | rootDir, 30 | config: { 31 | ssr: true, 32 | target: 'server' 33 | } 34 | }) 35 | 36 | test('render', async () => { 37 | const { body, headers } = await get('/') 38 | expect(body).toContain('nuxt-color-mode-script') 39 | expect(headers['content-security-policy']).toBeUndefined() 40 | }) 41 | }) 42 | 43 | describe('ssr: true, target: static, generated files', () => { 44 | const rootDir = join(__dirname, '..', 'playground') 45 | 46 | setupTest({ 47 | generate: true, 48 | rootDir, 49 | config: { 50 | ssr: true, 51 | target: 'static' 52 | } 53 | }) 54 | 55 | test('generated file', async () => { 56 | const { nuxt } = getContext() 57 | const generateDir = nuxt.options.generate.dir 58 | const files = ['index.html', '200.html'] 59 | for (const file of files) { 60 | const contents = await readFile(join(generateDir, file), 'utf-8') 61 | expect(contents).toMatch('nuxt-color-mode-script') 62 | } 63 | }) 64 | }) 65 | 66 | describe('ssr: true, csp hash on script', () => { 67 | const rootDir = join(__dirname, '..', 'playground') 68 | 69 | setupTest({ 70 | server: true, 71 | build: true, 72 | rootDir, 73 | config: { 74 | ssr: true, 75 | render: { 76 | csp: true 77 | } 78 | } 79 | }) 80 | 81 | test('csp hash on script', async () => { 82 | const { headers } = await get('/') 83 | expect(headers['content-security-policy']).toContain('sha256-') 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /playground/components/NuxtLogo.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![@nuxtjs/color-mode](https://color-mode.nuxtjs.org/preview.png)](https://color-mode.nuxtjs.org) 2 | 3 | # @nuxtjs/color-mode 4 | 5 | [![npm version][npm-version-src]][npm-version-href] 6 | [![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] 7 | [![Codecov][codecov-src]][codecov-href] 8 | [![License][license-src]][license-href] 9 | 10 | > 🌑 Dark and 🌕 Light mode with auto detection made easy with Nuxt 11 | 12 | [![nuxt-color-mode](https://user-images.githubusercontent.com/904724/79349768-f09cf080-7f36-11ea-93bb-20fae8c94811.gif)](https://color-mode.nuxtjs.app/) 13 | 14 |

15 | Live demo 16 |

17 | 18 | - [✨  Release Notes](https://color-mode.nuxtjs.org/releases) 19 | - [📖  Documentation](https://color-mode.nuxtjs.org) 20 | - [▶️  Online playground](https://stackblitz.com/edit/nuxt-color-mode) 21 | 22 | ## Features 23 | 24 | - Nuxt 3 and Nuxt Bridge support 25 | - Add `.${color}-mode` class to `` for easy CSS theming 26 | - Force a page to a specific color mode (perfect for incremental development) 27 | - Works with client-side and universal rendering 28 | - Auto detect system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode) 29 | - Sync dark mode across tabs and windows 🔄 30 | - Supports IE9+ 👴 31 | 32 | [📖  Read more](https://color-mode.nuxtjs.org) 33 | 34 | **Note**: v3 of `@nuxtjs/color-mode` is compatible with [Nuxt 3 and Nuxt Bridge](https://v3.nuxtjs.org/). If you're looking for the previous version of this module, check out [the previous docs](https://v2.color-mode.nuxtjs.org/), or [read more about the differences](https://color-mode.nuxtjs.org/#migrating-to-v3). 35 | 36 | ## Contributing 37 | 38 | You can contribute to this module online with CodeSandBox: 39 | 40 | [![Edit @nuxtjs/color-mode](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/nuxt-community/color-mode-module/tree/master/?fontsize=14&hidenavigation=1&theme=dark) 41 | 42 | Or locally: 43 | 44 | 1. Clone this repository 45 | 2. Install dependencies using `yarn install` or `npm install` 46 | 3. Start development server using `yarn dev` or `npm run dev` 47 | 48 | ## License 49 | 50 | [MIT License](./LICENSE) 51 | 52 | Copyright (c) Nuxt Team 53 | 54 | 55 | [npm-version-src]: https://img.shields.io/npm/v/@nuxtjs/color-mode/latest.svg 56 | [npm-version-href]: https://npmjs.com/package/@nuxtjs/color-mode 57 | 58 | [npm-downloads-src]: https://img.shields.io/npm/dt/@nuxtjs/color-mode.svg 59 | [npm-downloads-href]: https://npmjs.com/package/@nuxtjs/color-mode 60 | 61 | [github-actions-ci-src]: https://github.com/nuxt-community/color-mode-module/workflows/ci/badge.svg 62 | [github-actions-ci-href]: https://github.com/nuxt-community/color-mode-module/actions?query=workflow%3Aci 63 | 64 | [codecov-src]: https://img.shields.io/codecov/c/github/nuxt-community/color-mode-module.svg 65 | [codecov-href]: https://codecov.io/gh/nuxt-community/color-mode-module 66 | 67 | [license-src]: https://img.shields.io/npm/l/@nuxtjs/color-mode.svg 68 | [license-href]: https://npmjs.com/package/@nuxtjs/color-mode 69 | -------------------------------------------------------------------------------- /src/runtime/plugin.client.ts: -------------------------------------------------------------------------------- 1 | import { computed, reactive, watch } from 'vue' 2 | 3 | import type { ColorModeInstance } from './types' 4 | import { defineNuxtPlugin, isVue2, isVue3, useRouter, useHead, useState } from '#imports' 5 | import { globalName, storageKey, dataValue } from '#color-mode-options' 6 | 7 | const helper = window[globalName] as unknown as { 8 | preference: string 9 | value: string 10 | getColorScheme: () => string 11 | addColorScheme: (className: string) => void 12 | removeColorScheme: (className: string) => void 13 | } 14 | 15 | export default defineNuxtPlugin((nuxtApp) => { 16 | const colorMode = useState('color-mode', () => reactive({ 17 | // For SPA mode or fallback 18 | preference: helper.preference, 19 | value: helper.value, 20 | unknown: false, 21 | forced: false 22 | })).value 23 | 24 | if (dataValue) { 25 | if (isVue3) { 26 | useHead({ 27 | htmlAttrs: { [`data-${dataValue}`]: computed(() => colorMode.value) } 28 | }) 29 | } else { 30 | const app = nuxtApp.nuxt2Context.app 31 | const originalHead = app.head 32 | app.head = function () { 33 | const head = (typeof originalHead === 'function' ? originalHead.call(this) : originalHead) || {} 34 | head.htmlAttrs = head.htmlAttrs || {} 35 | head.htmlAttrs[`data-${dataValue}`] = colorMode.value 36 | return head 37 | } 38 | } 39 | } 40 | 41 | useRouter().afterEach((to) => { 42 | const forcedColorMode = isVue2 43 | ? (to.matched[0]?.components.default as any)?.options.colorMode 44 | : to.meta.colorMode 45 | 46 | if (forcedColorMode && forcedColorMode !== 'system') { 47 | colorMode.value = forcedColorMode 48 | colorMode.forced = true 49 | } else { 50 | if (forcedColorMode === 'system') { 51 | // eslint-disable-next-line no-console 52 | console.warn('You cannot force the colorMode to system at the page level.') 53 | } 54 | colorMode.forced = false 55 | colorMode.value = colorMode.preference === 'system' 56 | ? helper.getColorScheme() 57 | : colorMode.preference 58 | } 59 | }) 60 | 61 | let darkWatcher: MediaQueryList 62 | 63 | function watchMedia () { 64 | if (darkWatcher || !window.matchMedia) { return } 65 | 66 | darkWatcher = window.matchMedia('(prefers-color-scheme: dark)') 67 | darkWatcher.addEventListener('change', () => { 68 | if (!colorMode.forced && colorMode.preference === 'system') { 69 | colorMode.value = helper.getColorScheme() 70 | } 71 | }) 72 | } 73 | 74 | function watchStorageChange () { 75 | window.addEventListener('storage', (e) => { 76 | if (e.key === storageKey && e.newValue && colorMode.preference !== e.newValue) { 77 | colorMode.preference = e.newValue 78 | } 79 | }) 80 | } 81 | 82 | watch(() => colorMode.preference, (preference) => { 83 | if (colorMode.forced) { 84 | return 85 | } 86 | if (preference === 'system') { 87 | colorMode.value = helper.getColorScheme() 88 | watchMedia() 89 | } else { 90 | colorMode.value = preference 91 | } 92 | 93 | // Local storage to sync with other tabs 94 | window.localStorage?.setItem(storageKey, preference) 95 | }, { immediate: true }) 96 | 97 | watch(() => colorMode.value, (newValue, oldValue) => { 98 | helper.removeColorScheme(oldValue) 99 | helper.addColorScheme(newValue) 100 | }) 101 | 102 | if (colorMode.preference === 'system') { 103 | watchMedia() 104 | } 105 | 106 | nuxtApp.hook('app:mounted', () => { 107 | if (window.localStorage) { 108 | watchStorageChange() 109 | } 110 | if (colorMode.unknown) { 111 | colorMode.preference = helper.preference 112 | colorMode.value = helper.value 113 | colorMode.unknown = false 114 | } 115 | }) 116 | 117 | nuxtApp.provide('colorMode', colorMode) 118 | }) 119 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { promises as fsp } from 'fs' 2 | import { join, resolve } from 'pathe' 3 | import template from 'lodash.template' 4 | import { addPlugin, addTemplate, defineNuxtModule, isNuxt2, addComponent, addAutoImport, createResolver } from '@nuxt/kit' 5 | 6 | import { name, version } from '../package.json' 7 | 8 | const DEFAULTS: ModuleOptions = { 9 | preference: 'system', 10 | fallback: 'light', 11 | hid: 'nuxt-color-mode-script', 12 | globalName: '__NUXT_COLOR_MODE__', 13 | componentName: 'ColorScheme', 14 | classPrefix: '', 15 | classSuffix: '-mode', 16 | dataValue: '', 17 | storageKey: 'nuxt-color-mode' 18 | } 19 | 20 | export default defineNuxtModule({ 21 | meta: { 22 | name, 23 | version, 24 | configKey: 'colorMode', 25 | compatibility: { 26 | bridge: true 27 | } 28 | }, 29 | defaults: DEFAULTS, 30 | async setup (options, nuxt) { 31 | const resolver = createResolver(import.meta.url) 32 | 33 | // Read script from disk and add to options 34 | const scriptPath = await resolver.resolve('./script.min.js') 35 | const scriptT = await fsp.readFile(scriptPath, 'utf-8') 36 | options.script = template(scriptT)({ options }) 37 | 38 | // Inject options via virtual template 39 | nuxt.options.alias['#color-mode-options'] = addTemplate({ 40 | filename: 'color-mode-options.mjs', 41 | getContents: () => Object.entries(options).map(([key, value]) => 42 | `export const ${key} = ${JSON.stringify(value, null, 2)} 43 | `).join('\n') 44 | }).dst 45 | 46 | const runtimeDir = await resolver.resolve('./runtime') 47 | nuxt.options.build.transpile.push(runtimeDir) 48 | 49 | // Add plugins 50 | for (const template of ['plugin.client', 'plugin.server']) { 51 | addPlugin(resolve(runtimeDir, template)) 52 | } 53 | 54 | addComponent({ name: options.componentName, filePath: resolve(runtimeDir, 'component.' + (isNuxt2() ? 'vue2' : 'vue3') + '.vue') }) 55 | addAutoImport({ name: 'useColorMode', as: 'useColorMode', from: resolve(runtimeDir, 'composables') }) 56 | 57 | // Nuxt 3 - SSR false 58 | if (!nuxt.options.ssr) { 59 | // @ts-ignore TODO: use nitro plugin 60 | nuxt.hook('nitro:document', (template) => { 61 | template.contents = template.contents.replace('', ``) 62 | }) 63 | } 64 | 65 | if (!isNuxt2()) { 66 | return 67 | } 68 | 69 | // Nuxt 2 - SSR false 70 | nuxt.hook('vue-renderer:spa:prepareContext', ({ head }) => { 71 | const script = { 72 | hid: options.hid, 73 | innerHTML: options.script, 74 | pbody: true 75 | } 76 | 77 | head.script.push(script) 78 | 79 | const serializeProp = '__dangerouslyDisableSanitizersByTagID' 80 | head[serializeProp] = head[serializeProp] || {} 81 | head[serializeProp][options.hid] = ['innerHTML'] 82 | }) 83 | 84 | const createHash = await import('crypto').then(r => r.createHash) 85 | 86 | // Nuxt 2 - SSR true 87 | nuxt.hook('vue-renderer:ssr:csp', (cspScriptSrcHashes) => { 88 | const { csp } = nuxt.options.render 89 | const hash = createHash((csp as any).hashAlgorithm) 90 | hash.update(options.script!) 91 | cspScriptSrcHashes.push( 92 | `'${(csp as any).hashAlgorithm}-${hash.digest('base64')}'` 93 | ) 94 | }) 95 | 96 | // In Nuxt 2 dev mode we also inject full script via webpack entrypoint for storybook compatibility 97 | if (nuxt.options.dev) { 98 | const { dst } = addTemplate({ 99 | src: scriptPath, 100 | fileName: join('color-mode', 'script.min.js'), 101 | options 102 | }) 103 | nuxt.hook('webpack:config', (configs) => { 104 | for (const config of configs) { 105 | if (config.name !== 'server') { 106 | (config.entry as any).app.unshift(resolve(nuxt.options.buildDir, dst!)) 107 | } 108 | } 109 | }) 110 | } 111 | } 112 | }) 113 | 114 | export interface ModuleOptions { 115 | /** 116 | * The default value of $colorMode.preference 117 | * @default `system` 118 | */ 119 | preference: string 120 | /** 121 | * Fallback value if no system preference found 122 | * @default `light` 123 | */ 124 | fallback: string 125 | /** 126 | * @default `nuxt-color-mode-script` 127 | */ 128 | hid: string 129 | /** 130 | * @default `__NUXT_COLOR_MODE__` 131 | */ 132 | globalName: string 133 | /** 134 | * @default `ColorScheme` 135 | */ 136 | componentName: string 137 | /** 138 | * @default '' 139 | */ 140 | classPrefix: string 141 | /** 142 | * @default '-mode' 143 | */ 144 | classSuffix: string 145 | /** 146 | * Whether to add a data attribute to the html tag. If set, it defines the key of the data attribute. 147 | * For example, setting this to `theme` will output `` if dark mode is enabled. 148 | * @default '' 149 | */ 150 | dataValue: string 151 | /** 152 | * @default 'nuxt-color-mode' 153 | */ 154 | storageKey: string 155 | /** 156 | * The script that will be injected into the head of the page 157 | */ 158 | script?: string 159 | } 160 | -------------------------------------------------------------------------------- /docs/static/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/static/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/content/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Documentation' 3 | description: 'Dark and Light mode with auto detection made easy with Nuxt 🌗' 4 | category: 'Home' 5 | csb_link: https://codesandbox.io/embed/github/nuxt-community/color-mode-module/tree/master/?autoresize=1&fontsize=14&hidenavigation=1&module=%2Fplayground%2Fpages%2Findex.vue&theme=dark&view=preview 6 | --- 7 | 8 | 9 | 10 | v3 of `@nuxtjs/color-mode` is compatible with [Nuxt 3 and Nuxt Bridge](https://v3.nuxtjs.org/). If you're looking for the previous version of this module, check out [the previous docs](https://v2.color-mode.nuxtjs.org/), or [read more about the differences](#migrating-to-v3). 11 | 12 | 13 | 14 | ## Features 15 | 16 | - Nuxt 3 and Nuxt Bridge support 17 | - Add `.${color}-mode` class to `` for easy CSS theming 18 | - Force a page to a specific color mode (perfect for incremental development) 19 | - Works with client-side and universal rendering 20 | - Auto detect system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode) 21 | - Sync dark mode across tabs and windows 🔄 22 | - Supports IE9+ 👴 23 | 24 | ## Live demo 25 | 26 | Nuxt color mode demo 27 | 28 | Checkout the online demo. 29 | 30 | ## Setup 31 | 32 | 33 | 34 | `@nuxtjs/color-mode` version 3 supports Nuxt Bridge and Nuxt 3 only. For use in Nuxt 2 (without Bridge), make sure to install version 2. 35 | 36 | 37 | 38 | Add `@nuxtjs/color-mode` dependency to your project: 39 | 40 | 41 | 42 | 43 | ```bash 44 | yarn add --dev @nuxtjs/color-mode 45 | ``` 46 | 47 | 48 | 49 | 50 | ```bash 51 | npm install --save-dev @nuxtjs/color-mode 52 | ``` 53 | 54 | 55 | 56 | 57 | Then, add `@nuxtjs/color-mode` to the `modules` section of your `nuxt.config.js` 58 | 59 | ```js{}[nuxt.config.js] 60 | import { defineNuxtConfig } from 'nuxt' 61 | 62 | export default defineNuxtConfig({ 63 | modules: ['@nuxtjs/color-mode'] 64 | }) 65 | ``` 66 | 67 | You are ready to start theming your CSS with `.dark-mode` and `.light-mode` classes ✨ 68 | 69 | ## Usage 70 | 71 | You can access the color mode helper by either calling `useColorMode()` or accessing `$colorMode` directly in your template. This helper has the following properties: 72 | 73 | - `preference`: Actual color-mode selected (can be `'system'`), update it to change the user preferred color mode 74 | - `value`: Useful to know what color mode has been detected when `$colorMode === 'system'`, you should not update it 75 | - `unknown`: Useful to know if during SSR or Generate, we need to render a placeholder 76 | - `forced`: Useful to know if the current color mode is forced by the current page (useful to hide the color picker) 77 | 78 | ```html 79 | 90 | 91 | 95 | 96 | 110 | ``` 111 | 112 | ## Force a color mode 113 | 114 | You can force the color mode at the page level (only parent) by setting the `colorMode` property: 115 | 116 | ```html{}[pages/light.vue] 117 | 120 | 121 | 131 | ``` 132 | 133 | This feature is perfect for implementing dark mode to a website incrementally by setting the not-ready pages to `colorMode: 'light'`. 134 | 135 | 136 | 137 | We recommend to hide or disable the color mode picker on the page since it won't be able to change the current page color mode, using `$colorMode.forced` value. 138 | 139 | 140 | 141 | ## Example 142 | 143 | You can see a more advanced example in the [playground/ directory](https://github.com/nuxt-community/color-mode-module/tree/master/playground) or play online with the CodeSandBox below: 144 | 145 | 146 | 147 | ## Configuration 148 | 149 | You can configure the module by providing the `colorMode` property in your `nuxt.config.js`; here are the default options: 150 | 151 | ```js 152 | import { defineNuxtConfig } from 'nuxt' 153 | 154 | export default defineNuxtConfig({ 155 | modules: ['@nuxtjs/color-mode'], 156 | colorMode: { 157 | preference: 'system', // default value of $colorMode.preference 158 | fallback: 'light', // fallback value if not system preference found 159 | hid: 'nuxt-color-mode-script', 160 | globalName: '__NUXT_COLOR_MODE__', 161 | componentName: 'ColorScheme', 162 | classPrefix: '', 163 | classSuffix: '-mode', 164 | storageKey: 'nuxt-color-mode' 165 | } 166 | }) 167 | ``` 168 | 169 | Notes: 170 | - `'system'` is a special value; it will automatically detect the color mode based on the system preferences (see [prefers-color-mode spec](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode)). The value injected will be either `'light'` or `'dark'`. If `no-preference` is detected or the browser does not handle color-mode, it will set the `fallback` value. 171 | 172 | ## Caveats 173 | 174 | When `$colorMode.preference` is set to `'system'`, using `$colorMode` in your Vue template will lead to a flash. This is due to the fact that we cannot know the user preferences when pre-rendering the page since they are detected on client-side. 175 | 176 | To avoid the flash, you have to guard any rendering path which depends on `$colorMode` with `$colorMode.unknown` to render a placeholder or use our `` component. 177 | 178 | **Example:** 179 | 180 | ```vue 181 | 187 | ``` 188 | 189 | Props: 190 | - `placeholder`: `String` 191 | - `tag`: `String`, default: `'span'` 192 | 193 | ## TailwindCSS 194 | 195 | ### Tailwind v2 196 | 197 | Tailwind v2 introduced [dark mode](https://tailwindcss.com/docs/dark-mode), in order to work with `@nuxtjs/color-mode`, you need to set `darkMode: 'class'` in your `tailwind.config.js`: 198 | 199 | ```js{}[tailwind.config.js] 200 | module.exports = { 201 | darkMode: 'class' 202 | } 203 | ``` 204 | 205 | Then in your `nuxt.config.js`, set the `classSuffix` option to an empty string: 206 | 207 | ```js{}[nuxt.config.js] 208 | import { defineNuxtConfig } from 'nuxt' 209 | 210 | export default defineNuxtConfig({ 211 | modules: ['@nuxtjs/color-mode'], 212 | colorMode: { 213 | classSuffix: '' 214 | } 215 | }) 216 | ``` 217 | 218 | Checkout the [live example on CodeSandBox](https://codesandbox.io/s/nuxt-dark-tailwindcss-vxfuj). 219 | 220 | ### Tailwind Dark Mode Plugin 221 | 222 | You can easily integrate this module with [tailwindcss-dark-mode](https://github.com/ChanceArthur/tailwindcss-dark-mode) by just setting `darkSelector: '.dark-mode'`, see [changing the selector documentation](https://github.com/ChanceArthur/tailwindcss-dark-mode#changing-the-selector). 223 | 224 | ```js 225 | // tailwind.config.js 226 | module.exports = { 227 | theme: { 228 | darkSelector: '.dark-mode' 229 | }, 230 | variants: { 231 | backgroundColor: ["dark", "dark-hover", "dark-group-hover", "dark-even", "dark-odd", "hover", "responsive"], 232 | borderColor: ["dark", "dark-focus", "dark-focus-within", "hover", "responsive"], 233 | textColor: ["dark", "dark-hover", "dark-active", "hover", "responsive"] 234 | }, 235 | plugins: [ 236 | require('tailwindcss-dark-mode')() 237 | ] 238 | } 239 | ``` 240 | 241 | Checkout a [live example on CodeSandBox](https://codesandbox.io/s/nuxt-dark-tailwindcss-17g2j?file=/pages/index.vue) as well as [@nuxtjs/tailwindcss](https://github.com/nuxt-community/tailwindcss-module) module. 242 | 243 | ## Migrating to v3 244 | 245 | v3 of `@nuxtjs/color-mode` requires either Nuxt Bridge or Nuxt 3. (If you are using Nuxt 2 without Bridge, you should continue to use v2.) 246 | 247 | 1. The main change between Nuxt 2 -> Nuxt 3 is that you will define your color mode at the page level with `definePageMeta`: 248 | 249 | ```diff 250 | 253 | 254 | 262 | ``` 263 | 264 | ⚠️ If you are using Nuxt Bridge, you should not use `definePageMeta` but instead continue using the component option `colorMode`. 265 | 266 | 2. The `$colorMode` helper remains the same, but there is also a new composable (`useColorMode`) which is the recommended way of accessing color mode information. 267 | 268 | 3. If you were directly importing color mode configuration types, note that this has been renamed to `ModuleOptions`. 269 | 270 | ## Contributing 271 | 272 | You can contribute to this module online with CodeSandBox: 273 | 274 | [![Edit @nuxtjs/color-mode](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/nuxt-community/color-mode-module/tree/master/?fontsize=14&hidenavigation=1&theme=dark) 275 | 276 | Or locally: 277 | 278 | 1. Clone this repository 279 | 2. Install dependencies using `yarn install` or `npm install` 280 | 3. Start development server using `yarn dev` or `npm run dev` 281 | 282 | ## License 283 | 284 | [MIT License](https://github.com/nuxt-community/color-mode-module/blob/master/LICENSE) 285 | 286 | Copyright (c) Nuxt Team 287 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [3.1.3](https://github.com/nuxt-community/color-mode-module/compare/v3.1.2...v3.1.3) (2022-05-31) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * handle data attribute in script as well ([30b173e](https://github.com/nuxt-community/color-mode-module/commit/30b173e4ffebcd452ecc076e3660290907af196f)) 11 | 12 | ### [3.1.2](https://github.com/nuxt-community/color-mode-module/compare/v3.1.1...v3.1.2) (2022-05-31) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * add missing imports in client-plugin ([c0ce7b2](https://github.com/nuxt-community/color-mode-module/commit/c0ce7b2944e099600bcfb1541b42b9398821daa1)) 18 | 19 | ### [3.1.1](https://github.com/nuxt-community/color-mode-module/compare/v3.1.0...v3.1.1) (2022-05-31) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * add missing import ([70665db](https://github.com/nuxt-community/color-mode-module/commit/70665dbc6477f927d246c7eaa92ba177b2d41a7a)) 25 | 26 | ## [3.1.0](https://github.com/nuxt-community/color-mode-module/compare/v3.0.3...v3.1.0) (2022-05-31) 27 | 28 | 29 | ### Features 30 | 31 | * add support for `data-*` attribute in `` tag ([#144](https://github.com/nuxt-community/color-mode-module/issues/144)) ([7a92150](https://github.com/nuxt-community/color-mode-module/commit/7a92150a802aba29b6db0d78b3dfba3535bf3ceb)) 32 | 33 | ### [3.0.3](https://github.com/nuxt-community/color-mode-module/compare/v3.0.2...v3.0.3) (2022-05-10) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * **plugin:** prevent infinite loop on watch ([#145](https://github.com/nuxt-community/color-mode-module/issues/145)) ([d4ee818](https://github.com/nuxt-community/color-mode-module/commit/d4ee8186e5073e248baa8cea6438f8a50f6a9f19)) 39 | * rename useMeta to useHead ([#135](https://github.com/nuxt-community/color-mode-module/issues/135)) ([63d86ab](https://github.com/nuxt-community/color-mode-module/commit/63d86ab9702e247a89ca18167874051850665dec)) 40 | 41 | ### [3.0.2](https://github.com/nuxt-community/color-mode-module/compare/v3.0.1...v3.0.2) (2022-03-07) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * type helper as `$colorMode` not `colorMode` ([b2d89bd](https://github.com/nuxt-community/color-mode-module/commit/b2d89bd5f3048b92b7b43ec21b7e74e4f1ae2454)), closes [#130](https://github.com/nuxt-community/color-mode-module/issues/130) 47 | 48 | ### [3.0.1](https://github.com/nuxt-community/color-mode-module/compare/v3.0.0...v3.0.1) (2022-02-21) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * move script to head and set forced color mode on `` element ([#128](https://github.com/nuxt-community/color-mode-module/issues/128)) ([273f07e](https://github.com/nuxt-community/color-mode-module/commit/273f07ebda0d87fc7574d09658e60a9863ce84a8)) 54 | 55 | ## [3.0.0](https://github.com/nuxt-community/color-mode-module/compare/v2.1.1...v3.0.0) (2022-02-16) 56 | 57 | 58 | ### ⚠ BREAKING CHANGES 59 | 60 | * add support for nuxt 3/nuxt bridge (#118) 61 | 62 | ### Features 63 | 64 | * add support for nuxt 3/nuxt bridge ([#118](https://github.com/nuxt-community/color-mode-module/issues/118)) ([a5036a4](https://github.com/nuxt-community/color-mode-module/commit/a5036a4a816a3baa2bc2a953048469a7a78a851e)) 65 | 66 | ### [2.1.1](https://github.com/nuxt-community/color-mode-module/compare/v2.1.0...v2.1.1) (2021-08-02) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * add script to webpack build (in dev) for storybook support ([#99](https://github.com/nuxt-community/color-mode-module/issues/99)) ([54b482b](https://github.com/nuxt-community/color-mode-module/commit/54b482b5cd8a63c42575165a02d96a0e1c959cc4)), closes [#69](https://github.com/nuxt-community/color-mode-module/issues/69) 72 | 73 | ## [2.1.0](https://github.com/nuxt-community/color-mode-module/compare/v2.0.10...v2.1.0) (2021-08-02) 74 | 75 | 76 | ### Features 77 | 78 | * add csp hash for color script ([#94](https://github.com/nuxt-community/color-mode-module/issues/94)) ([e2f1ffc](https://github.com/nuxt-community/color-mode-module/commit/e2f1ffc2a64dd22d7ed5b40fc6bc78562e47c9d0)) 79 | 80 | ### [2.0.10](https://github.com/nuxt-community/color-mode-module/compare/v2.0.9...v2.0.10) (2021-06-11) 81 | 82 | 83 | ### Bug Fixes 84 | 85 | * module stability ([#92](https://github.com/nuxt-community/color-mode-module/issues/92)) ([e9aa91c](https://github.com/nuxt-community/color-mode-module/commit/e9aa91c8caf56d6cae1dfe641034a1960bee6269)) 86 | 87 | ### [2.0.9](https://github.com/nuxt-community/color-mode-module/compare/v2.0.8...v2.0.9) (2021-05-24) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * move ssr script injection into plugin for nitro compat ([#86](https://github.com/nuxt-community/color-mode-module/issues/86)) ([9002bb0](https://github.com/nuxt-community/color-mode-module/commit/9002bb0d696bac7945077d6bcafe3993866ebb2f)) 93 | 94 | ### [2.0.8](https://github.com/nuxt-community/color-mode-module/compare/v2.0.7...v2.0.8) (2021-05-20) 95 | 96 | ### [2.0.7](https://github.com/nuxt-community/color-mode-module/compare/v2.0.5...v2.0.7) (2021-05-20) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * move script injection into plugin for nitro compat ([#85](https://github.com/nuxt-community/color-mode-module/issues/85)) ([fb31651](https://github.com/nuxt-community/color-mode-module/commit/fb31651d7103a972d2e922d623565d791b84078c)) 102 | 103 | ### [2.0.6](https://github.com/nuxt-community/color-mode-module/compare/v2.0.5...v2.0.6) (2021-05-20) 104 | 105 | ### [2.0.5](https://github.com/nuxt-community/color-mode-module/compare/v2.0.4...v2.0.5) (2021-03-10) 106 | 107 | 108 | ### Bug Fixes 109 | 110 | * spa mode ([139b0e6](https://github.com/nuxt-community/color-mode-module/commit/139b0e68fced030db9035839ffdfd2fa1d80c117)) 111 | 112 | ### [2.0.4](https://github.com/nuxt-community/color-mode-module/compare/v2.0.3...v2.0.4) (2021-03-03) 113 | 114 | 115 | ### Bug Fixes 116 | 117 | * types compatible with nuxt-property-decorator ([#72](https://github.com/nuxt-community/color-mode-module/issues/72)) ([43f5806](https://github.com/nuxt-community/color-mode-module/commit/43f5806b84f407d55f7de939348e25293ebbaa12)) 118 | 119 | ### [2.0.3](https://github.com/nuxt-community/color-mode-module/compare/v2.0.2...v2.0.3) (2021-01-25) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * **type-defs:** make interface ColorModeInstance extend Vue ([#62](https://github.com/nuxt-community/color-mode-module/issues/62)) ([bac6667](https://github.com/nuxt-community/color-mode-module/commit/bac6667553c1801cd6aaefcce0d829bab03d663a)) 125 | * don't use window in created ([#59](https://github.com/nuxt-community/color-mode-module/issues/59)) ([9b47c6e](https://github.com/nuxt-community/color-mode-module/commit/9b47c6ea630e35c8958a62f5b645fd3ef7216846)) 126 | 127 | ### [2.0.2](https://github.com/nuxt-community/color-mode-module/compare/v2.0.1...v2.0.2) (2020-12-04) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * **type-defs:** make all options optional ([#56](https://github.com/nuxt-community/color-mode-module/issues/56)) ([7d2aaff](https://github.com/nuxt-community/color-mode-module/commit/7d2aaff02d0e81327a88be67310ef49263055bad)) 133 | 134 | ### [2.0.1](https://github.com/nuxt-community/color-mode-module/compare/v2.0.0...v2.0.1) (2020-12-02) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * **type-defs:** add types reference to package ([#52](https://github.com/nuxt-community/color-mode-module/issues/52)) ([82592f4](https://github.com/nuxt-community/color-mode-module/commit/82592f4451f696bb8cf2c5d2282fc7b60ac7cbb5)) 140 | 141 | ## [2.0.0](https://github.com/nuxt-community/color-mode-module/compare/v1.1.1...v2.0.0) (2020-10-13) 142 | 143 | 144 | ### ⚠ BREAKING CHANGES 145 | 146 | * Version 2 (#39) 147 | 148 | ### Features 149 | 150 | * Version 2 ([#39](https://github.com/nuxt-community/color-mode-module/issues/39)) ([47664d7](https://github.com/nuxt-community/color-mode-module/commit/47664d76dc76a44ff270a6cd8569f512e5b004f3)) 151 | 152 | ### [1.1.1](https://github.com/nuxt-community/color-mode-module/compare/v1.1.0...v1.1.1) (2020-09-15) 153 | 154 | 155 | ### Bug Fixes 156 | 157 | * **types:** add new options and include in package ([00bb7e8](https://github.com/nuxt-community/color-mode-module/commit/00bb7e85a114e5f50941b5445395b7b09d9a5a68)) 158 | 159 | ## [1.1.0](https://github.com/nuxt-community/color-mode-module/compare/v1.0.3...v1.1.0) (2020-09-15) 160 | 161 | 162 | ### Features 163 | 164 | * add classPrefix and classSuffix ([0f2141c](https://github.com/nuxt-community/color-mode-module/commit/0f2141c182cfef81321d8cd3bed39d173ec281b3)) 165 | * add types declaration ([#27](https://github.com/nuxt-community/color-mode-module/issues/27)) ([078d38f](https://github.com/nuxt-community/color-mode-module/commit/078d38f6347a297f388ef65aedfa503892bf73b0)), closes [#15](https://github.com/nuxt-community/color-mode-module/issues/15) 166 | 167 | ### [1.0.3](https://github.com/nuxt-community/color-mode-module/compare/v1.0.2...v1.0.3) (2020-08-04) 168 | 169 | 170 | ### Bug Fixes 171 | 172 | * handle spa fallback (regression) ([b92d64b](https://github.com/nuxt-community/color-mode-module/commit/b92d64b29a15bb901abe406aa8dda5709b5caacc)), closes [#21](https://github.com/nuxt-community/color-mode-module/issues/21) 173 | 174 | ### [1.0.2](https://github.com/nuxt-community/color-mode-module/compare/v1.0.1...v1.0.2) (2020-07-28) 175 | 176 | 177 | ### Bug Fixes 178 | 179 | * wait hydration to update $colorMode ([7a873ab](https://github.com/nuxt-community/color-mode-module/commit/7a873ab1f6f862b608923e904ae153b8f89864fc)) 180 | 181 | ### [1.0.1](https://github.com/nuxt-community/color-mode-module/compare/v1.0.0...v1.0.1) (2020-07-27) 182 | 183 | 184 | ### Bug Fixes 185 | 186 | * add `SameSite=Lax` to cookie ([#18](https://github.com/nuxt-community/color-mode-module/issues/18)) ([f236c93](https://github.com/nuxt-community/color-mode-module/commit/f236c93ae6092c8d273c9241901a47e01e058845)) 187 | * broken link ([#17](https://github.com/nuxt-community/color-mode-module/issues/17)) ([a4f0e02](https://github.com/nuxt-community/color-mode-module/commit/a4f0e021e31a1fedaef249f38cfd1e88a9e0ea19)) 188 | * correct spelling for preferred ([#10](https://github.com/nuxt-community/color-mode-module/issues/10)) ([cd565c5](https://github.com/nuxt-community/color-mode-module/commit/cd565c5f7425cde41b5255631ad088e2a1f4eff2)) 189 | * fix correct spelling for preferred ([#9](https://github.com/nuxt-community/color-mode-module/issues/9)) ([8f65b8d](https://github.com/nuxt-community/color-mode-module/commit/8f65b8d21955fc13c5c3428edb6b3f6fea2e3795)) 190 | 191 | ### [1.0.0](https://github.com/nuxt-community/color-mode-module/compare/v0.0.2...v1.0.0) (2020-04-15) 192 | 193 | ### Features 194 | 195 | * overall improvements ([#1](https://github.com/nuxt-community/color-mode-module/issues/1)) ([be3dd4b](https://github.com/nuxt-community/color-mode-module/commit/be3dd4b1885e025d05cac13f921ce338628eb305)) 196 | 197 | 198 | ### Bug Fixes 199 | 200 | * color-scheme component and hydration ([6f026bc](https://github.com/nuxt-community/color-mode-module/commit/6f026bc88eaeb75560b544d7bdafb36debd9f05d)) 201 | 202 | ### [0.0.2](https://github.com/nuxt-community/color-mode-module/compare/v0.0.1...v0.0.2) (2020-04-15) 203 | 204 | 205 | ### Bug Fixes 206 | 207 | * use colorMode options ([507faef](https://github.com/nuxt-community/color-mode-module/commit/507faef219789b674838f0d1de7882e9725664da)) 208 | 209 | ### 0.0.1 (2020-04-15) 210 | --------------------------------------------------------------------------------