Theme: setTheme deprecated, layered API replaces it

The legacy setTheme() function and data-dt-theme attribute have been deprecated in favor of the new layered API: initDialtoneTheme, setMode, setBrand, setContrast, and setMaterial.

TLDR

  • setTheme() still works but is deprecated. Migrate to initDialtoneTheme() for startup, setMode() / setBrand() / setContrast() / setMaterial() for runtime switching.
  • Root attributes: data-dt-theme is no longer set. New attributes are data-dt-mode, data-dt-brand, data-dt-contrast, and data-dt-material.
  • Run npx dialtone-migration-helper and select theme to mode to automate most of this.

Why

The old setTheme() model coupled three independent concerns — color mode (light/dark), brand (dp/tmo/melon), and contrast (default/high) — into a single monolithic theme object. Switching from light to dark required swapping the entire object, including brand tokens that hadn't changed.

The layered API separates these four orthogonal dimensions:

  • Mode (setMode) — light or dark. Changes instantly with a single attribute toggle.
  • Brand (setBrand) — which color palette overrides to apply (dp, tmo, melon, etc.).
  • Contrast (setContrast) — default or high contrast, for WCAG AAA accessibility.
  • Material (setMaterial) — surface texture layer (sandstone default, plus steel, graphite, iron, amethyst, jade). Applied independently of mode or brand; some brands lock their material — see Brand-locked materials for details.

You can now switch any dimension independently, which reduces bundle work, enables runtime contrast toggles without a full theme reload, and maps cleanly to user preferences (OS dark-mode + a brand choice + an accessibility setting + a material preference are four separate controls).

What Changed

Before After
Startup call setTheme(DpLight) initDialtoneTheme(Dp, 'light')
Mode switching setTheme(DpDark) setMode('dark')
Brand switching setTheme(TmoLight) setBrand(Tmo)
Contrast setTheme(theme, root, HighContrast) setContrast(HighContrast)
Disable contrast setTheme(theme, root, null) setContrast(null)
Material (not available) setMaterial('sandstone')
Root attribute (mode) data-dt-theme="dp-light" data-dt-mode="light"
Root attribute (brand) data-dt-brand="dp" (already existed) data-dt-brand="dp" (unchanged)
Root attribute (contrast) data-dt-contrast="default" (already existed) data-dt-contrast="default" (unchanged)
Root attribute (material) (not available) data-dt-material="sandstone"
CSS selector [data-dt-theme="dp-light"] [data-dt-mode="light"]

Quick Checklist

  1. Run the migration script: npx dialtone-migration-helper --cwd ./src and select theme to mode.
  2. Review any data-dt-theme="invert" patterns the script flagged — decide whether each should adopt v-dt-mode (see Manual Review).
  3. Smoke-test your app: toggle light/dark, switch brand if applicable, toggle high contrast if used, apply a material if your app uses one.

Migration

Run the migration helper from your project root:

npx dialtone-migration-helper --cwd ./src

Select theme to mode from the interactive menu. Add --dry-run to preview changes without writing files. Add --yes to apply without prompting.

The script handles the patterns below automatically. After it runs, also run npx eslint --fix to clean up the setTheme named import that becomes unused after the call-site rewrite.

Startup call

Before

setTheme(DpLight);

After

initDialtoneTheme(Dp, 'light');

Call initDialtoneTheme() once on startup. It loads core tokens, sets the initial mode and brand, and sets contrast to 'default'.

Runtime mode switching

Before

setTheme(DpDark);

After

setMode('dark');

Root attribute (HTML/CSS)

Before

<html data-dt-theme="dp-light">
[data-dt-theme="dp-light"] .d-banner { ... }

After

<html data-dt-mode="light" data-dt-brand="dp" data-dt-contrast="default" data-dt-material="sandstone">
[data-dt-mode="light"] .d-banner { ... }

If your code reads getAttribute('data-dt-theme') or sets it manually, the migration script rewrites setAttribute/getAttribute call first arguments and CSS [data-dt-theme] selectors automatically.

Contrast

Before

import HighContrast from '@dialpad/dialtone/themes/high-contrast';

// Enable on init
setTheme(DpLight, document.documentElement, HighContrast);

// Toggle on/off — required full re-init
setTheme(DpLight);
setTheme(DpLight, document.documentElement, HighContrast);

After

import HighContrast from '@dialpad/dialtone/themes/high-contrast';

// Enable
setContrast(HighContrast);

// Disable (return to default)
setContrast(null);

Manual Review for v-dt-mode Candidates

The script flags data-dt-theme="invert" patterns with a comment rather than auto-rewriting them:

<!-- TODO: review for v-dt-mode adoption — see /guides/migration/theme-to-mode/ -->
<section data-dt-theme="invert">...</section>

Why not auto-rewrite? The v-dt-mode directive resolves the inverted mode against the live parent mode using a MutationObserver — it stays reactive when the parent mode changes at runtime. A static data-dt-mode="dark" replacement would be wrong for any component sitting inside a dynamic parent. The correct rewrite depends on what the surrounding mode context is in your app.

For each flagged location, decide:

Adopt v-dt-mode (recommended when the parent mode is dynamic):

<!-- Before: manually inverted region -->
<section data-dt-theme="invert">Dark island inside a light page</section>

<!-- After: reactive invert via directive -->
<section v-dt-mode>Dark island inside a light page</section>
import { DtModeDirective } from '@dialpad/dialtone/vue';
app.use(DtModeDirective);

Keep a static override (acceptable when the parent mode never changes):

<section data-dt-mode="dark">Always dark regardless of parent</section>

Use DtModeIsland (when you need a styled container with background):

<dt-mode-island mode="invert">Inverted region with surface color</dt-mode-island>

For full directive documentation see the v-dt-mode Storybook page. For DtModeIsland, see the Mode Island component page.

Need Help?

If you have any troubles, please let us know in the #dialtone Dialpad channel.

Theme: setTheme deprecated, layered API replaces it documentation last updated Thursday, June 18, 2026