# Match your brand with tokens

Set the accent once through config for the common case, follow the OS or switch themes live from your own toggle, or override the semantic tokens when your design system wants control of every surface.

- Level: Appearance
- Uses: `theme.accentColor`, `theme.mode: 'auto'`, `--ks-* tokens`, `data-ks-theme`, `--ks-toolbar-height`

Everything the SDK paints is driven by CSS custom properties. You restyle it by setting those properties, never by overriding its class names, which are not a stable contract and change between releases. Work at the highest layer that solves your problem: overriding `--ks-surface` once is better than overriding eight component backgrounds, because it also catches the components added in the next release.

## 1. Just our brand color

Hover, subtle and border shades are derived from it. Nothing else to set. Any CSS color: a hex, a name or one color function; anything else is ignored with a console warning, and the indigo stays.

`viewer.ts`

```ts
new PDFViewer(container, doc, {
  theme: { accentColor: '#e91e63' },
});
```

## 2. Follow the OS

`'auto'` follows the OS through `prefers-color-scheme`; `'light'` (the default) and `'dark'` beat the OS in both directions. The mode is applied as a `data-ks-theme` attribute on the viewer's root element, so two viewers on one page can differ. The document itself never darkens.

`viewer.ts`

```ts
new PDFViewer(container, doc, { theme: { mode: 'auto' } });
```

## 3. Switching theme live

Keep `theme.mode: 'auto'` and set the attribute on `<html>` from your own theme toggle. The viewer and its dialogs switch immediately, with no rebuild. Remove the attribute to follow the OS again.

`theme-toggle.ts`

```ts
document.documentElement.dataset.ksTheme = isDark ? 'dark' : 'light';
```

## 4. Our whole design system

Override the semantic tokens once; toolbar, ribbon, panels, dialogs, popovers and whatever is added next release follow. A value set on `:root` reaches the viewer and the surfaces that render into `document.body` (the signature dialog, the security modal, every popover); a value on your own container reaches the viewer inside it. Your stylesheet loads after the SDK's, so an override at equal specificity wins without `!important`.

`theme.css`

```css
:root {
  --ks-accent: #e91e63;
  --ks-accent-hover: #c2185b;
  --ks-accent-subtle: #fce4ec;
  --ks-accent-text: #ffffff;

  --ks-surface: #ffffff;
  --ks-surface-raised: #fafafa;
  --ks-surface-sunken: #f1f1f4;

  --ks-text: #1a1a2e;
  --ks-text-muted: #6b6b80;
  --ks-border: #e4e4ec;

  --ks-font: 'Inter', system-ui, sans-serif;
  --ks-radius: 4px;
  --ks-radius-sm: 3px;
}

/* One viewer instance, if you host several. */
#invoice-viewer {
  --ks-accent: #e91e63;
}
```

> With the default `mode: 'light'` these values apply wherever the page around the viewer is light; with `mode: 'auto'` they apply in both themes.

## 5. A dark theme of your own

Leave `theme.mode` at `'auto'` and redefine the tokens under both selectors: the first for a page that sets `data-ks-theme="dark"` on `<html>` itself, the second for a dark OS. An explicit `theme.mode: 'dark'` uses the SDK's own dark palette; your `accentColor` still applies.

`theme.css`

```css
:root[data-ks-theme='dark'] {
  --ks-surface: #11131a;
  --ks-surface-raised: #171a22;
  --ks-surface-sunken: #0c0e13;
  --ks-text: #e8e8f0;
  --ks-text-muted: #9a9ab0;
  --ks-border: #252833;
  --ks-accent: #f48fb1; /* lighter in dark — contrast, not hue */
  --ks-accent-text: #11131a;
  --ks-canvas-bg: #07080b;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-ks-theme='light']) {
    /* same block */
  }
}
```

## 6. A denser toolbar

Component tokens each fall back to a semantic token, so one part can be retuned without redefining the system. In a compact or narrow viewer the toolbar uses its own smaller heights.

`theme.css`

```css
:root {
  --ks-toolbar-height: 56px;
  --ks-space-2: 6px;
}
```

## Gotchas

- Primitives (the raw color ramp) are not part of the public contract and may change in any release. The semantic tokens are the contract.
- Three things in the markup are kept across releases: the tokens (`--ks-*`), the `data-ks-*` attributes on the viewer element, and the structural classes (`.ks-viewer`, `.ks-toolbar`, `.ks-nav-bar`, `.ks-body`, `.ks-sidebar`, `.ks-page-container`, `.ks-page`, `.ks-portal`, `.ks-dialog-backdrop`, `.ks-dialog`, `.ks-toast`, `.ks-loading-overlay`, `.ks-busy-overlay`), for placing, sizing and hiding a region, not for repainting it. Every other class is internal.
- Dialogs, sheets and popovers live in one portal layer shared by the whole page. With several viewers in different themes or accents, they take the theme of the viewer created last. To style them directly, target `.ks-portal`.
- The `ks` prefix is fixed and not configurable.

## Related

- [Theme tokens](https://kernstra.com/docs/web-sdk/guides/theme-tokens)
- [Theme](https://kernstra.com/docs/web-sdk/config/theme/mode)
- [Bring your own chrome](https://kernstra.com/docs/web-sdk/recipes/own-chrome)

Web page: https://kernstra.com/docs/web-sdk/recipes/theming-tokens
