theme-mode
v1.1.1
Published
A lightweight utility for switching CSS theme modes
Downloads
373
Maintainers
Readme
Try it out on the Master CSS documentation site.
Features
Vanilla, Next, React, Vue, Svelte, and Master CSS are available:
- ⚡️ Ultra-lightweight ~1.2KB
- 🌈 Switch between
light
,dark
, andsystem
- 💖 Sync with system theme preferences
- 💾 Store the user's preference in
localStorage
- 💫 Access theme preferences and modes through context
- 🧩 Built-in
"use client"
directive
Why should I use this?
The prefers-color-scheme
cannot force override to the specified color mode. Once you want to switch themes, you cannot use @media (prefers-color-scheme: dark)
.
https://stackoverflow.com/questions/56300132/how-to-override-css-prefers-color-scheme-setting
How does this work?
This package automatically switches themes using class=""
and color-scheme:
; that's it.
<html class="dark" style="color-scheme: dark">
<body>
<h1 class="bg:black@dark bg:white@light">Hello World</h1>
</body>
</html>
To view the source code examples:
- React: https://github.com/master-co/theme-mode/tree/main/examples/react
- Vue: https://github.com/master-co/theme-mode/tree/main/examples/vue
- Svelte: https://github.com/master-co/theme-mode/tree/main/examples/svelte
Getting Started
Install the package depending on your framework.
Vanilla
npm install theme-mode
import ThemeMode from 'theme-mode'
const themeMode = new ThemeMode().init()
// Set `preference` anywhere to switch theme modes.
themeMode.preference = 'dark'
React
npm install @master/theme-mode.react
import ThemeModeProvider from '@master/theme-mode.react'
export default function App({ children }) {
return (
<ThemeModeProvider preference='system'>
{children}
</ThemeModeProvider>
)
}
Vue
npm install @master/theme-mode.vue
<script setup lang="ts">
import ThemeModeProvider from '@master/theme-mode.vue'
</script>
<template>
<ThemeModeProvider preference="system">
<slot></slot>
</ThemeModeProvider>
</template>
Svelte
npm install @master/theme-mode.svelte
<script lang="ts">
import ThemeModeProvider from '@master/theme-mode.svelte';
</script>
<ThemeModeProvider preference="system">
...
</ThemeModeProvider>
Basic usage
Default to light or dark mode
You can set the default theme mode when the user has not set a theme preference, such as common light
or dark
mode.
<ThemeModeProvider preference="dark">...</ThemeModeProvider>
Rendered as:
<html class="dark" style="color-scheme: dark">…</html>
Default based on the system preference
Automatically switches modes based on the user's system preference.
<ThemeModeProvider preference="system">...</ThemeModeProvider>
Rendered as:
<html class="light" style="color-scheme: light">…</html>
<!-- or -->
<html class="dark" style="color-scheme: dark">…</html>
Note: CSS only supports light and dark modes for system preferences.
Sync the user's preference to localStorage
By default options.store
is set to 'theme-preference'
, which uses this key to set local storage when the preference is changed.
In this way, the theme preference set last time will be applied when the user visits or refreshes the website again.
To disable local storage, set it to false
.
<ThemeModeProvider store={false}>...</ThemeModeProvider>
Apply styles based on theme modes
You can now create selector-driven CSS themes using tools like Master CSS.
<html class="light" style="color-scheme: light">
<body>
<div class="block@dark" hidden>Dark</div>
<div class="block@light" hidden>Light</div>
<div class="block@christmas" hidden>Christmas</div>
</body>
</html>
Create a theme-switching select
React
import { useThemeMode } from '@master/theme-mode.react'
export default function ThemeModeSelect() {
const themeMode = useThemeMode()
return (
<button>
{themeMode.value === 'dark' ? '🌜' : '☀️'} {themeMode.preference}
<select className="abs full inset:0 opacity:0"
value={themeMode.preference}
onChange={(event) => themeMode.preference = event.target.value}>
<option value="light">☀️ Light</option>
<option value="dark">🌜 Dark</option>
<option value="system">System</option>
</select>
</button>
)
}
Vue
<script setup lang="ts">
import { inject } from 'vue'
const themeMode = inject<any>('theme-mode')
</script>
<template>
<button class="px:5x r:2x font:18 h:48 bg:slate-10@light bg:gray-80@dark fg:strong rel">
{{ themeMode.value === 'dark' ? '🌜' : '☀️' }} {{ themeMode.preference }}
<select class="abs full inset:0 opacity:0" v-model="themeMode.preference">
<option value="light">☀️ Light</option>
<option value="dark">🌜 Dark</option>
<option value="system">System</option>
</select>
</button>
</template>
Svelte
<script lang="ts">
import { getThemeMode } from "@master/theme-mode.svelte";
const themeMode = getThemeMode();
</script>
<span id="value">{$themeMode.value}</span>
<span id="preference">{$themeMode.preference}</span>
<select class="abs full inset:0 opacity:0" bind:value={$themeMode.preference}>
<option value="light">☀️ Light</option>
<option value="dark">🌜 Dark</option>
<option value="system">System</option>
</select>
Avoid FOUC
If you've pre-rendered your CSS styles to the page to improve the page loading and first-render experience, it's crucial to initialize the theme mode in advance.
By default, three modules of minified advanced initial scripts for different default themes are exported:
theme-mode/pre-init
: https://github.com/master-co/theme-mode/tree/main/packages/core/src/pre-init.iife.min.tstheme-mode/pre-init-light
: https://github.com/master-co/theme-mode/tree/main/packages/core/src/pre-init-light.iife.min.tstheme-mode/pre-init-dark
: https://github.com/master-co/theme-mode/tree/main/packages/core/src/pre-init-dark.iife.min.ts
You have to use the build tool to inject these original scripts into HTML <head>
, taking Next.js as an example:
import PRE_INIT_THEME_MODE_SCRIPT from '!!raw-loader!theme-mode/pre-init';
export default async function RootLayout({ children }: {
children: JSX.Element
}) {
return (
<html suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: PRE_INIT_THEME_MODE_SCRIPT }}></script>
...
</head>
...
</html>
)
}
Or copy them directly:
const preference = localStorage.getItem('theme-preference') || 'system';
const value = preference === 'system'
? matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light'
: preference;
document.documentElement.classList.add(value);
if (['dark', 'light'].includes(value)) document.documentElement.style.colorScheme = value;
Those JS resources cannot be loaded from external because this is a critical script for the first painting of the page.
Options
.preference
Specify the default theme preference.
- Default:
undefined
- Value:
'dark'
|'light'
|'system'
|string
.store
Enable local storage and specify the key for localStorage
.
- Default:
'theme-preference'
- Value:
'theme-preference'
|string
|false
Properties
themeMode.preference
Set or get the current theme preference.
- Default:
undefined
- Value:
'dark'
|'light'
|'system'
|string
themeMode.value
Set or get the current theme mode.
- Default:
undefined
- Value:
'dark'
|'light'
|string
themeMode.storage
Get the currently stored theme preference.
- Default:
undefined
- Value:
'dark'
|'light'
|string
themeMode.systemPreference
Get the theme mode of the current system
- Default:
undefined
- Value:
'dark'
|'light'
|string
Methods
themeMode.init()
Initialize the default theme mode. This is usually performed after the DOM has been initialized.
themeMode.destroy()
Destroy the theme mode, including removing media query listeners.
Community
The Master community can be found here:
- Discuss on GitHub - Ask questions, voice ideas, and do any other discussion
- Join our Discord Server - Casually chat with other people using the language ✓ 中文
Our 《 Code of Conduct 》 applies to all Master community channels.
Contributing
Please see our CONTRIBUTING for workflow.