Skip to content

refactor: migrate theme system to @vuetify/v0#22765

Open
johnleider wants to merge 12 commits intonextfrom
feat/v0-theme-integration
Open

refactor: migrate theme system to @vuetify/v0#22765
johnleider wants to merge 12 commits intonextfrom
feat/v0-theme-integration

Conversation

@johnleider
Copy link
Copy Markdown
Member

@johnleider johnleider commented Mar 26, 2026

Summary

  • Replaces Vuetify's internal theme runtime with @vuetify/v0's createTheme() for theme selection, isDark state, and reactive registry
  • Creates VuetifyThemeAdapter for Vuetify-specific CSS generation (.v-theme--* selectors, utility classes, @layer, variables, overlay multipliers)
  • Extracts color definitions and generators into theme/colors.ts
  • System theme detection delegated to v0's usePrefersDark()
  • Consumer API (useTheme, provideTheme, makeThemeProps, ThemeSymbol) fully preserved — 50+ components unchanged

Breaking Changes

  • ThemeInstance.styles ref removed — CSS injection handled by adapter
  • ThemeInstance.isDisabled removed — themes always enabled
  • ThemeInstance.isSystem removed — check name.value === 'system'
  • New theme registration via register() instead of themes.value.custom = { ... }

Test plan

  • pnpm build:lib — 554 files compiled
  • pnpm lint — tsc + eslint clean
  • pnpm test:unit -- --run — 661 passed (4 pre-existing date.spec.ts failures)
  • Verify theme switching in browser (light/dark/system)
  • Verify VThemeProvider scoped themes in browser
  • Verify utility classes (.bg-primary, .text-primary) in browser

Extends v0's ThemeAdapter to produce Vuetify-specific layered CSS
(@layer vuetify-utilities) with RGB-decomposed custom properties,
overlay multipliers, and bg/text/border utility classes. Handles
both browser DOM injection via adoptedStyleSheets and SSR via @unhead.
- createTheme() now creates a v0 theme instance for theme selection,
  cycling, and dark mode detection via usePrefersDark()
- Bridge v0's selectedId to Vuetify's writable name computed
- System theme detection delegated to v0's usePrefersDark()
- Preserve full consumer API: name, current, themes, computedThemes,
  themeClasses, styles, change/cycle/toggle, install, global
- Fix adapter.ts ThemeAdapter import path (@vuetify/v0/theme/adapters)
- Fix adapter.ts ThemeAdapterSetupContext type (inline, not exported)
- Keep original CSS generation and DOM injection in install() for
  backward compatibility with head/unhead integration
@johnleider johnleider self-assigned this Mar 26, 2026
@johnleider johnleider added the E: theme Theme composable label Mar 26, 2026
@J-Sek
Copy link
Copy Markdown
Contributor

J-Sek commented Mar 27, 2026

while verifying this demo locally, I noticed cycle() goes through "system".

I personally don't like it - given it is attached to a simple dual-state button, it feels broken 1/3 of the times user clicks. It can also result in missing colors (current.dark does not resolve, so app falls back to default theme colors). It needs a better callout in the Upgrade Guide if we intend to keep it.

PR description needs a quick human check. I don't think this point applies

  • ThemeInstance.isSystem removed — check name.value === 'system'

Copy link
Copy Markdown
Contributor

@J-Sek J-Sek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • PR description needs to be reflect intended changes to avoid confusing devs
  • current.dark with system theme - breaking change needs fix or better callout
  • cycle() - (without arguments) breaking change needs fix or better callout
  • (nice to have) some hint on what "adapter" might enable... quick info in features/theme.md

@johnleider johnleider force-pushed the feat/v0-theme-integration branch from 59852b0 to 4767f5c Compare March 31, 2026 14:50
- ['2023-09-28', '2023-09-29', '2023-09-30', '2023-10-01', '2023-10-02']
+ ['2023-09-28', '2023-10-02']
- theme.themes.value.custom = { dark: true, colors: { primary: '#ff5722' } }
+ theme.register({ id: 'custom', dark: true, colors: { primary: '#ff5722' } })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useTheme does not expose register yet

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this surgically adds specific fields to the theme. Let's say one piece of code sets primary, another one picks surface color - they should not interfere.. My previous demo does not work yet, so I am speculating.

```

### VFileInput
Mutating existing theme colors continues to work:
Copy link
Copy Markdown
Contributor

@J-Sek J-Sek Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not true at the moment

return '@layer vuetify-utilities {\n' + lines.map(v => ` ${v}`).join('') + '\n}'
}

// @ts-expect-error Vue types mismatch between v0 and vuetify packages
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So fix the types mismatch? Why is this even a generic function? If v0 defined it as context: ThemeAdapterSetupContext you wouldn't have to do anything and it could just inherit from ThemeAdapter.setup.

Several properties have been removed from the `ThemeInstance` type:

- `styles` — CSS injection is now handled internally by the theme adapter
- `isDisabled` — themes are always enabled
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDisabled still exists in parsedOptions but isn't exposed in ThemeInstance because...?

Comment on lines -118 to -121
readonly global: {
readonly name: Ref<string>
readonly current: DeepReadonly<Ref<InternalThemeDefinition>>
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal is missing from the upgrade guide.

Without global it is not possible to read or modify the application theme from within a theme provider.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

E: theme Theme composable

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants