TLDR
setTheme()still works but is deprecated. Migrate toinitDialtoneTheme()for startup,setMode()/setBrand()/setContrast()/setMaterial()for runtime switching.- Root attributes:
data-dt-themeis no longer set. New attributes aredata-dt-mode,data-dt-brand,data-dt-contrast, anddata-dt-material. - Run
npx dialtone-migration-helperand 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 (sandstonedefault, plussteel,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
- Run the migration script:
npx dialtone-migration-helper --cwd ./srcand select theme to mode. - Review any
data-dt-theme="invert"patterns the script flagged — decide whether each should adoptv-dt-mode(see Manual Review). - 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.