Skip to content

CSS cascade order differs between dev and production when using auto-imported component styles #21903

@robertsLando

Description

@robertsLando

Describe the bug

When using vite-plugin-vuetify with autoImport: true, the CSS cascade order between dev mode and production builds is inconsistent in Vite 8. This causes visual regressions where styles render differently in production.

In dev mode, CSS <style> tags are injected in module-load order:

  1. Statically imported CSS (e.g. import 'material-design-icons-iconfont/dist/material-design-icons.css')
  2. Auto-imported component CSS (e.g. VIcon CSS, loaded lazily when the component first renders)

Since the component CSS loads after the static import, component rules like .v-icon--size-default { font-size: calc(var(--v-icon-size-multiplier) * 1.5em) } win over .material-icons { font-size: 24px } (same specificity, later source order wins).

In production, Rolldown places the auto-imported component CSS before the statically imported CSS in the output bundle. This reverses the cascade: .material-icons { font-size: 24px } now comes after .v-icon--size-default and wins, causing icons to render at a fixed 24px instead of the relative 1.5em size, and display: inline-block instead of display: inline-flex.

The result is icons that appear bigger and bolder in production builds compared to dev mode.

Reproduction

Entry point imports a Vuetify plugin:

// src/plugins/vuetify.js
import { createVuetify } from 'vuetify'
import 'vuetify/styles'
import { aliases, md } from 'vuetify/iconsets/md'
import 'material-design-icons-iconfont/dist/material-design-icons.css'

export default createVuetify({
  icons: { defaultSet: 'md', aliases, sets: { md } },
})

With vite-plugin-vuetify configured with autoImport: true:

// vite.config.mjs
vuetify({ autoImport: { labs: true, directives: true } })

Steps to reproduce

  1. Create a Vuetify 3 app using vite-plugin-vuetify with auto-import and material-design-icons-iconfont
  2. Add any <v-icon> to a page
  3. Run npm run dev — icons render at the correct relative size (1.5em)
  4. Run npm run build && npx vite preview — icons render at a fixed 24px, appearing larger

Evidence from build output

In the production CSS output, VIcon's CSS chunk appears early while the material-icons CSS ends up much later in the main index chunk:

VIcon CSS:            byte offset ~5,587   → .v-icon--size-default { font-size: calc(...1.5em) }
material-icons CSS:   byte offset ~340,440 → .material-icons { font-size: 24px }

Both selectors have the same specificity (single class), so the later one wins — which is .material-icons in production but .v-icon--size-default in dev mode.

Expected behavior

The CSS cascade order in production builds should match dev mode. Statically imported CSS should appear before auto-imported component CSS in the production output, preserving the same cascade resolution.

Actual behavior

Production builds reverse the order: auto-imported component CSS appears before statically imported CSS, changing which rules win the cascade for same-specificity selectors.

Workaround

Wrapping the icon font CSS import in a CSS @layer ensures consistent cascade priority regardless of source order:

/* src/assets/css/material-icons.css */
@import 'material-design-icons-iconfont/dist/material-design-icons.css' layer(icon-fonts);

Unlayered styles (Vuetify component CSS) always take precedence over layered styles, making the result order-independent.

Relation to #21826

This appears to be the same underlying issue reported in #21826, which was closed for lack of a minimal reproduction. The root cause is that Rolldown (introduced in Vite 8 via beta.15) reorders CSS extraction, changing the cascade for same-specificity selectors between dev and production.

System Info

System:
  OS: Linux 6.17.0-14-generic
  Node: 20.18.1
Binaries:
  npm: 10.8.2
npmPackages:
  vite: ^8.0.0 => 8.0.0
  vite-plugin-vuetify: ^2.1.2
  vuetify: ^3.8.5
  material-design-icons-iconfont: ^6.7.0

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions