Skip to content

Stacked mode + breakpoint variants (e.g. @dark@sm) emit invalid media query: missing and between media features #444

Description

@0Miles

Description

When a utility class combines a color-mode variant with a breakpoint variant (e.g. bg:black@dark@sm) and modeTrigger is media, the engine emits a malformed media query — the two media features are joined by a space instead of and:

@media (width>=52.125rem) (prefers-color-scheme:dark){.bg\:black\@dark\@sm{background-color:oklch(0% 0 none)}}

This is invalid CSS, so browsers drop the whole rule (the style silently never applies), and downstream CSS minifiers complain — e.g. esbuild during a Vite build:

▲ [WARNING] Expected "{" but found "(" [css-syntax-error]

The cause appears to be in packages/engine/src/utils/generate-condition.ts: when the mode's prefers-color-scheme component is pushed into an existing conditions.media array, generateCondition joins the media feature nodes with ' ' and no and combinator is inserted.

Note that @master/css-validator (same version) returns false for bg:black@dark@sm, so stacked variants may no longer be intended syntax in 2.0 — but then the expected behavior would be for the engine to reject the class rather than emit invalid CSS. Either outcome (emit ... and (prefers-color-scheme:dark), or treat the class as invalid) would be fine; silently generating a rule that browsers discard is the worst of both.

Expected: @media (width>=52.125rem) and (prefers-color-scheme:dark){...} — or the class is rejected as invalid.

Reproduction

  1. bun add @master/css@rc @master/css-compiler@rc @master/css-engine@rc (all resolve to 2.0.0-rc.88)
  2. master.css:
    @import '@master/css';
  3. repro.mjs:
    import { compileCSSManifestFile } from '@master/css-compiler'
    import { MasterCSS } from '@master/css-engine'
    
    const { manifest } = await compileCSSManifestFile('./master.css')
    const css = new MasterCSS(manifest)
    for (const name of ['bg:black@sm', 'bg:black@dark', 'bg:black@dark@sm']) {
        console.log(name, '=>', css.generate(name).map(r => r.text).join(''))
    }
  4. bun repro.mjs prints:
    bg:black@sm      => @media (width>=52.125rem){.bg\:black\@sm{background-color:oklch(0% 0 none)}}
    bg:black@dark    => @media (prefers-color-scheme:dark){.bg\:black\@dark{background-color:oklch(0% 0 none)}}
    bg:black@dark@sm => @media (width>=52.125rem) (prefers-color-scheme:dark){.bg\:black\@dark\@sm{background-color:oklch(0% 0 none)}}
    

The single-variant classes are correct; the stacked one is missing and between the two media features. The same output is produced through @master/css.vite in a real build.

System Informations

OS: macOS (arm64)
Node.js / runtime: Bun 1.3.0
Package Manager: bun
@master/css: 2.0.0-rc.88 (also present in earlier 2.0 rc versions; source on the `rc` branch still joins media condition nodes with a space)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions