# Dark mode

Render dark PDF pages, synchronize your theme, and style overlays to match.

Source: https://anara.com/lector/docs/dark-mode

These docs describe the source deployed with this site, tracking main rather than a versioned npm release. Check your installed @anaralabs/lector and pdfjs-dist versions before applying examples.

`Root` accepts `colorScheme="dark"` to remap page colors during canvas rendering. It changes the document's pixels; your application still owns the toolbar, sidebar, and surrounding UI theme.

## Follow your app theme

Use the `pdf-setup.ts` from [installation](https://anara.com/lector/docs/installation). Pass your resolved theme as a prop:

```tsx title="themed-viewer.tsx"
"use client";

import { CanvasLayer, Page, Pages, Root, TextLayer } from "@anaralabs/lector";
import "./pdf-setup";

export default function ThemedViewer({ dark }: { dark: boolean }) {
  return (
    <Root
      source="/sample.pdf"
      colorScheme={dark ? "dark" : "light"}
      style={{ height: 600 }}
    >
      <Pages><Page><CanvasLayer /><TextLayer /></Page></Pages>
    </Root>
  );
}
```

Unlike initial zoom options, `colorScheme` updates the store when the prop changes. The canvas, thumbnails, and high-zoom detail layer use the active scheme. Cached bitmaps can be reused when available; switching is not guaranteed to be instantaneous for every document or cache state.

## Let a viewer control its own theme

If you do not pass a `colorScheme` prop, a child of `Root` can switch the scheme through the store:

```tsx title="dark-mode-toggle.tsx"
"use client";

import { usePdf } from "@anaralabs/lector";

export default function DarkModeToggle() {
  const scheme = usePdf((state) => state.colorScheme);
  const setScheme = usePdf((state) => state.setColorScheme);

  return (
    <button type="button" onClick={() => setScheme(scheme === "dark" ? "light" : "dark")}>
      {scheme === "dark" ? "Use light pages" : "Use dark pages"}
    </button>
  );
}
```

Choose one owner for the scheme. When the `colorScheme` prop is supplied, it synchronizes the store back to that value; a store-only toggle will not override it permanently.

## Customize the palette

`darkModeColors` accepts optional `background` and `foreground` colors. The defaults, exported as `DEFAULT_DARK_MODE_COLORS`, are `#141210` and `#eae6e0`.

```tsx
<Root
  source="/sample.pdf"
  colorScheme="dark"
  darkModeColors={{ background: "#1e1e2e", foreground: "#cdd6f4" }}
>
  {/* Your bounded Pages container */}
</Root>
```

The background replaces white paper and the foreground replaces black text and line art. Omitted fields in a supplied `darkModeColors` prop resolve to the defaults. The prop controls the palette even if you toggle the scheme through the store.

Without a palette prop, `setColorScheme("dark", { background, foreground })` updates it at runtime. Toggling the scheme without a new palette preserves the current palette.

Pass resolved CSS colors such as hex or `rgb(...)`; `var(--token)` is not resolved by the canvas color mapper. If your theme stores colors in CSS variables, resolve them after the intended theme class has been applied. Reading computed styles during render can capture the previous theme's values.

## Match custom overlays

`createDarkModeColorMap(colors)` returns a color-mapping function. Use it for application overlays that should follow the PDF palette:

```tsx title="themed-highlight-layer.tsx"
"use client";

import { HighlightLayer, createDarkModeColorMap, usePdf } from "@anaralabs/lector";

export default function ThemedHighlightLayer() {
  const scheme = usePdf((state) => state.colorScheme);
  const colors = usePdf((state) => state.darkModeColors);
  const yellow = "#ffdf60";
  const background = scheme === "dark" ? createDarkModeColorMap(colors)(yellow) : yellow;

  return <HighlightLayer style={{ background, opacity: 0.4 }} />;
}
```

Mount this under `Page` in place of `HighlightLayer`. If you use blend modes, test them against dark paper; a multiply blend chosen for white pages can make a highlight disappear on dark ones.

## Rendering behavior and limits

Lector remaps vector fills and strokes and uses a custom PDF.js canvas factory for internal scratch canvases. The current implementation also detects mostly white, page-covering scanned paper and recolors qualifying scans. Photos, colorful scans, and embedded figures are generally preserved. Scan detection is heuristic, so test your own documents rather than assuming every raster page will change.

Some mesh gradients retain their original colors, and transparency or soft-mask midtones can differ. DOM annotations and form widgets are separate from canvas recoloring; style them with your application's CSS. Passing a custom `CanvasFactory` through `documentOptions` replaces Lector's scratch-canvas recoloring integration.

Remove older CSS inversion filters when using `colorScheme`; combining both approaches applies another transformation to the already recolored page.
