Environment
Vuetify version(s): 4.1.0 – 4.1.3 (current computedThemes/provideTheme implementation, unchanged in this range)
Steps to reproduce
- Configure a theme with color
variations (e.g. variations: { colors: ['homework'], darken: 4, lighten: 4 }).
- 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.
- 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.
Environment
Vuetify version(s): 4.1.0 – 4.1.3 (current
computedThemes/provideThemeimplementation, unchanged in this range)Steps to reproduce
variations(e.g.variations: { colors: ['homework'], darken: 4, lighten: 4 }).theme.current.value.colors['homework-darken-2']from inside a component nested under any component that usesmakeThemeProps()+provideTheme()(VCard, VSheet, VAppBar, VMain, a Nuxt default layout rendered inside<v-app>, etc.) — even if that component never sets athemeprop.theme.global.current.value.colors['homework-darken-2']read from the same spot.Expected behavior
theme.current.value.colorsshould include the same processed data astheme.global.current.value.colorswhen nothemeprop 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']isundefined. Only the basecolors['homework']key from the raw theme config is present.theme.global.current.value.colorsin the same component does contain the variation keys correctly.Root cause
In
packages/vuetify/src/composables/theme.ts:current(line ~400) istoRef(() => computedThemes.value[name.value]).computedThemes(~line 375) is wheremergeDeep(dark/light defaults, original),genVariations(...), andgenOnColors(...)all actually run.provideTheme()(~line 647-669), which is called by every component usingmakeThemeProps()regardless of whether athemeprop value is actually passed, builds its owncurrentas:theme.themesis the raw, unprocessed theme configref(line ~363) — none ofcomputedThemes's merge/variation/on-color generation applies to it.theme.global.currentis unaffected because it's captured once at the root and passed through unchanged on every re-provide (...themespread at line ~660), so it always points at the correctly processed rootcurrent.Net effect: any
useTheme().currentread below the firstprovideTheme()-calling ancestor in the tree (i.e. almost any non-trivial component tree) silently loses variations, generated on-colors, and dark/light default merging, whiletheme.name.valuestill resolves correctly (sincenameinprovideThemedoes fall back totheme.name.valuecorrectly) — 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()'scurrentshould derive from the same processed source as the root (e.g. reusetheme.computedThemes— which is already spread through via...theme— instead of the rawtheme.themes), so a scoped/re-provided theme context stays fully processed even without an explicitthemeprop override.