diff --git a/docs/src/content/utilities/clear-session-mode.md b/docs/src/content/utilities/clear-session-mode.md
new file mode 100644
index 0000000..9e73e0b
--- /dev/null
+++ b/docs/src/content/utilities/clear-session-mode.md
@@ -0,0 +1,24 @@
+---
+title: clearTemporaryMode
+description: Clears the temporary override and reverts to the persisted or system mode.
+section: Utilities
+---
+
+`clearTemporaryMode` removes any temporary mode set via `setMode(..., { temporaryOnly: true })`.
+
+After calling this, the active mode falls back to the user's persisted preference (from `localStorage`) or to the system preference if none is set.
+
+## Usage
+
+```svelte
+
+
+
+```
+
+## See also
+
+- [setMode](/docs/utilities/set-mode)
+- [resetMode](/docs/utilities/reset-mode)
diff --git a/docs/src/content/utilities/set-mode.md b/docs/src/content/utilities/set-mode.md
index c044270..864b1e8 100644
--- a/docs/src/content/utilities/set-mode.md
+++ b/docs/src/content/utilities/set-mode.md
@@ -4,11 +4,15 @@ description: Sets the current mode to "light", "dark", or "system".
section: Utilities
---
+
+
`setMode` is a function that updates the user's preferred mode.
It accepts one of three string values: `"light"`, `"dark"`, or `"system"`.
-This updates both the visual mode and the persisted preference in `localStorage`.
+This updates both the visual mode and the persisted preference in `localStorage`. You can also pass it options to customize the behavior, such as applying it temporarily without persisting it to `localStorage`.
## Usage
@@ -20,3 +24,39 @@ This updates both the visual mode and the persisted preference in `localStorage`
```
+
+You can also apply the mode temporarily (without persisting to `localStorage`) by passing the `temporaryOnly` option as `true`. This can be useful if you need to set the mode to a specific value for a specific part of your website. (ex: marketing part vs docs part, where in marketing part you want to set the mode to `light` and in docs part you want the user to choose the mode).
+
+`(marketing) group - layout.svelte`
+
+```svelte
+
+```
+
+`(docs) group - layout.svelte`
+
+```svelte
+
+```
+
+Then you get the marketing with light mode as you like, and the docs part will revert to the user's local storage preference!
+
+## Options
+
+The `setMode` utility accepts the following options:
+
+
+ Whether to apply the mode only temporarily (do not persist to `localStorage`).
+
diff --git a/packages/mode-watcher/src/lib/index.ts b/packages/mode-watcher/src/lib/index.ts
index d226336..85cb2ec 100644
--- a/packages/mode-watcher/src/lib/index.ts
+++ b/packages/mode-watcher/src/lib/index.ts
@@ -2,6 +2,7 @@ import {
generateSetInitialModeExpression,
createInitialModeExpression,
resetMode,
+ clearTemporaryMode,
setMode,
setTheme,
toggleMode,
@@ -16,6 +17,7 @@ export {
setMode,
toggleMode,
resetMode,
+ clearTemporaryMode,
modeStorageKey,
userPrefersMode,
systemPrefersMode,
@@ -24,5 +26,9 @@ export {
setTheme,
themeStorageKey,
};
-export type { SystemModeValue, UserPrefersMode, SystemPrefersMode } from "./mode-states.svelte.js";
+export type {
+ SystemModeValue,
+ UserPrefersMode,
+ SystemPrefersMode,
+} from "./mode-states.svelte.js";
export { default as ModeWatcher } from "./components/mode-watcher.svelte";
diff --git a/packages/mode-watcher/src/lib/mode.ts b/packages/mode-watcher/src/lib/mode.ts
index 818b43f..ab7838e 100644
--- a/packages/mode-watcher/src/lib/mode.ts
+++ b/packages/mode-watcher/src/lib/mode.ts
@@ -1,23 +1,40 @@
import { userPrefersMode } from "./mode-states.svelte.js";
import { customTheme } from "./theme-state.svelte.js";
-import { derivedMode } from "./states.svelte.js";
+import { derivedMode, temporaryUserPrefersMode } from "./states.svelte.js";
import type { Mode, ThemeColors } from "./types.js";
/** Toggle between light and dark mode */
export function toggleMode(): void {
userPrefersMode.current = derivedMode.current === "dark" ? "light" : "dark";
+ temporaryUserPrefersMode.current = undefined;
}
+export type SetModeOptions = {
+ /** Apply only temporarily; do not persist to localStorage. */
+ temporaryOnly?: boolean;
+};
+
/** Set the mode to light or dark */
-export function setMode(mode: Mode): void {
- userPrefersMode.current = mode;
+export function setMode(mode: Mode, options?: SetModeOptions): void {
+ if (options?.temporaryOnly) {
+ temporaryUserPrefersMode.current = mode;
+ } else {
+ temporaryUserPrefersMode.current = undefined;
+ userPrefersMode.current = mode;
+ }
}
/** Reset the mode to operating system preference */
export function resetMode(): void {
+ temporaryUserPrefersMode.current = undefined;
userPrefersMode.current = "system";
}
+/** Reset the current session-only override, falling back to persisted/user/system */
+export function clearTemporaryMode(): void {
+ temporaryUserPrefersMode.current = undefined;
+}
+
/** Set the theme to a custom value */
export function setTheme(newTheme: string): void {
customTheme.current = newTheme;
@@ -52,13 +69,18 @@ export function setInitialMode({
const theme = localStorage.getItem(themeStorageKey) ?? defaultTheme;
const light =
mode === "light" ||
- (mode === "system" && window.matchMedia("(prefers-color-scheme: light)").matches);
+ (mode === "system" &&
+ window.matchMedia("(prefers-color-scheme: light)").matches);
if (light) {
- if (darkClassNames.length) rootEl.classList.remove(...darkClassNames.filter(Boolean));
- if (lightClassNames.length) rootEl.classList.add(...lightClassNames.filter(Boolean));
+ if (darkClassNames.length)
+ rootEl.classList.remove(...darkClassNames.filter(Boolean));
+ if (lightClassNames.length)
+ rootEl.classList.add(...lightClassNames.filter(Boolean));
} else {
- if (lightClassNames.length) rootEl.classList.remove(...lightClassNames.filter(Boolean));
- if (darkClassNames.length) rootEl.classList.add(...darkClassNames.filter(Boolean));
+ if (lightClassNames.length)
+ rootEl.classList.remove(...lightClassNames.filter(Boolean));
+ if (darkClassNames.length)
+ rootEl.classList.add(...darkClassNames.filter(Boolean));
}
rootEl.style.colorScheme = light ? "light" : "dark";
@@ -67,7 +89,7 @@ export function setInitialMode({
if (themeMetaEl) {
themeMetaEl.setAttribute(
"content",
- mode === "light" ? themeColors.light : themeColors.dark
+ mode === "light" ? themeColors.light : themeColors.dark,
);
}
}
@@ -83,7 +105,9 @@ export function setInitialMode({
/**
* A type-safe way to generate the source expression used to set the initial mode and avoid FOUC.
*/
-export function createInitialModeExpression(config: SetInitialModeArgs = {}): string {
+export function createInitialModeExpression(
+ config: SetInitialModeArgs = {},
+): string {
return `(${setInitialMode.toString()})(${JSON.stringify(config)});`;
}
diff --git a/packages/mode-watcher/src/lib/states.svelte.ts b/packages/mode-watcher/src/lib/states.svelte.ts
index 5660c4c..54ffeac 100644
--- a/packages/mode-watcher/src/lib/states.svelte.ts
+++ b/packages/mode-watcher/src/lib/states.svelte.ts
@@ -1,6 +1,6 @@
import { box } from "svelte-toolbelt";
import { isBrowser, sanitizeClassNames } from "./utils.js";
-import type { ThemeColors } from "./types.js";
+import type { Mode, ThemeColors } from "./types.js";
import { withoutTransition } from "./without-transition.js";
import { systemPrefersMode, userPrefersMode } from "./mode-states.svelte.js";
import { customTheme } from "./theme-state.svelte.js";
@@ -33,13 +33,21 @@ export const darkClassNames = box([]);
*/
export const lightClassNames = box([]);
+/**
+ * A non-persistent override for the user's preferred mode.
+ * When set, this value takes precedence over the persisted `userPrefersMode`.
+ */
+export const temporaryUserPrefersMode = box(undefined);
+
function createDerivedMode() {
const current = $derived.by(() => {
if (!isBrowser) return undefined;
- const derivedMode =
- userPrefersMode.current === "system"
- ? systemPrefersMode.current
+ const preferredMode =
+ temporaryUserPrefersMode.current !== undefined
+ ? temporaryUserPrefersMode.current
: userPrefersMode.current;
+ const derivedMode =
+ preferredMode === "system" ? systemPrefersMode.current : preferredMode;
const sanitizedDarkClassNames = sanitizeClassNames(darkClassNames.current);
const sanitizedLightClassNames = sanitizeClassNames(lightClassNames.current);
diff --git a/packages/mode-watcher/src/routes/+page.svelte b/packages/mode-watcher/src/routes/+page.svelte
index 593722c..6f378c9 100644
--- a/packages/mode-watcher/src/routes/+page.svelte
+++ b/packages/mode-watcher/src/routes/+page.svelte
@@ -2,6 +2,7 @@
import {
mode,
resetMode,
+ clearTemporaryMode,
setMode,
systemPrefersMode,
theme,
@@ -73,4 +74,16 @@
>
Reset
+
+
diff --git a/packages/mode-watcher/src/tests/mode.spec.ts b/packages/mode-watcher/src/tests/mode.spec.ts
index 2fbdc0c..8ab1f41 100644
--- a/packages/mode-watcher/src/tests/mode.spec.ts
+++ b/packages/mode-watcher/src/tests/mode.spec.ts
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it } from "vitest";
import { userEvent } from "@testing-library/user-event";
import { tick } from "svelte";
import { mediaQueryState } from "../../scripts/setupTest.js";
+import { setMode, clearTemporaryMode, modeStorageKey } from "$lib/index.js";
import Mode from "./Mode.svelte";
import StealthMode from "./StealthMode.svelte";
import type { ModeWatcherProps } from "$lib/types.js";
@@ -353,6 +354,36 @@ describe("mode-watcher", () => {
expect(classes2).toContain("custom-l-class");
});
+ it("applies mode temporarily without persisting to localStorage", async () => {
+ const { rootEl } = setup();
+ expect(getClasses(rootEl)).toContain("dark");
+ expect(localStorage.getItem(modeStorageKey.current)).toBe("system");
+
+ setMode("light", { temporaryOnly: true });
+ await tick();
+
+ expect(getClasses(rootEl)).not.toContain("dark");
+ expect(getColorScheme(rootEl)).toBe("light");
+ // localStorage unchanged
+ expect(localStorage.getItem(modeStorageKey.current)).toBe("system");
+ });
+
+ it("clears temporary override and reverts to persisted value", async () => {
+ const { rootEl } = setup();
+ // start with temporary-only light
+ setMode("light", { temporaryOnly: true });
+ await tick();
+ expect(getColorScheme(rootEl)).toBe("light");
+ // clear override
+ clearTemporaryMode();
+ await tick();
+ // should revert to system -> dark
+ expect(getClasses(rootEl)).toContain("dark");
+ expect(getColorScheme(rootEl)).toBe("dark");
+ // localStorage still system
+ expect(localStorage.getItem(modeStorageKey.current)).toBe("system");
+ });
+
it("allows the user to set a custom theme via the `defaultTheme` prop", async () => {
const { theme, rootEl } = setup({
defaultTheme: "dracula",