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:
- Statically imported CSS (e.g.
import 'material-design-icons-iconfont/dist/material-design-icons.css')
- 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
- Create a Vuetify 3 app using
vite-plugin-vuetify with auto-import and material-design-icons-iconfont
- Add any
<v-icon> to a page
- Run
npm run dev — icons render at the correct relative size (1.5em)
- 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.
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
Describe the bug
When using
vite-plugin-vuetifywithautoImport: 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:import 'material-design-icons-iconfont/dist/material-design-icons.css')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-defaultand wins, causing icons to render at a fixed 24px instead of the relative1.5emsize, anddisplay: inline-blockinstead ofdisplay: 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:
With
vite-plugin-vuetifyconfigured withautoImport: true:Steps to reproduce
vite-plugin-vuetifywith auto-import andmaterial-design-icons-iconfont<v-icon>to a pagenpm run dev— icons render at the correct relative size (1.5em)npm run build && npx vite preview— icons render at a fixed24px, appearing largerEvidence 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:
Both selectors have the same specificity (single class), so the later one wins — which is
.material-iconsin production but.v-icon--size-defaultin 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
@layerensures consistent cascade priority regardless of source order: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
Validations