cssnestedtailwind-cssvitetailwind-css-4

CSS nesting with Vite 6.2.3 & TailwindCSS v4.0.16 only works on dev, not on build


I'm working with:

Whenever I run my dev server, the .container classes are generated and everything works fine. The moment I do a build, .container is not included in the css.

Default vite setup vite.config.ts

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
  plugins: [
    tailwindcss(),
  ],
})

package.json

{
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "@tailwindcss/vite": "^4.0.16",
    "tailwindcss": "^4.0.16"
  }
}

app.css

@import "tailwindcss";

@theme {
  /* Extra breakpoints */
  --breakpoint-xs: 440px;
  --breakpoint-ml: 960px;

  /* Container paddings */
  --container-padding: 30px;

  /* Container sizes */
  --container-xs: 1100px;
  --container-sm: 1325px;
  --container: 1625px;
  --container-lg: 2180px;
  --container-xl: 2447px;
  --container-2xl: 2674px;
}
.container {
  max-width: calc(var(--container) + (var(--container-padding) * 2)) !important;

  margin-left: auto !important;
  margin-right: auto !important;

  padding-left: var(--container-padding) !important;
  padding-right: var(--container-padding) !important;

  &-xs {
    max-width: calc(var(--container-xs) + (var(--container-padding) * 2)) !important;
  }

  &-sm {
    max-width: calc(var(--container-sm) + (var(--container-padding) * 2)) !important;
  }

  &-lg {
    max-width: calc(var(--container-lg) + (var(--container-padding) * 2)) !important;
  }

  &-xl {
    max-width: calc(var(--container-xl) + (var(--container-padding) * 2)) !important;
  }

  &-2xl {
    max-width: calc(var(--container-2xl) + (var(--container-padding) * 2)) !important;
  }
}

index.html

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="/src/styles.css" rel="stylesheet">
</head>

<body>
  <div class="container">
    <h1 class="text-3xl font-bold underline text-green-500">
      Hello world!
    </h1>
  </div>
</body>
</html>

So the issue, espected output of my css would be the following. But it does not get rendered. Only when in dev mode it works, but not after a build.

:root {
  /* Extra breakpoints */
  --breakpoint-xs: 440px;
  --breakpoint-ml: 960px;
  /* Container paddings */
  --container-padding: 30px;
  /* Container sizes */
  --container-xs: 1100px;
  --container-sm: 1325px;
  --container: 1625px;
  --container-lg: 2180px;
  --container-xl: 2447px;
  --container-2xl: 2674px;
}

.container {
  max-width: calc(var(--container) + var(--container-padding) * 2) !important;
  margin-left: auto !important;
  margin-right: auto !important;
  padding-left: var(--container-padding) !important;
  padding-right: var(--container-padding) !important;
}
.container-xs {
  max-width: calc(var(--container-xs) + var(--container-padding) * 2) !important;
}
.container-sm {
  max-width: calc(var(--container-sm) + var(--container-padding) * 2) !important;
}
.container-lg {
  max-width: calc(var(--container-lg) + var(--container-padding) * 2) !important;
}
.container-xl {
  max-width: calc(var(--container-xl) + var(--container-padding) * 2) !important;
}
.container-2xl {
  max-width: calc(var(--container-2xl) + var(--container-padding) * 2) !important;
}

Video of issue:


Solution

  • As @imhvost pointed out, the &-xs etc. is no valid CSS.

    I assume the dev build recovers from that error more or less (it also does not apply the &- declaration but at least the first styles directly applied to .container are deemed valid), but in prod build, the LightningCSS minification (which is used by @tailwindcss/vite under the hood for transpilation) which seems to be running twice for some reasons, then removes the errorous parts of the CSS more aggressively.

    CSS which is compiled by yarn dev looks like this:

    .container {
      max-width: calc(var(--container) + (var(--container-padding) * 2)) !important;
      margin-left: auto !important;
      margin-right: auto !important;
      padding-left: var(--container-padding) !important;
      padding-right: var(--container-padding) !important;
      &-xs {
        max-width: calc(var(--container-xs) + (var(--container-padding) * 2)) !important;
      }
      &-sm {
        max-width: calc(var(--container-sm) + (var(--container-padding) * 2)) !important;
      }
      &-lg {
        max-width: calc(var(--container-lg) + (var(--container-padding) * 2)) !important;
      }
      &-xl {
        max-width: calc(var(--container-xl) + (var(--container-padding) * 2)) !important;
      }
      &-2xl {
        max-width: calc(var(--container-2xl) + (var(--container-padding) * 2)) !important;
      }
    }
    

    While the relevant CSS compiled by yarn build is this (note the -2xl is stripped out as well as the container-related styles):

    -xs.container {
        max-width: calc(var(--container-xs) + (var(--container-padding)*2))!important
    }
    
    -sm.container {
        max-width: calc(var(--container-sm) + (var(--container-padding)*2))!important
    }
    
    -lg.container {
        max-width: calc(var(--container-lg) + (var(--container-padding)*2))!important
    }
    
    -xl.container {
        max-width: calc(var(--container-xl) + (var(--container-padding)*2))!important
    }
    

    Also, my IDE shows an error with the selector starting with &-2. This suggests that this breaks because the selector starting with a dash, followed by a number, renders the whole rule invalid.

    Screenshot of errorous CSS selector

    As a workaround i'd suggest to write out the selectors without nesting or combinators:

    .container {
      max-width: calc(var(--container) + (var(--container-padding) * 2)) !important;
    
      margin-left: auto !important;
      margin-right: auto !important;
    
      padding-left: var(--container-padding) !important;
      padding-right: var(--container-padding) !important;
    }
    .container-xs {
      max-width: calc(var(--container-xs) + (var(--container-padding) * 2)) !important;
    }
    
    .container-sm {
      max-width: calc(var(--container-sm) + (var(--container-padding) * 2)) !important;
    }
    
    .container-lg {
      max-width: calc(var(--container-lg) + (var(--container-padding) * 2)) !important;
    }
    
    .container-xl {
      max-width: calc(var(--container-xl) + (var(--container-padding) * 2)) !important;
    }
    
    .container-2xl {
      max-width: calc(var(--container-2xl) + (var(--container-padding) * 2)) !important;
    }