Skip to content

provideTheme() current ref bypasses computedThemes, dropping variations/on-colors/dark-light merge #22986

Description

@johnleider

Environment

Vuetify version(s): 4.1.0 – 4.1.3 (current computedThemes/provideTheme implementation, unchanged in this range)

Steps to reproduce

  1. Configure a theme with color variations (e.g. variations: { colors: ['homework'], darken: 4, lighten: 4 }).
  2. Read theme.current.value.colors['homework-darken-2'] from inside a component nested under any component that uses makeThemeProps() + provideTheme() (VCard, VSheet, VAppBar, VMain, a Nuxt default layout rendered inside <v-app>, etc.) — even if that component never sets a theme prop.
  3. Compare against theme.global.current.value.colors['homework-darken-2'] read from the same spot.

Expected behavior

theme.current.value.colors should include the same processed data as theme.global.current.value.colors when no theme prop override is in effect for that scope — variations, generated on-colors, and dark/light default merging should all be present either way.

Actual behavior

theme.current.value.colors['homework-darken-2'] is undefined. Only the base colors['homework'] key from the raw theme config is present. theme.global.current.value.colors in the same component does contain the variation keys correctly.

Root cause

In packages/vuetify/src/composables/theme.ts:

  • The root theme instance's current (line ~400) is toRef(() => computedThemes.value[name.value]). computedThemes (~line 375) is where mergeDeep(dark/light defaults, original), genVariations(...), and genOnColors(...) all actually run.
  • provideTheme() (~line 647-669), which is called by every component using makeThemeProps() regardless of whether a theme prop value is actually passed, builds its own current as:
    const current = toRef(() => theme.themes.value[name.value])
    theme.themes is the raw, unprocessed theme config ref (line ~363) — none of computedThemes's merge/variation/on-color generation applies to it.
  • theme.global.current is unaffected because it's captured once at the root and passed through unchanged on every re-provide (...theme spread at line ~660), so it always points at the correctly processed root current.

Net effect: any useTheme().current read below the first provideTheme()-calling ancestor in the tree (i.e. almost any non-trivial component tree) silently loses variations, generated on-colors, and dark/light default merging, while theme.name.value still resolves correctly (since name in provideTheme does fall back to theme.name.value correctly) — making the symptom look like a targeting/scope bug rather than a data-processing one.

Reproduction / discussion

Originally surfaced and diagnosed in Discord: https://discord.com/channels/340160225338195969/1514650254522192013 (thread starting ~2026-07-01, shujji + j_sek).

Suggested fix

provideTheme()'s current should derive from the same processed source as the root (e.g. reuse theme.computedThemes — which is already spread through via ...theme — instead of the raw theme.themes), so a scoped/re-provided theme context stays fully processed even without an explicit theme prop override.

Metadata

Metadata

Assignees

Labels

T: bugFunctionality that does not work as intended/expected

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions