# Applying a theme (/docs/applying-a-theme)



A themed subtree needs the shared Luke UI stylesheet and one theme stylesheet. Both are static CSS.
Luke UI does not inject styles at runtime.

## Import the stylesheets [#import-the-stylesheets]

Import `@luke-ui/react/stylesheet.css` and one theme's stylesheet, for example
`@luke-ui/react/themes/tactile/stylesheet.css`. A theme stylesheet themes the whole document from
`:root`, with no class and no JavaScript. Import one bundled theme, and Luke UI does not load the
other.

## Apply the root class [#apply-the-root-class]

Apply `rootClassName` to an element you own, such as the application shell. It supplies the reset
and base typography. It carries no theme identity, so it works the same way for a bundled theme and
a custom theme.

apps/docs/src/samples/theming/apply-theme.tsx

```tsx
import '@luke-ui/react/stylesheet.css';
import '@luke-ui/react/themes/tactile/stylesheet.css';
import { rootClassName } from '@luke-ui/react/theme';
import type { PropsWithChildren } from 'react';

export function App({ children }: PropsWithChildren) {
	return (
		<div className={rootClassName} data-color-mode="dark">
			{children}
		</div>
	);
}
```

## Choose a colour mode [#choose-a-colour-mode]

Leave `data-color-mode` unset to follow `prefers-color-scheme`. Set `data-color-mode="light"` or
`data-color-mode="dark"` when a root or nested scope needs a fixed mode.

An explicit mode also sets the native `color-scheme` property. Browser controls and scrollbars then
match the Luke UI content.

## Nest colour-mode scopes [#nest-colour-mode-scopes]

Colour modes nest freely. Use a nested mode when one part of the interface must stay light or dark
independently of its parent.

apps/docs/src/examples/theming/color-mode-override.tsx

```tsx
import { Box } from '@luke-ui/react/box';
import { Button } from '@luke-ui/react/button';
import { Text } from '@luke-ui/react/text';
import { vars } from '@luke-ui/react/theme';
import { useState } from 'react';

export default () => {
	const [parentMode, setParentMode] = useState<'light' | 'dark'>('light');

	return (
		<Box
			data-color-mode={parentMode}
			display="grid"
			gap="sp16"
			padding="sp24"
			style={{
				backgroundColor: vars.color.surface.canvas,
				color: vars.color.text.primary,
			}}
		>
			<Box display="grid" gap="sp8">
				<Text elementType="strong" fontWeight="emphasis">
					Parent colour mode
				</Text>
				<Box aria-label="Parent colour mode" display="flex" gap="sp8" role="group">
					{(['light', 'dark'] as const).map((option) => (
						<Button
							appearance={parentMode === option ? 'solid' : 'subtle'}
							aria-pressed={parentMode === option}
							key={option}
							onPress={() => setParentMode(option)}
							tone="accent"
						>
							{option === 'light' ? 'Light' : 'Dark'}
						</Button>
					))}
				</Box>
			</Box>
			<Box
				padding="sp16"
				style={{
					backgroundColor: vars.color.surface.floating,
					border: `1px solid ${vars.color.border.decorative}`,
					borderRadius: vars.radius.surface,
					color: vars.color.text.primary,
				}}
			>
				<Text>This panel follows the parent mode.</Text>
			</Box>
			<Box
				data-color-mode="dark"
				padding="sp16"
				style={{
					backgroundColor: vars.color.surface.floating,
					border: `1px solid ${vars.color.border.decorative}`,
					borderRadius: vars.radius.surface,
					color: vars.color.text.primary,
				}}
			>
				<Text>This panel is fixed to dark mode.</Text>
			</Box>
		</Box>
	);
};
```

apps/docs/src/samples/theming/nested-color-mode.tsx

```tsx
import type { PropsWithChildren } from 'react';

export function DarkPageWithLightPreview({ children }: PropsWithChildren) {
	return (
		<div data-color-mode="dark">
			Dark application
			<section data-color-mode="light">{children}</section>
		</div>
	);
}
```

## Avoid a colour-mode flash [#avoid-a-colour-mode-flash]

Render `rootClassName` and any known explicit mode in the initial HTML. When the mode comes from a
client-side preference, set `data-color-mode` before the themed UI paints.

Use the same value for the server HTML and the first client render. This avoids a hydration warning.

## Portals need nothing extra [#portals-need-nothing-extra]

A theme stylesheet themes the whole document from `:root`, so a body-level portal inherits it with
no class applied.

A colour mode does not follow a portal out of its branch. A portal rendered into `document.body`
takes the document's mode, not a mode set on a nested scope near its trigger. Set `data-color-mode`
on the portal root yourself when it must match a nested scope.

## Add an identity class for more than one theme [#add-an-identity-class-for-more-than-one-theme]

Loading one theme needs no identity class. Loading more than one theme in the same document does, so
an explicit class can win over another theme's `:root` fallback.

Each bundled theme exports its identity class as `themeClassName` from its own entrypoint, for
example `@luke-ui/react/themes/tactile`. Apply it to `<html>`, or to the root of the subtree that
needs it.

An authored theme gets its class the same way, from `getThemeClassName` in `@luke-ui/react/theme`.
Pass the `name` your `ThemeInput` declares. It returns the same class the generated stylesheet
selects on, and throws when the name is not kebab-case.

apps/docs/src/samples/theming/multi-theme-app.tsx

```tsx
import '@luke-ui/react/stylesheet.css';
import '@luke-ui/react/themes/tactile/stylesheet.css';
import { getThemeClassName, rootClassName } from '@luke-ui/react/theme';
import { cx } from '@luke-ui/react/utils';
import type { PropsWithChildren } from 'react';

// The same kebab-case `name` the product theme's `ThemeInput` declares.
const productThemeClassName = getThemeClassName('product');

type AppProps = PropsWithChildren<{ productStylesheetHref: string }>;

export function App({ children, productStylesheetHref }: AppProps) {
	return (
		<>
			<link href={productStylesheetHref} rel="stylesheet" />
			<div className={cx(rootClassName, productThemeClassName)}>{children}</div>
		</>
	);
}
```

Theme identities do not nest. Never place one identity class inside another identity's subtree. A
nested identity resolves its own identity-owned values correctly, and its colour, depth, and
action-control-finish values also resolve correctly under the system-controlled mode. But an
explicit `data-color-mode` scope on or inside the nested identity makes those mode-dependent values
compete with the ancestor identity at equal precedence, so stylesheet order decides the winner.

## Continue learning [#continue-learning]

<Cards>
  <Card href="/docs/authoring-a-theme" title="Authoring a theme">
    Create a product-owned stylesheet.
  </Card>

  <Card href="/docs/token-reference" title="Token reference">
    Browse public semantic values for custom elements.
  </Card>

  <Card href="/docs/theming" title="Theming">
    See how identity, colour mode, and tokens fit together.
  </Card>
</Cards>
