Skip to content

Migrate to css cascade layers#3229

Open
JLHwung wants to merge 5 commits into
babel:mainfrom
JLHwung:migrate-to-css-cascade-layers
Open

Migrate to css cascade layers#3229
JLHwung wants to merge 5 commits into
babel:mainfrom
JLHwung:migrate-to-css-cascade-layers

Conversation

@JLHwung

@JLHwung JLHwung commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

In this PR we enable the @docusaurus/plugin-css-cascade-layers plugin and apply a custom layer to all the stylesheets within src/css and static/css.

Note: although caniuse-lite is bumped, the build target of the current website is not changed because we use rspack, which uses browserslist-rs, which depends on the browserslist-data crate, the last release of which is already 6 months ago. Because browserslist-data is bundled in the rspack distributed binary, the defaults query has not been updated for some time longer than 6 months. So the current defaults does not support CSS cascade layers.

Anyway, adding a custom layer fixes all the specificity issues.

@netlify

netlify Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploy Preview for babel-v9 ready!

Name Link
🔨 Latest commit 05965a7
🔍 Latest deploy log https://app.netlify.com/projects/babel-v9/deploys/6a61017f4fdbda00087128d2
😎 Deploy Preview https://deploy-preview-3229--babel-v9.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploy Preview for babel-v8 ready!

Name Link
🔨 Latest commit 05965a7
🔍 Latest deploy log https://app.netlify.com/projects/babel-v8/deploys/6a61017f4b9c9d0008a8b5ac
😎 Deploy Preview https://deploy-preview-3229--babel-v8.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment on lines +183 to +184
"babel.custom": (filePath: string) =>
filePath.includes("/src/css") || filePath.includes("/static/css"),

@JLHwung JLHwung Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slorber It would be great if there is a single customCSS(filePath: string): boolean option which defines a docusaurus.custom layer overriding the other predefined layers. Of course users can turn to the layers option for more advanced usage such as resetting before infima, or defining multiple layers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand, wouldn't it be better to just add @layer babel.custom in your CSS files directly?

As you can see in the default config, I hesitated including a @layer docusaurus.website to all website CSS. I decided it was not a good idea by default because it may be confusing for users not familiar with that feature. In particular, !important works quite differently when using layers.

For that reason I think it's better to keep custom CSS unlayered and let users apply a layer themselves in their CSS file.

In the future, we'll likely hardcode a layer in our CSS packages, instead of having a PostCSS plugin apply it on top of unlayered CSS. We only built the plugin so that we could provide an opt-in future flag. So I think it's reasonable to just use regular @layer directives in your CSS files, because this is what we'll likely all use in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand, wouldn't it be better to just add @layer babel.custom in your CSS files directly?

This is a very good point. Yesterday I tried adding @layer when debugging on this PR but it didn't work. Now I have investigated and here is what I found:

To avoid the ambiguous defaults target interpreted by different tools, I have temporarily set the target to chrome 141, since any version later than chrome 142 will throw "Error: No browserslist config found to handle the 'browserslist' target.", which is probably an rspack issue as it misinterprets the browser version unknown error thrown from the browserslist-rs crate due to the outdated brwoserslist-data crate. I believe it will be somehow fixed in rspack 2 once they update the crate dependencies.

Here is the screenshot of the landing page in development mode.

image

It should have been:

image

So the specificity does not apply. And the reason is that the @layer babel.custom is not transpiled, but somehow @layer docusaurus.*, added by the postcss plugin, are transpiled, even though chrome 141 should support the cascade layer very well. The transpiled output always win over the native @layer.

I have pushed the experimental change to #3230.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: The production build of #3230 does looks good: https://deploy-preview-3230--babel-v8.netlify.app/, so it is an issue only reproducible on a development server.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh weird I don't know what is happening, if you can send a repro I'll take a look.

But we'll release v4.0 with Rspack v2 soon so it may not be worth it if your prod build works? I guess it remains a problem for local DX?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh weird I don't know what is happening, if you can send a repro I'll take a look.

I guess the imported CSS and the builtin CSS are handled by different tools (probably one is from JS and the other is from Rust?) You can check out the branch of #3230 and run yarn && yarn start.

But we'll release v4.0 with Rspack v2 soon so it may not be worth it if your prod build works? I guess it remains a problem for local DX?

Well, local DX is important, imo any visual difference between development & production should be avoided. For the time being I will keep the current approach, I will definitely give it a new try once the v4.0 with rspack 2 is released. Are there any alpha version that we can test? I have checked the npm tag and it seems there isn't an v4 alpha publicly available.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No alpha for now and our canary process broken due to migrating to pnpm recently, I need to fix it 😅

[
"@docusaurus/plugin-css-cascade-layers",
{
layers: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI you may want to add other layers here

If that helps, here's our default layers when you use the future flag:

// Not ideal to compute layers using "filePath.includes()"
// But this is mostly temporary until we add first-class layers everywhere
function layerFor(...params: string[]) {
  return (filePath: string) => {
    const posixFilePath = posixPath(filePath);
    return params.some((p) => posixFilePath.includes(p));
  };
}

// Object order matters, it defines the layer order
export const DEFAULT_LAYERS: PluginOptions['layers'] = {
  'docusaurus.infima': layerFor('node_modules/infima/dist'),
  'docusaurus.theme-common': layerFor(
    'packages/docusaurus-theme-common/lib',
    'node_modules/@docusaurus/theme-common/lib',
  ),
  'docusaurus.theme-classic': layerFor(
    'packages/docusaurus-theme-classic/lib',
    'node_modules/@docusaurus/theme-classic/lib',
  ),
  'docusaurus.core': layerFor(
    'packages/docusaurus/lib',
    'node_modules/@docusaurus/core/lib',
  ),
  'docusaurus.plugin-debug': layerFor(
    'packages/docusaurus-plugin-debug/lib',
    'node_modules/@docusaurus/plugin-debug/lib',
  ),
  'docusaurus.theme-mermaid': layerFor(
    'packages/docusaurus-theme-mermaid/lib',
    'node_modules/@docusaurus/theme-mermaid/lib',
  ),
  'docusaurus.theme-live-codeblock': layerFor(
    'packages/docusaurus-theme-live-codeblock/lib',
    'node_modules/@docusaurus/theme-live-codeblock/lib',
  ),
  'docusaurus.theme-search-algolia.docsearch': layerFor(
    'node_modules/@docsearch/css/dist',
  ),
  'docusaurus.theme-search-algolia': layerFor(
    'packages/docusaurus-theme-search-algolia/lib',
    'node_modules/@docusaurus/theme-search-algolia/lib',
  ),
  // docusaurus.website layer ? (declare it, even if empty?)
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I end up importing DEFAULT_LAYERS from @docusaurus/plugin-css-cascade-layers/lib/options. Would you consider exporting DEFAULT_LAYERS from the index.ts? It might benefit other users as well if they want to override layers but do not want to manipulate the default internal layers.

Comment on lines +183 to +184
"babel.custom": (filePath: string) =>
filePath.includes("/src/css") || filePath.includes("/static/css"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand, wouldn't it be better to just add @layer babel.custom in your CSS files directly?

As you can see in the default config, I hesitated including a @layer docusaurus.website to all website CSS. I decided it was not a good idea by default because it may be confusing for users not familiar with that feature. In particular, !important works quite differently when using layers.

For that reason I think it's better to keep custom CSS unlayered and let users apply a layer themselves in their CSS file.

In the future, we'll likely hardcode a layer in our CSS packages, instead of having a PostCSS plugin apply it on top of unlayered CSS. We only built the plugin so that we could provide an opt-in future flag. So I think it's reasonable to just use regular @layer directives in your CSS files, because this is what we'll likely all use in the future.

@slorber

slorber commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Note, for this kind of website upgrades, you might find this visual regression workflow useful: https://docusaurus.io/blog/upgrading-frontend-dependencies-with-confidence-using-visual-regression-testing

@JLHwung

JLHwung commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Note, for this kind of website upgrades, you might find this visual regression workflow useful: https://docusaurus.io/blog/upgrading-frontend-dependencies-with-confidence-using-visual-regression-testing

Thank you very much for the tip. I will explore if we can add an argos workflow.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants