by Karen Stevenson

Styling the WYSIWYG Editor in Drupal 8

Drupal 8 ships with a built-in WYSIWG editor called CKEditor. It’s great to have it included in core, but I had some questions about how to control the styling. In particular, I wanted the styling in the editor to look like my front-end theme, even though I use an administration theme for the node form. I spent many hours trying to find the answer, but it turned out to be simple if a little confusing.

In my example, I have a front-end theme called “Custom Theme” that extends the Bootstrap theme. I use core’s “Seven” theme as an administration theme, and I checked the box to use the administration theme for my node forms. 

My front end theme adds custom fonts to Bootstrap and uses a larger than normal font, so it’s distinctively different than the standard styling that comes with the WYSIWYG editor. 

Front End Styling

Front end styling

WYSIWYG Styling

Out of the box, the styling in the editor looks very different than my front-end theme. The font family and line height are wrong, and the font size is too small.

Back end, before the changes

It turns out there are two ways to alter the styling in the WYSIWYG editor, adding some information to the default theme’s info.yml file, or implementing HOOK_ckeditor_css_alter() in either a module or in the theme. The kicker is that the info changes go in the FRONT END theme, even though I’m using an admin theme on the node form.

I added the following information to my default theme info file, custom_theme.info.yml. The font-family.css and style.css files are the front-end theme CSS files that I want to pass into the WYSIWYG editor. Even if I select the option to use the front-end theme for the node form, the CSS from that theme will not make it into the WYSIWYG editor without making this change, so this is necessary whether or not you use an admin theme on the node form!  

name: "Custom Theme"
description: A subtheme of Bootstrap theme for Drupal 8.
type: theme
core: 8.x
base theme: bootstrap
ckeditor_stylesheets:
  - https://fonts.googleapis.com/css?family=Open+Sans
  - css/font-family.css
  - css/style.css
libraries:
  ...

WYSIWYG Styling

After this change, the font styles in the WYSIWYG editor match the text in the primary theme.

Back end, after the change

When CKEditor builds the editor iframe, it checks to see which theme is the default theme, then looks to see if that theme has values in the info.yml file for ckeditor_stylesheets. If it finds anything, it adds those CSS files to the iframe. Relative CSS file URLs are assumed to be files in the front-end theme’s directory, or you can use absolute URLs to other files.

The contributed Bootstrap module does not implement ckeditor_stylesheets, so I had to create a sub-theme to take advantage of this. I always create a sub-theme anyway, to add in the little tweaks I want to make. In this case, my sub-theme also uses a Google font instead of the default font, and I can also pass that font into the WYSIWYG editor.

TaDa!

That was easy to do, but it took me quite a while to understand how it worked. So I decided to post it here in case anyone else is as confused as I was.

More Information

To debug this further and understand how to impact the styling inside the WYSIWYG editor, you can refer to the relevant code from two files in core, ckeditor.module:  

/**
 * Retrieves the default theme's CKEditor stylesheets.
 *
 * Themes may specify iframe-specific CSS files for use with CKEditor by
 * including a "ckeditor_stylesheets" key in their .info.yml file.
 *
 * @code
 * ckeditor_stylesheets:
 *   - css/ckeditor-iframe.css
 * @endcode
 */
function _ckeditor_theme_css($theme = NULL) {
  $css = [];
  if (!isset($theme)) {
    $theme = \Drupal::config('system.theme')->get('default');
  }
  if (isset($theme) && $theme_path = drupal_get_path('theme', $theme)) {
    $info = system_get_info('theme', $theme);
    if (isset($info['ckeditor_stylesheets'])) {
      $css = $info['ckeditor_stylesheets'];
      foreach ($css as $key => $url) {
        if (UrlHelper::isExternal($url)) {
          $css[$key] = $url;
        }
        else {
          $css[$key] = $theme_path . '/' . $url;
        }
      }
    }
    if (isset($info['base theme'])) {
      $css = array_merge(_ckeditor_theme_css($info['base theme']), $css);
    }
  }
  return $css;
}

and Plugin/Editor/CKEditor.php:  

 /**
   * Builds the "contentsCss" configuration part of the CKEditor JS settings.
   *
   * @see getJSSettings()
   *
   * @param \Drupal\editor\Entity\Editor $editor
   *   A configured text editor object.
   * @return array
   *   An array containing the "contentsCss" configuration.
   */
  public function buildContentsCssJSSetting(Editor $editor) {
    $css = [
      drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
      drupal_get_path('module', 'system') . '/css/components/align.module.css',
    ];
    $this->moduleHandler->alter('ckeditor_css', $css, $editor);
    // Get a list of all enabled plugins' iframe instance CSS files.
    $plugins_css = array_reduce($this->ckeditorPluginManager->getCssFiles($editor), function($result, $item) {
      return array_merge($result, array_values($item));
    }, []);
    $css = array_merge($css, $plugins_css);
    $css = array_merge($css, _ckeditor_theme_css());
    $css = array_map('file_create_url', $css);
    $css = array_map('file_url_transform_relative', $css);
    return array_values($css);
  }

Published
in

newsletter-bot