Skip to content

scope option prefixes @property rules and produces invalid CSS (preset-wind4 / properties layer) #5129

@reeucq

Description

@reeucq

UnoCSS version

66.6.3

Describe the bug

Bug description

When using UnoCSS generate() with the scope option (e.g. scope: '.cms-scope') together with @unocss/preset-wind4, the generated CSS incorrectly prefixes Houdini @property at-rules with the scope selector, producing invalid CSS like:

.cms-scope @property --un-text-opacity { ... }

@property must be a top-level at-rule, so .cms-scope @property ... is invalid and the browser drops the entire rule. This causes preset-wind4 utilities that rely on the registered custom properties (e.g. opacity defaults like --un-text-opacity: 100%) to behave incorrectly—colors/gradients appear “washed out” because color-mix() inputs end up missing expected variables/defaults.

preset-wind4 explicitly uses @property registrations in its “properties” layer.
@property is a CSS at-rule and must be emitted as an at-rule (not combined with a selector).

Expected behavior

When scope is provided:

  • Normal selectors should be scoped (e.g. .cms-scope .text-red-500 { ... })
  • At-rules like @property ... should remain valid top-level rules (i.e. not be prefixed by .cms-scope)

Actual behavior

@property rules are prefixed by .cms-scope, becoming invalid and ignored by the browser.

Reproduction

Minimal script (Node)

  1. Install:
npm i @unocss/core@66.6.3 @unocss/preset-wind4@66.6.3
  1. Create repro.mjs:
import { createGenerator } from '@unocss/core'
import presetWind4 from '@unocss/preset-wind4'

const uno = await createGenerator({
  presets: [presetWind4()],
})

const html = `<div class="text-blue-600/50 bg-blue-100">hello</div>`

// ✅ without scope: @property is emitted correctly as a top-level at-rule
const noScope = await uno.generate(html, { minify: true, preflights: false })
console.log('--- no scope ---')
console.log(noScope.css.slice(0, 300))

// ❌ with scope: output contains invalid ".cms-scope @property ..."
const scoped = await uno.generate(html, { minify: true, preflights: false, scope: '.cms-scope' })
console.log('--- scoped ---')
console.log(scoped.css.slice(0, 300))
  1. Run:
node repro.mjs

System Info

  • UnoCSS: 66.6.3
  • @unocss/preset-wind4: 66.6.3
  • Node: (fill in, e.g. v20.x)
  • Package manager: (npm/pnpm/yarn + version)
  • OS: (macOS/Linux/Windows + version)
  • Browser(s) affected: Any (invalid CSS rule per spec; tested in (Chrome/Firefox/Safari + version))

Validations

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