Force color scheme on a page
-Force color scheme on a page
+Script to be injected for avoiding FOUC
-Optional
styles: Record<string, string>Optional
forcedTheme_: stringOptional
forcedColorScheme_: ColorSchemeTypeScript to be injected for avoiding FOUC
+Optional
styles: Record<string, string>Optional
forcedTheme_: stringOptional
forcedColorScheme_: ColorSchemeType<ThemeSwitcher />
-
-<ThemeSwitcher />
+
+
+const ThemeSwitcher = ({
forcedTheme,
forcedColorScheme,
targetSelector,
themeTransition,
styles,
nonce,
}: ThemeSwitcherProps) => {
const k = targetSelector || `#${DEFAULT_ID}`;
// handle client side exceptions when script is not run. <- for client side apps like vite or CRA
if (typeof window !== "undefined" && !window.m)
noFOUCScript(k, initialState, styles, forcedTheme, forcedColorScheme);
const [state, setState] = useThemeStore(targetSelector);
const [forced] = useForcedStore(targetSelector);
useEffect(() => {
media.addEventListener("change", () =>
setState(state => ({ ...state, s: media.matches ? DARK : LIGHT })),
);
addEventListener("storage", e => {
if (e.key === k) setState(state => ({ ...state, ...JSON.parse(e.newValue || "{}") }));
});
}, []);
useEffect(() => {
const restoreThansitions = modifyTransition(themeTransition);
updateDOM(resolveTheme(state));
restoreThansitions();
localStorage.setItem(k, JSON.stringify(state));
}, [state]);
useEffect(() => {
updateForcedProps(forcedTheme, forcedColorScheme);
updateDOM(resolveTheme(state));
}, [forcedColorScheme, forcedTheme]);
useEffect(() => {
updateForcedState(forced.f, forced.fc);
updateDOM(resolveTheme(state));
}, [forced]);
return <Script {...{ k, n: nonce, s: styles, t: forcedTheme, c: forcedColorScheme }} />;
}
-
-const ThemeSwitcher = ({
forcedTheme,
forcedColorScheme,
targetSelector,
themeTransition,
styles,
nonce,
}: ThemeSwitcherProps) => {
const k = targetSelector || `#${DEFAULT_ID}`;
// handle client side exceptions when script is not run. <- for client side apps like vite or CRA
if (typeof window !== "undefined" && !window.m)
noFOUCScript(k, initialState, styles, forcedTheme, forcedColorScheme);
const [state, setState] = useThemeStore(targetSelector);
const [forced] = useForcedStore(targetSelector);
useEffect(() => {
media.addEventListener("change", () =>
setState(state => ({ ...state, s: media.matches ? DARK : LIGHT })),
);
addEventListener("storage", e => {
if (e.key === k) setState(state => ({ ...state, ...JSON.parse(e.newValue || "{}") }));
});
}, []);
useEffect(() => {
const restoreThansitions = modifyTransition(themeTransition);
updateDOM(resolveTheme(state));
restoreThansitions();
localStorage.setItem(k, JSON.stringify(state));
}, [state]);
useEffect(() => {
updateForcedProps(forcedTheme, forcedColorScheme);
updateDOM(resolveTheme(state));
}, [forcedColorScheme, forcedTheme]);
useEffect(() => {
updateForcedState(forced.f, forced.fc);
updateDOM(resolveTheme(state));
}, [forced]);
return <Script {...{ k, n: nonce, s: styles, t: forcedTheme, c: forcedColorScheme }} />;
}
+
+
+Optional
targetSelector: stringconst [] = useTheme(options);
-
-const [] = useTheme(options);
+
+
+const useTheme = (targetSelector?: string): UseThemeYield => {
const [state, setState] = useThemeStore(targetSelector);
const [_, setForcedState] = useForcedStore(targetSelector);
useEffect(() => {
resolveTheme = window.r;
}, []);
const setter =
<T>(key: string) =>
(arg: T) =>
setState(state => ({ ...state, [key]: arg }));
const hookResult: UseThemeYield = {
theme: state.t,
darkTheme: state.d,
lightTheme: state.l,
colorSchemePref: state.c,
systemColorScheme: state.s,
resolvedColorScheme: state.c === SYSTEM || state.c === "" ? state.s : state.c,
resolvedTheme: state.t,
setTheme: setter<string>("t"),
setDarkTheme: setter<string>("d"),
setLightTheme: setter<string>("l"),
setThemeSet: ({ darkTheme: d, lightTheme: l }) => setState(state => ({ ...state, d, l })),
setColorSchemePref: setter<ColorSchemeType>("c"),
toggleColorScheme(skipSystem) {
let index = colorSchemes.indexOf(state.c);
const len = colorSchemes.length;
if (index === -1 || (skipSystem && index === len - 1)) index = 0;
setter("c")(colorSchemes[(index + 1) % len]);
},
setForcedColorScheme: forcedColorScheme =>
setForcedState(state => ({ ...state, fc: forcedColorScheme })),
setForcedTheme: forcedTheme => setForcedState(state => ({ ...state, f: forcedTheme })),
};
if (resolveTheme) {
const resolvedValues = resolveTheme(state);
hookResult.resolvedColorScheme = resolvedValues[0];
hookResult.resolvedTheme = resolvedValues[1];
}
return hookResult;
}
-
-const useTheme = (targetSelector?: string): UseThemeYield => {
const [state, setState] = useThemeStore(targetSelector);
const [_, setForcedState] = useForcedStore(targetSelector);
useEffect(() => {
resolveTheme = window.r;
}, []);
const setter =
<T>(key: string) =>
(arg: T) =>
setState(state => ({ ...state, [key]: arg }));
const hookResult: UseThemeYield = {
theme: state.t,
darkTheme: state.d,
lightTheme: state.l,
colorSchemePref: state.c,
systemColorScheme: state.s,
resolvedColorScheme: state.c === SYSTEM || state.c === "" ? state.s : state.c,
resolvedTheme: state.t,
setTheme: setter<string>("t"),
setDarkTheme: setter<string>("d"),
setLightTheme: setter<string>("l"),
setThemeSet: ({ darkTheme: d, lightTheme: l }) => setState(state => ({ ...state, d, l })),
setColorSchemePref: setter<ColorSchemeType>("c"),
toggleColorScheme(skipSystem) {
let index = colorSchemes.indexOf(state.c);
const len = colorSchemes.length;
if (index === -1 || (skipSystem && index === len - 1)) index = 0;
setter("c")(colorSchemes[(index + 1) % len]);
},
setForcedColorScheme: forcedColorScheme =>
setForcedState(state => ({ ...state, fc: forcedColorScheme })),
setForcedTheme: forcedTheme => setForcedState(state => ({ ...state, f: forcedTheme })),
};
if (resolveTheme) {
const resolvedValues = resolveTheme(state);
hookResult.resolvedColorScheme = resolvedValues[0];
hookResult.resolvedTheme = resolvedValues[1];
}
return hookResult;
}
+
+
+Optional
targetSelector: stringNo longer need to add this component for avoiding FOUC.
+No longer need to add this component for avoiding FOUC.
+Optional
targetSelector: stringOptional
targetSelector: stringinternal store
-Optional
targetSelector: stringinternal store
+Optional
targetSelector: stringNote: react18-themes will now be maintained as nextjs-themes
, as server-specific APIs are no longer needed.
🤟 👉 Unleash the Power of React Server Components
This project was inspired by next-themes. Unlike next-themes, nextjs-themes
doesn't require wrapping everything in a provider, allowing you to take full advantage of React 18 Server Components. Additionally, it offers more features and control over your app's theming.
This project was inspired by next-themes. Unlike next-themes, nextjs-themes
doesn't require wrapping everything in a provider, allowing you to take full advantage of React 18 Server Components. Additionally, it offers more features and control over your app's theming.
import from nextjs-themes/client/component
)useTheme
hookCheck out the live example.
$ pnpm add nextjs-themes
+
-$ pnpm add nextjs-themes
-
or
-$ npm install nextjs-themes
-
+$ npm install nextjs-themes
+
+
or
-$ yarn add nextjs-themes
-
-$ yarn add nextjs-themes
+
+
$ pnpm add nextjs-themes-lite
+
-$ pnpm add nextjs-themes-lite
-
or
-$ npm install nextjs-themes-lite
-
+$ npm install nextjs-themes-lite
+
+
or
-$ yarn add nextjs-themes-lite
-
+$ yarn add nextjs-themes-lite
+
+
Note:
r18gs
is a peer dependency
To add dark mode support, modify _app.js
as follows:
import { ThemeSwitcher } from "nextjs-themes";
function MyApp({ Component, pageProps }) {
return (
<>
<ThemeSwitcher forcedTheme={Component.theme} />
<Component {...pageProps} />
</>
);
}
export default MyApp;
+
-To add dark mode support, modify _app.js
as follows:
import { ThemeSwitcher } from "nextjs-themes";
function MyApp({ Component, pageProps }) {
return (
<>
<ThemeSwitcher forcedTheme={Component.theme} />
<Component {...pageProps} />
</>
);
}
export default MyApp;
-
⚡🎉Boom! Dark mode is ready in just a couple of lines!
-app
router (Server Components)Update app/layout.jsx
to add ThemeSwitcher
from nextjs-themes
:
// app/layout.jsx
import { ThemeSwitcher } from "nextjs-themes";
export default function Layout({ children }) {
return (
<html lang="en">
<head />
<body>
<ThemeSwitcher />
{children}
</body>
</html>
);
}
-
+app
router (Server Components)Update app/layout.jsx
to add ThemeSwitcher
from nextjs-themes
:
// app/layout.jsx
import { ThemeSwitcher } from "nextjs-themes";
export default function Layout({ children }) {
return (
<html lang="en">
<head />
<body>
<ThemeSwitcher />
{children}
</body>
</html>
);
}
+
+
Woohoo! Multiple theme modes with Server Components support!
-Next.js app supports dark mode, including System preference with prefers-color-scheme
. The theme is synced between tabs, modifying the data-theme
attribute on the html
element:
:root {
--background: white;
--foreground: black;
}
[data-theme="dark"] {
--background: black;
--foreground: white;
}
-
-Show different images based on the current theme:
-import Image from "next/image";
import { useTheme } from "nextjs-themes";
function ThemedImage() {
const { resolvedTheme } = useTheme();
const src = resolvedTheme === "light" ? "/light.png" : "/dark.png";
return <Image src={src} width={400} height={400} />;
}
export default ThemedImage;
-
-The useTheme
hook provides theme information and allows changing the theme:
import { useTheme } from "nextjs-themes";
const ThemeChanger = () => {
const { theme, setTheme } = useTheme();
return (
<div>
The current theme is: {theme}
<button onClick={() => setTheme("light")}>Light Mode</button>
<button onClick={() => setTheme("dark")}>Dark Mode</button>
</div>
);
};
-
+Next.js app supports dark mode, including System preference with prefers-color-scheme
. The theme is synced between tabs, modifying the data-theme
attribute on the html
element:
:root {
--background: white;
--foreground: black;
}
[data-theme="dark"] {
--background: black;
--foreground: white;
}
+
+
+Show different images based on the current theme:
+import Image from "next/image";
import { useTheme } from "nextjs-themes";
function ThemedImage() {
const { resolvedTheme } = useTheme();
const src = resolvedTheme === "light" ? "/light.png" : "/dark.png";
return <Image src={src} width={400} height={400} />;
}
export default ThemedImage;
+
+
+The useTheme
hook provides theme information and allows changing the theme:
import { useTheme } from "nextjs-themes";
const ThemeChanger = () => {
const { theme, setTheme } = useTheme();
return (
<div>
The current theme is: {theme}
<button onClick={() => setTheme("light")}>Light Mode</button>
<button onClick={() => setTheme("dark")}>Dark Mode</button>
</div>
);
};
+
+
The useTheme
hook returns the following object:
interface UseThemeYield {
theme: string;
darkTheme: string;
lightTheme: string;
colorSchemePref: ColorSchemeType;
systemColorScheme: ResolvedColorSchemeType;
resolvedColorScheme: ResolvedColorSchemeType;
resolvedTheme: string;
setTheme: (theme: string) => void;
setDarkTheme: (darkTheme: string) => void;
setLightTheme: (lightTheme: string) => void;
setThemeSet: (themeSet: { darkTheme: string; lightTheme: string }) => void;
setColorSchemePref: (colorSchemePref: ColorSchemeType) => void;
toggleColorScheme: (skipSystem?: boolean) => void;
setForcedTheme: (forcedTheme: string) => void;
setForcedColorScheme: (forcedColorScheme: ColorSchemeType) => void;
}
-
+interface UseThemeYield {
theme: string;
darkTheme: string;
lightTheme: string;
colorSchemePref: ColorSchemeType;
systemColorScheme: ResolvedColorSchemeType;
resolvedColorScheme: ResolvedColorSchemeType;
resolvedTheme: string;
setTheme: (theme: string) => void;
setDarkTheme: (darkTheme: string) => void;
setLightTheme: (lightTheme: string) => void;
setThemeSet: (themeSet: { darkTheme: string; lightTheme: string }) => void;
setColorSchemePref: (colorSchemePref: ColorSchemeType) => void;
toggleColorScheme: (skipSystem?: boolean) => void;
setForcedTheme: (forcedTheme: string) => void;
setForcedColorScheme: (forcedColorScheme: ColorSchemeType) => void;
}
+
+
import { ForceTheme } from "nextjs-themes";
function MyPage() {
return (
<>
<ForceTheme theme="my-theme" />
...
</>
);
}
export default MyPage;
+
+
+For the pages router, you have two options. The first option is the same as the app router, and the second option, which is compatible with next-themes
, involves adding the theme
property to your page component like this:
function MyPage() {
return <>...</>;
}
MyPage.theme = "my-theme";
export default MyPage;
+
-import { ForceTheme } from "nextjs-themes";
function MyPage() {
return (
<>
<ForceTheme theme="my-theme" />
...
</>
);
}
export default MyPage;
-
-For the pages router, you have two options. The first option is the same as the app router, and the second option, which is compatible with next-themes
, involves adding the theme
property to your page component like this:
function MyPage() {
return <>...</>;
}
MyPage.theme = "my-theme";
export default MyPage;
-
Similarly, you can force a color scheme. This will apply your defaultDark
or defaultLight
theme, which can be configured via hooks.
Next Themes works with any library. For Styled Components, createGlobalStyle
in your custom App:
// pages/_app.js
import { createGlobalStyle } from "styled-components";
import { ThemeSwitcher } from "nextjs-themes";
const GlobalStyle = createGlobalStyle`
:root {
--fg: #000;
--bg: #fff;
}
[data-theme="dark"] {
--fg: #fff;
--bg: #000;
}
`;
function MyApp({ Component, pageProps }) {
return (
<>
<GlobalStyle />
<ThemeSwitcher forcedTheme={Component.theme} />
<Component {...pageProps} />
</>
);
}
+
+
+In tailwind.config.js
, set the dark mode property to class:
// tailwind.config.js
module.exports = {
darkMode: "class",
};
+
-Next Themes works with any library. For Styled Components, createGlobalStyle
in your custom App:
// pages/_app.js
import { createGlobalStyle } from "styled-components";
import { ThemeSwitcher } from "nextjs-themes";
const GlobalStyle = createGlobalStyle`
:root {
--fg: #000;
--bg: #fff;
}
[data-theme="dark"] {
--fg: #fff;
--bg: #000;
}
`;
function MyApp({ Component, pageProps }) {
return (
<>
<GlobalStyle />
<ThemeSwitcher forcedTheme={Component.theme} />
<Component {...pageProps} />
</>
);
}
-
-In tailwind.config.js
, set the dark mode property to class:
// tailwind.config.js
module.exports = {
darkMode: "class",
};
-
⚡🎉Ready to use dark mode in Tailwind!
Caution: Your class must be
"dark"
, which is the default value used in this library. Tailwind requires the class name"dark"
for dark-theme.
Use dark-mode specific classes:
-<h1 className="text-black dark:text-white">
-
--Refer to the migration guide.
++ +<h1 className="text-black dark:text-white"> +
Migration
+-Refer to the migration guide.
Docs
-🤩 Don't forget to star this repo!
Want a hands-on course for getting started with Turborepo? Check out React and Next.js with TypeScript
-FAQ
Do I need to use CSS variables with this library?
+Docs
+🤩 Don't forget to star this repo!
Want a hands-on course for getting started with Turborepo? Check out React and Next.js with TypeScript
+FAQ
Do I need to use CSS variables with this library?
No. You can hard code values for every class:
-+.my-class {
color: #555;
}
[data-theme="dark"] .my-class {
color: white;
} -+.my-class {
color: #555;
}
[data-theme="dark"] .my-class {
color: white;
} +Why is
resolvedTheme
andresolvedColorScheme
necessary?To reflect the System theme preference and forced theme/colorScheme pages in your UI. For instance, buttons or dropdowns indicating the current colorScheme should say "system" when the System colorScheme preference is active.
-
resolvedTheme
is useful for modifying behavior or styles at runtime:+const { resolvedTheme, resolvedColorScheme } = useTheme();
const background = getBackground(resolvedTheme);
<div style={{ color: resolvedColorScheme === 'dark' ? white : black, background }}> -+const { resolvedTheme, resolvedColorScheme } = useTheme();
const background = getBackground(resolvedTheme);
<div style={{ color: resolvedColorScheme === 'dark' ? white : black, background }}> +Without
-resolvedTheme
, you would only know the theme is "system", not what it resolved to.License
This library is licensed under the MPL-2.0 open-source license.
+License
This library is licensed under the MPL-2.0 open-source license.
Please consider enrolling in our courses or sponsoring our work.
- -with 💖 by Mayank Kumar Chaudhari
with 💖 by Mayank Kumar Chaudhari
Optional
sizeDiameter of the color switch
-Optional
skipSkip system colorScheme while toggling
-Optional
targetOptional
forcedOptional
forcedOptional
nonceThe nonce value for your Content Security Policy.
-''
-
-Optional
stylesprovide styles object imported from CSS/SCSS modules, if you are using CSS/SCSS modules.
-Optional
targetOptional
themeOptional
forcedOptional
forcedOptional
nonceThe nonce value for your Content Security Policy.
+Optional
stylesprovide styles object imported from CSS/SCSS modules, if you are using CSS/SCSS modules.
+Optional
targetOptional
themeactions
-Optional
skipSystem: booleanactions
+Optional
childrenOptional
forcedOptional
stylesprovide styles object imported from CSS/SCSS modules, if you are using CSS/SCSS modules.
-Optional
tag'div'
-
-Optional
targetid of target element to apply classes to. This is useful when you want to apply theme only to specific container.
-Optional
childrenOptional
forcedOptional
stylesprovide styles object imported from CSS/SCSS modules, if you are using CSS/SCSS modules.
+Optional
tagOptional
targetid of target element to apply classes to. This is useful when you want to apply theme only to specific container.
+Optional
childrenOptional
forcedOptional
stylesprovide styles object imported from CSS/SCSS modules, if you are using CSS/SCSS modules.
-Optional
tag'html'
-
-Optional
targetid of target element to apply classes to. This is useful when you want to apply theme only to specific container.
-Optional
childrenOptional
forcedOptional
stylesprovide styles object imported from CSS/SCSS modules, if you are using CSS/SCSS modules.
+Optional
tagOptional
targetid of target element to apply classes to. This is useful when you want to apply theme only to specific container.
+colorSchemePref
-darkTheme
-lightTheme
-systemColorScheme
-theme
-with 💖 by Mayank Kumar Chaudhari
diff --git a/docs/modules/client_color_switch_color_switch.html b/docs/modules/client_color_switch_color_switch.html index 0458487b..fd05663f 100644 --- a/docs/modules/client_color_switch_color_switch.html +++ b/docs/modules/client_color_switch_color_switch.html @@ -1,3 +1,3 @@ -Optional
forcedThemeProp: stringOptional
forcedColorSchemeProp: ColorSchemeTypeOptional
forcedOptional
forcedas no longer needed.
-Types to be exposed to users
-Types to be exposed to users
+Const
Const
Const
shared constants -- keep in separate files for better tree-shaking and dependency injection
-Const
shared constants -- keep in separate files for better tree-shaking and dependency injection
+Const
Const
Const
Const
Const
Const
Color switch button to quickly set user preference
-