On this page

Theming

Applying a theme

Load a theme, set its identity class, and choose colour modes.

A themed document needs the shared Luke UI stylesheet, one theme stylesheet, and that theme's identity class on <html>. Theming is static CSS, so it needs no provider. Read Installation for the imports, and for where to set the classes in a single-page app or a framework that renders the document.

The identity class selects the theme's tokens. The shared stylesheet paints <body> with the theme's base surface, text colour, and body font for every theme.

Use one identity class

Set exactly one identity class, on <html>. An identity class on any other element does nothing. Two identity classes on <html> are not supported: the themes' rules compete, and stylesheet order decides which values win. To switch themes at runtime, replace the class rather than adding a second one.

Choose a colour mode

Without a colour mode, the document follows the system preference, and changes with it live. Set data-color-mode="light" or data-color-mode="dark" on <html> to fix the mode for the whole document, as in <html class="luke-ui-theme-tactile" data-color-mode="dark">.

The theme sets the native color-scheme property for each mode. Browser controls and scrollbars then match the Luke UI content.

System mode exists only at the document level, and it means no attribute. When a preference control offers "System", remove data-color-mode from <html>. Do not resolve the preference to light or dark yourself, because the attribute would then stop following a later system change. Only lowercase light and dark are valid values.

Set the mode before first paint

Put the identity class, and any mode you know in advance, in the initial HTML. When the mode comes from a preference stored in the browser, set it with an inline script in <head>, before the page paints. The script sets data-color-mode only for an explicit choice.

import { themeClassName } from '@luke-ui/theme-tactile';
import type { PropsWithChildren } from 'react';

// Runs before first paint. It sets an explicit mode only. Without one, the theme follows the
// system preference, so `system` needs no attribute.
const colorModeScript = `
try {
	const mode = localStorage.getItem('color-mode');
	if (mode === 'light' || mode === 'dark') document.documentElement.dataset.colorMode = mode;
} catch {}
`;

export function RootLayout({ children }: PropsWithChildren) {
	return (
		// The script changes `<html>` before React hydrates it.
		<html className={themeClassName} lang="en" suppressHydrationWarning>
			<head>
				<script dangerouslySetInnerHTML={{ __html: colorModeScript }} />
			</head>
			<body>{children}</body>
		</html>
	);
}

In a single-page app, put the same script in a <script> element in the <head> of index.html. In a framework that renders <html>, set suppressHydrationWarning on it, because the script changes <html> before React hydrates it.

Continue learning