Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/runed/src/lib/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from "./use-event-listener/index.js";
export * from "./use-geolocation/index.js";
export * from "./use-intersection-observer/index.js";
export * from "./use-mutation-observer/index.js";
export * from "./use-preferred-color-scheme/index.js";
export * from "./use-resize-observer/index.js";
export * from "./use-interval/index.js";
export * from "./use-throttle/index.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./use-preferred-color-scheme.svelte.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MediaQuery } from "svelte/reactivity";

export type ColorSchemeType = "dark" | "light" | "no-preference";

export type UsePreferredColorSchemeOptions = {
/**
* Fallback value for server-side rendering
* @defaultValue "no-preference"
*/
fallback?: ColorSchemeType;
};

/**
* Reactive prefers-color-scheme media query.
*
* @see https://runed.dev/docs/utilities/use-preferred-color-scheme
*/
export function usePreferredColorScheme(options?: UsePreferredColorSchemeOptions) {
const { fallback = "no-preference" } = options ?? {};

// Map ColorSchemeType fallback to boolean values for MediaQuery
const isLightFallback = fallback === "light";
const isDarkFallback = fallback === "dark";

const isLight = new MediaQuery("(prefers-color-scheme: light)", isLightFallback);
const isDark = new MediaQuery("(prefers-color-scheme: dark)", isDarkFallback);
const current = $derived.by<ColorSchemeType>(() => {
if (isDark.current) return "dark";
if (isLight.current) return "light";
return "no-preference";
});
return {
get current() {
return current;
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect } from "vitest";
import { flushSync } from "svelte";
import { usePreferredColorScheme } from "./use-preferred-color-scheme.svelte.js";
import { testWithEffect } from "$lib/test/util.svelte.js";

describe("usePreferredColorScheme", () => {
testWithEffect("initializes with dark fallback", () => {
const util = usePreferredColorScheme({ fallback: "dark" });
flushSync();
expect(typeof util.current).toBe("string");
expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true);
});

testWithEffect("initializes with light fallback", () => {
const util = usePreferredColorScheme({ fallback: "light" });
flushSync();
expect(typeof util.current).toBe("string");
expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true);
});

testWithEffect("initializes with no-preference fallback (default)", () => {
const util = usePreferredColorScheme();
flushSync();
expect(typeof util.current).toBe("string");
expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true);
});

testWithEffect("returns object with current getter", () => {
const util = usePreferredColorScheme({ fallback: "no-preference" });
flushSync();
expect(typeof util).toBe("object");
expect(typeof util.current).toBe("string");
expect(["dark", "light", "no-preference"].includes(util.current)).toBe(true);
});
});
73 changes: 73 additions & 0 deletions sites/docs/src/content/utilities/use-preferred-color-scheme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: usePreferredColorScheme
description: Detect color scheme preference using the browser's prefers-color-scheme media query.
category: Sensors
---

<script>
import Demo from '$lib/components/demos/use-preferred-color-scheme.svelte';
</script>

`usePreferredColorScheme` provides a reactive string that reflects the user's color scheme
preference based on their browser or OS settings. It uses the
[prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
media query and updates automatically when the preference changes.

## Demo

<Demo />

## Usage

```svelte
<script lang="ts">
import { usePreferredColorScheme } from "runed";

const colorScheme = usePreferredColorScheme();
const colorSchemeWithDarkFallback = usePreferredColorScheme({ fallback: "dark" });
const colorSchemeWithLightFallback = usePreferredColorScheme({ fallback: "light" });
</script>

<div class:dark={colorScheme.current === "dark"} class:light={colorScheme.current === "light"}>
{colorScheme.current === "dark"
? "🌙 Dark mode"
: colorScheme.current === "light"
? "☀️ Light mode"
: "🎨 No preference"}
</div>

<!-- During SSR, these will show the specified fallback values -->
<div class:dark={colorSchemeWithDarkFallback.current === "dark"}>
{colorSchemeWithDarkFallback.current === "dark" ? "🌙 Dark mode (fallback)" : "☀️ Light mode"}
</div>

<div class:light={colorSchemeWithLightFallback.current === "light"}>
{colorSchemeWithLightFallback.current === "light" ? "☀️ Light mode (fallback)" : "🌙 Dark mode"}
</div>
```

## Type Definition

```ts
type ColorSchemeType = "dark" | "light" | "no-preference";

type UsePreferredColorSchemeOptions = {
/**
* Fallback value for server-side rendering
* @defaultValue "no-preference"
*/
fallback?: ColorSchemeType;
};

function usePreferredColorScheme(options?: UsePreferredColorSchemeOptions): {
readonly current: ColorSchemeType;
};
```

## Notes

- Uses the `prefers-color-scheme: dark` and `prefers-color-scheme: light` media queries.
- Returns "dark", "light", or "no-preference" based on user's system preference.
- During server-side rendering, returns the specified fallback value (defaults to "no-preference").
- Automatically updates when user changes their system color scheme preference.
- Works with both light and dark theme preferences and detects when no preference is set.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script lang="ts">
import { usePreferredColorScheme } from "runed";
import { DemoContainer } from "@svecodocs/kit";

const colorScheme = usePreferredColorScheme();
</script>

<DemoContainer>
<note class="mb-2"> Preferred Color Scheme: </note>
<code>{{ colorScheme: colorScheme.current }}</code>
</DemoContainer>