Skip to content
DDelTech MUNDocs

Theming

next-themes, the theme cookie, per-area toggles and portal theming.

next-themes, with a cookie so server-rendered shells get the right first paint.

The provider

src/components/providers.tsx:

<ThemeProvider attribute="class" defaultTheme="system" enableSystem
  storageKey={THEME_STORAGE_KEY} disableTransitionOnChange>

<html> carries suppressHydrationWarning, which is required: the theme class is written before React hydrates.

src/lib/theme.ts:

ConstantValue
THEME_COOKIEtheme-preference
THEME_STORAGE_KEYtheme-site
DEFAULT_THEMEsystem
THEME_COOKIE_MAX_AGEone year

Both exist for different jobs. localStorage applies the choice before paint on public pages. The cookie is what a server component reads, so the admin shell renders with the right class in its first response rather than flashing.

Legacy per-area cookies (theme-admin, theme-recruitment) are read as a migration path. New choices always write the single global cookie.

Across subdomains

www.deltechmun.in, docs.deltechmun.in and test.deltechmun.in are different origins, so each has its own localStorage. On its own, next-themes would keep a separate choice per host.

Three pieces make one choice follow the person:

PieceWhereDoes
Shared cookiepersistThemePreference()Writes theme-preference with Domain=deltechmun.in, after clearing any old host-only copy.
MigrationThemePreferenceSyncOn each load, re-issues an existing cookie on the shared domain, so choices saved before this existed carry over.
Boot scriptTHEME_BOOT_SCRIPT in <head>Copies the cookie into localStorage before next-themes reads it, so the other subdomain paints in the right theme with no flash.

Staging shares the domain too, so a theme chosen on test.deltechmun.in also applies on production. It is a display preference, so that is harmless.

Locally the cookie has no Domain, since localhost is not under deltechmun.in.

themeClass

themeClass(theme) // "dark" | "theme-light" | ""

theme-light is not the same as no class. It has to actively override an inherited .dark. See the custom variant in the design system.

Two toggles

AreaThemeToggle (src/components/theme/area-theme-toggle.tsx) is for authenticated shells. It sets optimistic state, persists the preference, toggles the class directly on .${area}-shell, then calls router.refresh() so the server-rendered class agrees with the cookie.

ThemeToggle (src/app/(marketing)/_components/theme-toggle.tsx) is the simpler public one. This documentation site uses it, because areaForPath puts /docs in the site area.

Adding a themed area

areaForPath() maps a path to an area. If you add one, add its shell class in globals.css and give it a matching -shell suffix, because AreaThemeToggle targets .${area}-shell by name.

The convention check

scripts/check-theme-preference.ts enforces the pattern. Run npm run check after touching anything here.