Skip to main content

CKEditor5 scoped styles with PostCSS

The Drupal 10 update is moving to CKEditor 5. What’s different? It’s no longer an iframe! So how do we scope any custom styles we want to include?

by rikki.bochow /

First and foremost, if you haven’t read the Drupal docs about including custom styles, then please read that. It will give you the necessary context.

This article is specifically about a theme setup that already uses PostCSS (as opposed to Sass - this is much easier with Sass), has various component stylesheets, and includes some level of theme CSS to the CKEditor window to help improve the content editor experience.

Simply including the same styles used in CKEditor 4 to CKEditor 5 now bleed out into the admin theme, so we need to scope them to the .ck-content class.

Let's just say the theme's original ckeditor.css file looks something like this:

@import "./custom-properties.css";
@import "./base/base.css";
@import "./button/button.css";
@import "./layout/layout.css";

body {
  font-family: var(--font-family);
  padding: .5rem;
}

We use a couple of PostCSS plugins already, notably postcss-import and postcss-preset-env but in order to wrap everything here in a certain class, including everything we’re importing…

We need a new plugin

I tested a few class prefixing plugins, but the only one that suited my requirements was postcss-prefixwrap. With it installed, here’s our basic postcss.config.js:

module.exports = {
  plugins: [
    require("postcss-import"),
    ... other plugins
    require("postcss-prefixwrap")(".ck-content", {
      whitelist: ["docroot/themes/my_theme/src/ckeditor.css"],
      nested: "&",
      ignoredSelectors: [":root", /^\\.ck-(.+)$/],
    }),
  ],
}

We place it last; we add the class to use as the prefix .ck-content and specify our options;

  • whitelist: our ckeditor.css, so this plugin only applies to that file.
  • nested: because we use ampersand for nested selectors
  • ignoredSelectors: include the :root where we have some global custom properties and any class that starts with .ck- because it’s already scoped.

The only change we need to make to our ckeditor.css file is to change our body styles to live on .ck-content instead. Otherwise, they’ll come out at .ck-content body and be ignored.

Now every selector in our generated ckeditor.css file (that we didn’t ignore) is prefixed with .ck-content; thus all the styles are scoped to CKEditor 5 and no longer bleed into the admin theme.

A note on CSS Custom Properties

If you (like me) prefer to preserve your custom properties, you may find the need to scope them as well, to avoid clashes with custom properties the admin theme might have.

For this, I added one more PostCSS plugin postcss-variables-prefixer;

module.exports = {
  plugins: [
    require("postcss-import"),
    ... other plugins
    require("postcss-variables-prefixer")({
      prefix: "pnx-",
    }),
    require("postcss-prefixwrap")(".ck-content", {
      whitelist: ["docroot/themes/my_theme/src/ckeditor.css"],
      nested: "&",
      ignoredSelectors: [":root", /^\\.ck-(.+)$/],
    }),
  ],
}

This isn’t specific to the ckeditor.css file, but that’s ok with me; they can be unique everywhere.

Hopefully, this helps make the transition to CKEditor 5 a little smoother for your content editors when updating to Drupal 10.

Related Articles

Lightning talk: Custom CKEditor Widgets in Drupal 8

We're starting up our Lightning talks again during our weekly developer meetings here at PreviousNext. This week was about wiring up a straight forward plugin.js and extending CKEditorPluginBase to create a custom CKEditor widget in Drupal 8.

Watch the video for a run through of how this is done in Drupal 8.

Upgrading your site to Drupal 10

Upgrading from Drupal 9 to Drupal 10 requires preparation. These pointers will help you navigate the major version update and handle contrib and custom modules.