A port of next-themes
for SvelteKit. Many thanks to Paco Coursey for his work on the original library!
An abstraction for themes in your SvelteKit app.
$ pnpm install sveltekit-themes
Import the provider and wrap your layout.
<script lang="ts">
import { ThemeProvider } from 'sveltekit-themes';
const { children } = $props();
</script>
<ThemeProvider attribute="class">
{@render children()}
</ThemeProvider>
That's it, your SvelteKit app fully supports dark mode, including System preference with prefers-color-scheme. The theme is also immediately synced between tabs. By default, sveltekit-themes modifies the data-theme attribute on the html element, which you can easily use to style your app:
:root {
/* Your default theme */
--background: white;
--foreground: black;
}
[data-theme='dark'] {
--background: black;
--foreground: white;
}
Let's dig into the details.
All your theme configuration is passed to ThemeProvider.
storageKey = 'theme'
: Key used to store theme setting in localStoragedefaultTheme = 'system'
: Default theme name (for v0.0.12 and lower the default was light
). If enableSystem
is false, the default theme is light
forcedTheme
: Forced theme name for the current page (does not modify saved theme settings)enableSystem = true
: Whether to switch between dark
and light
based on prefers-color-scheme
enableColorScheme = true
: Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttonsdisableTransitionOnChange = false
: Optionally disable all CSS transitions when switching themes.themes = ['light', 'dark']
: List of theme namesattribute = 'data-theme'
: HTML attribute modified based on the active themeclass
and data-*
(meaning any data attribute, data-mode
, data-color
, etc.).value
: Optional mapping of theme name to attribute valueobject
where key is the theme name and value is the attribute value.nonce
: Optional nonce passed to the injected script
tag, used to allow-list the next-themes script in your CSPscriptProps
: Optional props to pass to the injected script
tag.useTheme takes no parameters, but returns all the methods and properties you need to manage themes in your components. In order to maintain Svelte's reactivity with runes, you should create an instance that is not desctructured.
<script lang="ts">
import { useTheme } from '$lib';
const theme = useTheme();
</script>
<select
onchange={(e) => theme.set(e.currentTarget.value)}
value={theme.current}
>
<option value="light"> Light Theme </option>
<option value="dark"> Dark Theme </option>
<option value="system"> System Theme </option>
</select>
current
: Active theme name.set(name)
: Function to update the theme. Pass the new theme value or use a callback to set the new theme based on the current theme.forcedTheme
: Forced page theme or falsy. If forcedTheme
is set, you should disable any theme switching UI.resolvedTheme
: If enableSystem
is true and the active theme is "system", this returns whether the system preference resolved to "dark" or "light". Otherwise, identical to theme
.systemTheme
: If enableSystem
is true, represents the System theme preference ("dark" or "light"), regardless what the active theme is.themes
: The list of themes passed to ThemeProvider
(with "system" appended, if enableSystem
is true).That's it. Once again, thank you to Paco Coursey for his work on the original library, which made this port possible. If you have any issues or suggestions, feel free to open an issue or PR.