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 div
the 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-links
classes. 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 :local
allows 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.
Originally published at Stefan Bauer — N8D.