Article
3 comments

How to better control CSS class naming in SPFx

CSS is currently not capable of scoping the design only to a component on a web page. It is just possible through different class names for elements on the page. To avoid the inference of same style sheet classes on the same page, SPFx post-fix every class name used in the web parts CSS files. There are also hidden gems that allow you to change this behaviour dynamically as required and sometime the class names shouldn’t be renamed at all cost. Enough about the theory lets take a closer more detailed look.

Post-fixing CSS Classes

To explain this in more detail, I render a simple div inside the web part container.

  public render(): void {
    this.domElement.innerHTML = `
      <div class="helloSpfx">Hello SPFX</div>
    `;
  }

This code will just render out a simple ‘Hello World’ text.

Base web part without style definitions.

Let us apply some style sheet definition and use the class name already included in the HTML source.

.helloSPFx{
    background-color: $ms-color-themeDark;
    color: $ms-color-white;
    padding: 1em;
}

These style definitions should theoretically set the background colour, text colour and add padding to the defined div. The web part still looks unchanged because the specified CSS class cannot be used directly in the web part.

Base web part without fixed style definition

The reason is that the class name got post-fixed with a random string. Also, a JavaScript object was generated, and this references to the original class name. To assign the correct class name to the divthe code of the web part needs to be changed slightly.

  public render(): void {
    this.domElement.innerHTML = `
      <div class="${styles.helloSPFx}">Hello SPFX</div>
    `;
  }

The definition ${styles.helloSPFx} in the TypeScript/JavaScript code is a so-called template literal. Long story kept short. This reference to the object property styles.helloSPFx get automatically replaced with the compiled style class name. After this change, the web part renders properly.

Base web part using template literal for class names

The compile style sheet corresponding to the web part looks like this.

.helloSPFx_785aef7e {
    background-color: "[theme:themeDark, default: #005a9e]";
    color: "[theme:white, default: #ffffff]";
    padding: 1em
}

By generating this unique class name out of the defined generic class name, SPFx makes sure that the CSS added to the page only affects the web part and not the rest of the page. A tool named CSS Modules provides this functionality for the web part code.

Move classes to a global scope

As mentioned before through CSS Modules the style gets local scoped to the web part only but also brings some additional benefits. It allows you to switch the context from a web part scoped to global scoped context. Made possible by a pseudo-class :global primarily for the use of CSS Modules. The W3C CSS specification nor SASS are aware of this pseudo-class. In regular CSS this will be ignored by the browser and most likely not applied to any class at all.

:global{
  .my-links{
    color: yellow;
    text-decoration: none;
    &:hover{
      text-decoration: underline;
    }
  }
}

.helloSPFx{
  background-color: $ms-color-themeDark;
  color: $ms-color-white;
  padding: 1em;
}

Only CSS Modules knows about it and let the classes inside untouched. Everything inside the global scope won’t get post-fixing class names or variables in the style object. The resulting CSS looks like this.

.my-links {
    color: #ff0;
    text-decoration: none
}

.my-links:hover {
    color: #ff0;
    text-decoration: underline
}

.helloSPFx_8a6546f1 {
    background-color: "[theme:themeDark, default: #005a9e]";
    color: "[theme:white, default: #ffffff]";
    padding: 1em
}

This has some potential pitfall because any other web part that uses the .my-links class too gets potentially overwritten by this style definition also. It is definitely an unwanted behaviour. My recommendation is not to use the :global pseudo-class at the top level of any SPFx style sheet. It makes more sense to use it inside a web part container CSS class.

.helloSPFx{
  background-color: $ms-color-themeDark;
  color: $ms-color-white;
  padding: 1em;
  :global{
    .my-links{
      color: yellow;
      text-decoration: none;
      &:hover{
        text-decoration: underline;
      }
    }
  }
}

With this slightly adopted style definition, you have two benefits. The overall web part container .helloSPFx gets a random string appended and the .my-links classes won’t get post-fixed because they only exist inside the web part container and inside a :global scope.

.helloSPFx_29f8847b {
    background-color: "[theme:themeDark, default: #005a9e]";
    color: "[theme:white, default: #ffffff]";
    padding: 1em
}

.helloSPFx_29f8847b .my-links {
    color: #ff0;
    text-decoration: none
}

.helloSPFx_29f8847b .my-links:hover {
    color: #ff0;
    text-decoration: underline
}

The compiled code for the .my-links the class gets an additional post-fixed Style Sheet Class up front. This way you can make sure that the .my-links the definition only affects all instances inside the web part, but not existing elements outside the web part.

Can I switch from a global context to a local web part scoped again?

CSS module provides another pseudo-class for that and all the code wrapped inside a :local pseudo-class switch back to the SPFx default behaviour.

.helloSPFx{
  background-color: $ms-color-themeDark;
  color: $ms-color-white;
  padding: 1em;
  // switch to global scope
  :global{
    .my-links{
      color: yellow;
      text-decoration: none;
      &:hover{
        color: yellow;
        text-decoration: underline;
      }
      // switch back to local scope
      :local{
          .description{
            color: white;
            font-size: 12px;
          }
      }
    }
  }
}

The code above switches first the context from the local scope (implicit because it is the default behaviour) to the global containing the .my-linksclasses. It behaves just like shown before. To make explicitly the .description class post-fixed by CSS Modules the :local definition switch the context back to the default behaviour.

.helloSPFx_1a880917 {
    background-color: "[theme:themeDark, default: #005a9e]";
    color: "[theme:white, default: #ffffff]";
    padding: 1em
}

.helloSPFx_1a880917 .my-links {
    color: #ff0;
    text-decoration: none
}

.helloSPFx_1a880917 .my-links:hover {
    color: #ff0;
    text-decoration: underline
}

.helloSPFx_1a880917 .my-links .description_1a880917 {
    color: #fff;
    font-size: 12px
}

The last style definition in the code above shows precisely how this mingle with the scopes happen after the compilation of the styles. The class .helloSPFx_1a880917 gets post-fixed, after that the .my-links won’t get the name and stays untouched. Through the parent class definition, it is safe to use at a quasi-local scope. Through switching the .description back to a local scope, this class gets postfixed again and is also available as a variable on the style object.

When, why and how?

The :global pseudo-class might be the most important for web parts because it allows you to embed any style definition directly inside the web part. Whenever you use an external style sheet provided by a third party vendor that doesn’t seem to match with SPFx you can embed it this way. The class names of this component don’t need a rewrite. It will boost your productivity and helps you especially when migration to the SharePoint Framework.

My recommendation is to use it just inside the web part container. It makes the additional classes safe from interference to the rest of the page and the overall design. Another benefit of this method is that it allows to access the container classes around your web part and optimise the design especially for that. This way the web part can take the embedded container into account. The same web part can behave differently embedded on a full page, a two column layout or any other provided by the new page model. Plus :localallows you to switch the context to be web part focused dynamically wherever needed.

In an upcoming post, I will show how to make efficient use and take the web part zone container into account on how the resulting web part looks and behaves.


Also published on Medium.

3 Comments

  1. Another great post Stefan. Thanks for posting.

    I personally can’t wait until all browsers support the shadow-root spec of web components. That will finally truly allow for per-component scooping of styles and we can stop using all of these hacks and work-arounds. I personally will be moving all of my future open source widgets to be web components, since they will be Framework/library agnostic and thus truly global and reusable. Shadow-root support across browsers is gaining momentum too… 🙂

    Reply

  2. Stefan, have you had success using this :global scope on spfx extensions? I’ve been able to successfully use these within the context of a spfx web-part, but not with the SCSS for extensions.

    Reply

Leave a Reply

Required fields are marked *.


This site uses Akismet to reduce spam. Learn how your comment data is processed.