API
The component, hook, values and types @k8ordo/color-scheme exports. There is one entry point, @k8ordo/color-scheme.
On this page
ColorSchemeProvider
Import from @k8ordo/color-scheme
The provider that goes in the root layout, inside <body>, around the whole page. It renders the inline script that puts dark on <html> before the first paint, and keeps the class in step afterwards.
ColorSchemeProvider(props: ColorSchemeProviderProps): ReactNodeParameters
defaultPreferenceColorSchemePreference = 'system'- What a visitor who chose nothing gets.
'system'follows the OS setting;'light'or'dark'holds every visitor there until they choose. Defaults to'system'. noncestring | undefined- The nonce to put on the inline script; under
@k8ordo/server, passnonce(). Leave it out when the script is allowed by hash. childrenReactNode- The whole page. The script is rendered ahead of it.
Caveats
- It is exported from a
'use client'module, so a root layout that is a Server Component renders it as it is. - Give
<html>suppressHydrationWarning. Theclassthe script adds is not in the server’s HTML, and React reports it as a mismatch in development. - Put exactly one in the application. Each provider renders its own script and writes the class, so two of them can disagree.
- A server can read neither the stored choice nor the OS setting, so it renders the default; with a
'system'default, that is'light'.
layout.tsxexport default function RootLayout({
children,
}: {
children: ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ColorSchemeProvider>{children}</ColorSchemeProvider>
</body>
</html>
);
}useColorScheme
Import from @k8ordo/color-scheme
Returns the scheme on screen, the visitor’s choice, and a function that changes it. Use it in Client Components.
useColorScheme(): UseColorSchemeReturns
UseColorScheme — An object holding scheme, preference and setPreference.
Fields
scheme'light' | 'dark'- What is on screen: from the visitor’s choice, the provider’s default, or the OS setting.
preference'light' | 'dark' | 'system'- What the visitor chose;
'system'while nothing is chosen. setPreference(preference: ColorSchemePreference) => void- Stores a choice.
'system'clears the stored one, so the provider’s default applies again.
Caveats
- Called outside the provider, it throws an error starting with
useColorScheme needs <ColorSchemeProvider> above it. It never quietly falls back to the default. - It only reads what the provider decided, and never touches the class on
<html>or localStorage. - In the server render and the hydration render it returns the server’s guess. The stored choice and the OS setting are read from the render right after.
scheme-toggle.tsxconst { scheme, setPreference } = useColorScheme();
<button
onClick={() => {
setPreference(scheme === 'dark' ? 'light' : 'dark');
}}
type="button"
>
Toggle
</button>See “Build a switch” for a toggle and a three-way choice.
colorSchemeState
Import from @k8ordo/color-scheme
Where the preference lives: the @k8ordo/state defineLocalState definition itself. It is stored in localStorage under k8ordo-state:color-scheme, as a row with one field, preference.
const colorSchemeState = defineLocalState(
'color-scheme',
z.object({ preference: z.optional(z.enum(['light', 'dark'])) }),
);Fields
kind'local'- The kind of place the state lives in:
'local', for localStorage. key'color-scheme'- Its name within
@k8ordo/state. There is one store per name. schemaZodMiniObject- The schema the definition was given.
storageKey'k8ordo-state:color-scheme'- The localStorage key:
k8ordo-state:followed bykey. inlineRead() => string- Returns a JavaScript expression for an inline script. It evaluates to the stored row’s object, or to
nullwhen the row cannot be read.
Caveats
useAppState(colorSchemeState)reads the storedpreferencewithout the hook. It returns'light' | 'dark' | undefined, not the resolvedscheme.- Change the choice through
setPreference. AlocalStorage.setItemin the same tab goes unnoticed by the provider until a reload. - Do not define another
defineLocalState('color-scheme', …)in the application. Stores are shared by name, so the two definitions would fight over one row.
stored-preference.tsxconst [{ preference }] = useAppState(colorSchemeState);
// 'light' | 'dark' | undefinedcolorSchemeScriptHash
Import from @k8ordo/color-scheme
Resolves to the SHA-256 of the provider’s inline script as a CSP source, 'sha256-…', for a policy that allows the script without a nonce.
colorSchemeScriptHash(
defaultPreference?: ColorSchemePreference,
): Promise<string>Parameters
defaultPreferenceColorSchemePreference = 'system'- The
defaultPreferencethe provider is given. The default is written into the script, so the hash depends on it.
Returns
Promise<string> — A CSP source such as 'sha256-…', quotes included.
Caveats
- Call it wherever the policy is written, every time, rather than copying its value: the script is the installed version’s, and an update may change it.
- Where every response can carry a nonce, as under
@k8ordo/server, the hash is not needed.
vite.config.tsframework({
csp: {
'script-src': ["'self'", await colorSchemeScriptHash()],
},
});ColorScheme
Import from @k8ordo/color-scheme
A scheme that can be on screen; the type of scheme.
type ColorScheme = 'light' | 'dark';ColorSchemePreference
Import from @k8ordo/color-scheme
A scheme, or 'system'. As a visitor’s choice, 'system' is nothing chosen, so the provider’s default applies; as that default, it follows the OS setting.
type ColorSchemePreference = ColorScheme | 'system';ColorSchemeProviderProps
Import from @k8ordo/color-scheme
The props of ColorSchemeProvider, for a component of your own that wraps it.
type ColorSchemeProviderProps = {
readonly defaultPreference?: ColorSchemePreference;
readonly nonce?: string;
readonly children: ReactNode;
};UseColorScheme
Import from @k8ordo/color-scheme
What useColorScheme() returns, for a component that receives it as props.
type UseColorScheme = {
readonly scheme: ColorScheme;
readonly preference: ColorSchemePreference;
readonly setPreference: (preference: ColorSchemePreference) => void;
};