Skip to content

provideCssVarNamespacing() breaks styling of third-party libraries themed by global CSS variables #70064

Open

Description

@lacolaco

Which @angular/* package(s) are the source of the bug?

compiler

Is this a regression?

No

Description

CSS Variables namespacing, introduced in v22.1.0, rewrites custom property references inside a component's styles unconditionally, while global stylesheets are left alone by design. For a library published with partial compilation that rewrite does not happen when the library is built. It happens during the consuming application's build, with the consumer's namespace.

As a result, calling provideCssVarNamespacing() breaks any third-party library that exposes theming through custom properties defined in a global stylesheet, which is the usual way libraries do it. Angular Material ends up completely unstyled.

With provideCssVarNamespacing():

with provideCssVarNamespacing

Without:

without provideCssVarNamespacing

With @angular/material and the prebuilt azure-blue theme, a plain <button matButton="filled">:

computed with provideCssVarNamespacing() without
background-color rgba(0, 0, 0, 0) rgb(0, 92, 187)
color rgb(0, 0, 0) rgb(255, 255, 255)
border-radius 0px 9999px

The theme defines its tokens in the global stylesheet and the components reference them, so the two sides end up under different names:

$ grep -o -- "--mat-sys-[a-z-]*:" node_modules/@angular/material/prebuilt-themes/azure-blue.css | sort -u | wc -l
157

$ grep -o -- "var(--mat-" node_modules/@angular/material/fesm2022/button.mjs | wc -l
832
getComputedStyle(document.documentElement).getPropertyValue('--mat-sys-primary')       // "#005cbb"
getComputedStyle(document.documentElement).getPropertyValue('--myapp_mat-sys-primary') // ""

This is not specific to Material. A minimal library component with color: var(--brand) is published as

// dist/ui/fesm2022/ui.mjs
styles: ["#lib{color:var(--brand);border:2px solid var(--brand);...}"]

and comes out of the consuming application's build as

#lib[_ngcontent-%COMP%]{color:var(--%NS%brand)}

because PartialComponentLinkerVersion1.linkPartialDeclaration re-runs compileComponentFromMetadata, which applies namespaceCssVariables to styles unconditionally (packages/compiler/src/render3/view/compiler.ts).

--global-- is the designed opt-out, but it only helps in source you control. A library would have to spell every one of its tokens --global--… to defend itself against a decision its consumers make, and libraries published before v22.1 obviously cannot. A consuming application has no way to apply the opt-out to a dependency.

Steps to reproduce:

  1. ng new an application on 22.1
  2. npm i @angular/material @angular/cdk and add node_modules/@angular/material/prebuilt-themes/azure-blue.css to the styles array in angular.json
  3. put <button matButton="filled">Filled button</button> in the template
  4. add provideCssVarNamespacing() to appConfig.providers
  5. build and open the page

Please provide a link to a minimal reproduction of the bug

https://github.com/lacolaco/angular-cssvar-namespacing-library-repro

Please provide the exception or error you saw

No errors. The declarations are silently dropped.

Please provide the environment you discovered this bug in (run ng version)

Angular CLI       : 22.1.2
Angular           : 22.1.0
Node.js           : 22.22.3
Package Manager   : npm 10.9.8
Operating System  : darwin arm64

┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package                   │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build            │ 22.1.2            │ ^22.1.2           │
│ @angular/cdk              │ 22.1.0            │ ^22.1.0           │
│ @angular/cli              │ 22.1.2            │ ^22.1.2           │
│ @angular/common           │ 22.1.0            │ ^22.1.0           │
│ @angular/compiler         │ 22.1.0            │ ^22.1.0           │
│ @angular/compiler-cli     │ 22.1.0            │ ^22.1.0           │
│ @angular/core             │ 22.1.0            │ ^22.1.0           │
│ @angular/forms            │ 22.1.0            │ ^22.1.0           │
│ @angular/material         │ 22.1.0            │ ^22.1.0           │
│ @angular/platform-browser │ 22.1.0            │ ^22.1.0           │
│ @angular/router           │ 22.1.0            │ ^22.1.0           │
│ ng-packagr                │ 22.1.0            │ ^22.1.0           │
│ rxjs                      │ 7.8.2             │ ~7.8.0            │
│ typescript                │ 6.0.3             │ ~6.0.2            │
└───────────────────────────┴───────────────────┴───────────────────┘

Anything else?

For context on prior art, in case this reads as already known: the closest discussion I found is #67362, where the mismatch between a var() reference and a declaration living in a global stylesheet was raised, and --global-- was chosen as the answer.

What doesn't appear in either PR is the distribution dimension. Neither discussion mentions partial compilation, the linker, or npm packages, and the single sentence about libraries in the description is about using CssVarNamespacer for JS references rather than a library's own styles. --global-- only helps in source the author controls, which is what makes this case different from an application namespacing itself. I can't see the internal design doc linked from #67362, so this may well have been considered there.

If the answer here is that libraries need to adopt --global-- for their tokens, then adopting this feature is gated on an ecosystem-wide source change rather than on an application-level provider. Every library published before 22.1 is incompatible with any consumer that turns the flag on, there is no version constraint or diagnostic that surfaces it, and the failure mode is styles silently not applying. That seems like a larger coordination problem than a minor release can carry. --global- with a single hyphen is already slated to become an error in v23, so parts of this are on a major track anyway.

It also seems worth asking whether linked library styles should be namespaced at all. The namespace is application-scoped (it defaults to APP_ID), so rewriting a dependency's styles during the consuming application's link step is a choice rather than a consequence. Leaving them alone would still give the feature what it is after, an application's own variables no longer colliding with another application's on the same page, while keeping libraries working.

Activity

  1. added this to the needsTriage milestone on Aug 4, 2026
  2. added this to the needsTriage milestone on Aug 4, 2026
  3. deleted a comment from on Aug 4, 2026
  4. deleted a comment from on Aug 5, 2026
  5. deleted a comment from on Aug 5, 2026
  6. deleted a comment from on Aug 5, 2026
  7. deleted a comment from on Aug 5, 2026
  8. deleted a comment from on Aug 5, 2026
  9. deleted a comment from on Aug 5, 2026
  10. deleted a comment from on Aug 5, 2026
  11. added
    featureLabel used to distinguish feature request from other issues
    and removed on Aug 12, 2026
  12. modified the milestones: needsTriage, Backlog on Aug 12, 2026
  13. added a commit that references this issue on Sep 17, 2026
    d8a6de2
  14. splincode commented on Sep 21, 2026

    @splincode
    Contributor

    @JeanMeche Following the feedback on #70477, I agree that disabling namespacing for all partially compiled library styles is too broad.

    Would you be open to an application-level option to exclude specific CSS custom properties or prefixes from namespacing?

    For example, the API could look like this (just a proposal):

    provideCssVarNamespacing({
      namespace: 'myapp',
      exclude: {
        names: ['--brand'],
        prefixes: ['--mat-', '--mdc-'],
      },
    });

    This would let consumers preserve global theme tokens used by existing libraries without rebuilding those libraries. All other variables would continue to be namespaced, including variables in library components.

    The exclusion rules would need to apply consistently to component styles, template and host style bindings, and CssVarNamespacer, so that declarations, reads, and writes use the same names. The existing --global-- behavior and current provider calls would remain supported.

    Would this be a reasonable direction for a follow-up PR, or would you prefer a different approach? I can prepare a prototype with regression tests for global theming and isolation between applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: compilerIssues related to `ngc`, Angular's template compilercore: CSS encapsulationfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions