Skip to the content

Capsid menu

app/lib/theme.ts

47 lines and 5 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of theme.ts
FunctionLinesComplexityCoveredCRAPMutation
isWritableTheme19 to 212100%OkCRAP 2No data: no mutants in this code
themeFromRequest24 to 41694%WatchCRAP 6No data: no mutants in this code
themeAttribute44 to 462100%OkCRAP 2No data: no mutants in this code
colorSchemeMeta52 to 542100%OkCRAP 2No data: no mutants in this code
serializeThemeCookie56 to 58133%OkCRAP 1.3No data: no mutants in this code

Imports

It imports no other module of this repo. Imported by app/enhance/theme.ts, app/root.tsx, app/routes/theme.ts, workers/app.ts.

Source

/**
 * A cookie, not localStorage, so the server writes the right `data-theme` into the first byte and
 * nothing flashes.
 */

const THEME_COOKIE = "theme";

const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;

// "system" is a cache-key value but not writable: no control posts it and `/theme` refuses it.
const THEMES = ["light", "dark", "system"] as const;

type Theme = (typeof THEMES)[number];

const WRITABLE_THEMES = ["light", "dark"] as const;

export type WritableTheme = (typeof WRITABLE_THEMES)[number];

export function isWritableTheme(value: unknown): value is WritableTheme {
  return typeof value === "string" && (WRITABLE_THEMES as readonly string[]).includes(value);
}

// Anything unrecognised reads as "system": a bad cookie should cost the default theme, never the page.
export function themeFromRequest(request: Request): Theme {
  const header = request.headers.get("cookie");
  if (!header) return "system";
  for (const part of header.split(";")) {
    const [name, ...rest] = part.trim().split("=");
    if (name !== THEME_COOKIE) continue;
    // `decodeURIComponent` throws, and this builds the cache key on every request: unguarded, a
    // bad cookie is a 500 on every page.
    let value;
    try {
      value = decodeURIComponent(rest.join("="));
    } catch {
      return "system";
    }
    return isWritableTheme(value) ? value : "system";
  }
  return "system";
}

// Omitted for "system", which hands the decision to prefers-color-scheme.
export function themeAttribute(theme: Theme): "light" | "dark" | undefined {
  return theme === "system" ? undefined : theme;
}

/**
 * `data-theme` means nothing to the browser until the CSS parses, so without this meta the canvas
 * painted between documents is light.
 */
export function colorSchemeMeta(theme: Theme): "light" | "dark" | "light dark" {
  return theme === "system" ? "light dark" : theme;
}

export function serializeThemeCookie(theme: Theme): string {
  return `${THEME_COOKIE}=${theme}; Path=/; Max-Age=${THEME_COOKIE_MAX_AGE}; SameSite=Lax; Secure`;
}