diff --git a/docs/guide/styling/common-setup.md b/docs/guide/styling/common-setup.md index 17923634..062d6b62 100644 --- a/docs/guide/styling/common-setup.md +++ b/docs/guide/styling/common-setup.md @@ -66,3 +66,77 @@ export default defineNuxtConfig({ ``` When using `configFile`, you can also enable [Experimental Caching](/guide/styling/caching) to improve build performance. + +### Cascade Layers + +Vuetify 4 organizes its styles with [CSS cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer). Because component styles are injected on demand, their relative order — and therefore layer priority — would otherwise depend on injection order, which is non-deterministic in dev and chunk-dependent in production. This can let Vuetify's reset outrank component rules (for example a `` rendering at the wrong font size). + +To make it deterministic, the module inlines the establishing layer order into the SSR'd `` before any component style is parsed. This happens automatically on **Vuetify 4** and needs no configuration. + +The default order is: + +```css +@layer vuetify-core, vuetify-components, vuetify-overrides, vuetify-utilities, vuetify-final; +``` + +::: info +This applies to **Vuetify 4** only and is skipped when `styles` is `'none'`. Vuetify 3's layers are opt-in, use different names, and live under a single top-level `vuetify` layer. +::: + +#### Custom order + +A flat `@layer` statement freezes the listed layers contiguously, so a layer you declare later can only be appended after them. If you need your own layer to sit **between** Vuetify's layers, provide the full order via `cascadeLayers`. Known Vuetify layer names are offered as autocomplete suggestions, and any custom name is allowed. + +```ts +export default defineNuxtConfig({ + modules: ['vuetify-nuxt-module'], + vuetify: { + moduleOptions: { + cascadeLayers: [ + 'vuetify-core', + 'vuetify-components', + 'my-overrides', // beats components, loses to vuetify-overrides + 'vuetify-overrides', + 'vuetify-utilities', + 'vuetify-final' + ] + } + } +}) +``` + +Your list should include Vuetify's layers, otherwise the ordering guarantee is lost for any omitted layer. + +#### Opt out + +Set `cascadeLayers` to `false` to inject nothing and manage the cascade-layer order yourself. + +```ts +export default defineNuxtConfig({ + modules: ['vuetify-nuxt-module'], + vuetify: { + moduleOptions: { + cascadeLayers: false + } + } +}) +``` + +#### Migrating from a manual workaround + +Earlier versions had no fix for the layer-order race, so a common workaround was to declare the layer order yourself — typically an inline head style: + +```ts +// no longer needed +app: { + head: { + style: [{ + innerHTML: '@layer vuetify-core,vuetify-components,vuetify-overrides,vuetify-utilities,vuetify-final;', + tagPriority: -100 + }] + } +} +``` + +- **If your workaround used the default order** (as above), you can simply **remove it** — the module now injects the same statement. Leaving it in place is harmless (re-declaring the same order is a no-op), just redundant. +- **If your workaround declared a custom order** to slot your own layer between Vuetify's — especially via a `css: ['~/layers.css']` file or a `@layer` line in your `configFile` SCSS — move that order to [`cascadeLayers`](#custom-order). Otherwise the module's default statement, parsed first, establishes Vuetify's layers contiguously and your custom layer can only end up after them. Alternatively, set `cascadeLayers: false` to keep your own workaround authoritative. diff --git a/packages/vuetify-nuxt-module/src/types.ts b/packages/vuetify-nuxt-module/src/types.ts index ddcadd55..46dd92b9 100644 --- a/packages/vuetify-nuxt-module/src/types.ts +++ b/packages/vuetify-nuxt-module/src/types.ts @@ -146,6 +146,20 @@ export type LabComponentName = keyof typeof import('vuetify/labs/components') export type LabComponents = boolean | LabComponentName | LabComponentName[] export type VuetifyLocale = keyof typeof import('vuetify/locale') +/** + * A CSS cascade-layer name for `cascadeLayers`. Vuetify 4's built-in layer + * names are suggested for autocomplete, while any custom layer name is allowed. + */ +export type VuetifyCascadeLayer + = | 'vuetify-core' + | 'vuetify-components' + | 'vuetify-overrides' + | 'vuetify-utilities' + | 'vuetify-final' + // `string & {}` keeps the literal suggestions above for autocomplete while + // still allowing arbitrary custom layer names. + | (string & {}) + export interface VOptions extends Partial> { /** * Configure the SSR options. @@ -289,6 +303,33 @@ export interface MOptions { */ utilities?: boolean } + /** + * Establishing CSS cascade-layer order, inlined into the SSR'd ``. + * + * In treeshaking modes Vuetify's per-component styles are injected on demand, + * each carrying its own `@layer vuetify-components { … }` block. Their order + * is otherwise decided by injection sequence (non-deterministic in dev, chunk + * order in prod), which can let `vuetify-core.reset` outrank component rules + * (e.g. `` renders at the wrong font-size). Declaring the + * layer order once, before any component style is parsed, makes it + * deterministic. See https://github.com/vuetifyjs/nuxt-module/issues/381. + * + * - **omit** (default) — inject Vuetify's order: + * `vuetify-core, vuetify-components, vuetify-overrides, vuetify-utilities, vuetify-final`. + * - **`string[]`** — inject your own order, e.g. to slot a custom layer + * between Vuetify's (a flat `@layer` statement freezes the named layers + * contiguously, so a later-declared new layer can only append). Your list + * should include Vuetify's layers, or the race it fixes can reappear for + * any omitted layer. + * - **`false`** — inject nothing; manage the cascade-layer order yourself. + * + * Vuetify 4 only — ignored on Vuetify 3 (layers are opt-in there, use + * different names, and live under a single top-level `vuetify` layer) and + * when `styles` is `'none'`. + * + * @since v1.0.0 + */ + cascadeLayers?: VuetifyCascadeLayer[] | false /** * Disable the modern SASS compiler and API. * diff --git a/packages/vuetify-nuxt-module/src/utils/configure-nuxt.ts b/packages/vuetify-nuxt-module/src/utils/configure-nuxt.ts index d51b222f..f0f9d0c4 100644 --- a/packages/vuetify-nuxt-module/src/utils/configure-nuxt.ts +++ b/packages/vuetify-nuxt-module/src/utils/configure-nuxt.ts @@ -3,7 +3,7 @@ import type { VuetifyNuxtContext } from './config' import { addImports, addPlugin, addTemplate, extendWebpackConfig, isNuxtMajorVersion, resolvePath } from '@nuxt/kit' import { RESOLVED_VIRTUAL_MODULES } from '../vite/constants' import { toKebabCase } from './index' -import { resolveVuetifyConfigFile } from './styles' +import { applyCascadeLayersHeadStyle, resolveVuetifyConfigFile } from './styles' import { addVuetifyNuxtPlugins } from './vuetify-nuxt-plugins' export function getTemplate (source: string, settings: string | null): string { @@ -55,6 +55,12 @@ export async function configureNuxt ( } } + // Inline the establishing cascade-layer order into the SSR'd so layer + // priority is parsed before any runtime-injected component