Article
2 comments

Develop SPFx web parts for different section designs using CSS

Over the last couple of days, I banged my head against the wall. I hate when things don’t work as expected. On the other hand, I love challenges.

The thing we are talking about the documentation on “Supporting section background“. To be clear, the documentation is correct. It works as described. The issue I and many others have with this documentation it does not apply to your project.

If you use CSS-in-JS, you are fine and off the hook, but it might impact the performance in a negative way.

An inline style definition is also a good option, but isn’t there a better way?

Before we begin

I like to dissect current documentation by time writing. I do this not to show that it is wrong but tell the story what is going on there.

The approach does not work in IE11. Thanks for asking. It might work by using polyfills. I haven’t tested this case.

The approach, written using a no-framework web part, works for all frameworks, cause it does not take any dependency on your framework. Instead, it is purely CSS.

Step 1: Getting section background themes running

Like mentioned in the documentation, you have to enable them variants, add “supportThemeVariants” and set it to “true” on the web part manifest.

{
  // ...

  "supportsThemeVariants": true,

  "version": "*",
  "manifestVersion": 2,

  "requiresCustomScript": false,

  "preconfiguredEntries": [{
    // ...
  }]
}

Step 2: Add support for theme variants in your code

The first step requires some development in the web part itself. Like I said in this case, and for the necessary implementation, it does not matter if it is ReactJS, No Framework, VueJS, you name it.

Import some new references on top of your web part file.

/** Step 1: Add references to the theme provider and all related objects **/
import {
  ThemeProvider,
  ThemeChangedEventArgs,
  IReadonlyTheme,
  ISemanticColors
} from '@microsoft/sp-component-base';

...
export default class SectionBackgroundWebPart extends BaseClientSideWebPart<ISectionBackgroundWebPartProps> {

/*** Step 2: Add these private properties for theme provider and theme variant ***/
  private _themeProvider: ThemeProvider;
  private _themeVariant: IReadonlyTheme | undefined;

/*** Step 3: Create on init method ***/
protected onInit(): Promise<void> {

  return super.onInit();

}

The first step is to add a reference to the ThemeProvider and all related objects. Inside the web part (Step 2) we also need to have private properties for _themeProvider and ‘_themeVariant’.
‘_themeProvider’ allows us to access the theming information of the current web part section.
‘_themeVariant’ store all property the relates to the current theme applied to the page section.

For the last step (Step 3), we need to have to create an on init function.

Request the current theme of the section

Now with the base set, we can request the current theme, and we extend the onInit method.

protected onInit(): Promise<void> {

  // Consume the new ThemeProvider service
  this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);

  // If it exists, get the theme variant
  this._themeVariant = this._themeProvider.tryGetTheme();

  return super.onInit();
}

To get the theme, we request the ‘ThemeProvider’ service, which allows us further request the current theme of the section, of course, we assign it to the ‘_themeVariant’ object.

Now we are one step further to enable the correct theming on our web part.

The Theme Object

TryGetTheme() returns an object that contains the following information.

Theme Object returned by SPFx

  • effect – Visual effects like the elevations and roundness of corners and buttons
  • font – all font combinations defined in Fluent UI (tiny, … to xxLarge Plus)
  • isInverted – describes if it uses a dark theme when true.
  • palette – the colour palette of the theme
  • rtl – Right-to-left aka the CSS text flow of a document
  • semanticColors – for example all details how a button is a style, how a text box looks.
  • spacing – some helper for the spacing of an element

The most crucial property is semanticColors which we will use style the web part.

JS to CSS transition

The current documentation shows further some example of how to react to these changes and how to use it using inline style, but we pause here for a moment.

Now that we have a JavaScript object, we cannot use in CSS. Well we can, we need to convert JavaScript back to CSS.

CSS has a concept named CSS variable, which is pretty smart.

CSS Variables

CSS variables in IE11 do not work, but other than that they have big support in all common browsers.

Browser support for CSS variables

CSS variable can be define like this:

/* Root of the HTML document */
:root{
    /* define variable and set it to the named color yellow */
    --myVar: yellow;
    /* default border */
    --myDefaultBorder: 1px black solid;
    /* default padding */
    --myDefaultPadding: 1em;
}

Normally you can define them on the root of an HTML document, or HTML or BODY tag. In your style sheet you later consume it then like this:

.myDiv{
  background-color: var(--myVar);
  border: var(--myDefaultBorder);
  padding: var(--myDefaultPadding);
}

So you can request the variable using the “function” var and pass in the requested colour name. Here is a small example of how this works.

See the Pen CSS Variable Basics by Stefan Bauer (@StfBauer) on CodePen.14928

The benefits of CSS variables are the come with some goodies.

CSS Variables are scoped

There is nothing as crucial as scope everything we do to our web part, and this is where CSS variables help us in a big way. Take a look at the following example.

See the Pen LYZxbYP by Stefan Bauer (@StfBauer) on CodePen.14928

First I define ‘–myBackgroundColor’ and ‘–myTextColor’ on a root level and use it on the body tag. It should affect the whole document, and it does.

Later I then redefine on the ‘.my-scoped-div’ different values for ‘–myBackgroundColor’ and ‘–myTextColor’ and use it in there as well.

As you see in this example, the changed variable values only affect the div element but not the code that comes after the div, which makes it the perfect candidate to be used in something like a web part.

CSS Variable does not care when they get assigned

Usually, you would think that the following code fails.

body{
  color: var(--myTextColor);
  background-color: var(--myBackgroundColor);
    font-family: sans-serif;
  font-size: 1.5em;
}

.my-scoped-div{
  /** redefine variables **/
  --myTextColor: black;
  --myBackgroundColor: white;
  /** use the redefined colors **/
  color: var(--myTextColor);
  background-color: var(--myBackgroundColor);
}

/* Root of the HTML document */
:root{
  --myBackgroundColor: black;
    --myTextColor: white;
}

The first use was on the body but without any values. JavaScript would, in this case, raise an exception. CSS does not know anything like this exception or error handling not even with variables involved. It shows the correct styling, or it doesn’t, it works, or it doesn’t. Simple as that.

On the other hand, browsers are pretty smart to know what to apply to certain elements. Also to learn where to use it. See the follwing example:

See the Pen Reversed scoping by Stefan Bauer (@StfBauer) on CodePen.14928

Suppose you update for example a root level CSS variable later with javascript. The browser detects the change and re-colour all elements automatically. Later more on that.

Overall CSS variable is smart and the right tool to pick for section background aware content.

Assign CSS Variables to the web part scope

So we have already the JavaScript theme variant object from before and need to turn this into CSS variables. Nothing more straightforward as that.

protected onInit(): Promise<void> {

  // Consume the new ThemeProvider service
  this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);

  // If it exists, get the theme variant
  this._themeVariant = this._themeProvider.tryGetTheme();

    // If there is a theme variant
  if (this._themeVariant) {

        // we set transfer semanticColors into CSS variables
    this.setCSSVariables(this._themeVariant.semanticColors);

  }

  return super.onInit();

}

If we have a ‘_themeVariant’ we call a method name ‘setCSSVaraible’ that adds the support for CSS variable on the web part only.

private setCSSVariables(theming: any) {

    // request all key defined in theming
  let themingKeys = Object.keys(theming);
    // if we have the key
  if (themingKeys !== null) {
        // loop over it
    themingKeys.forEach(key => {
            // add CSS variable to style property of the web part
      this.domElement.style.setProperty(`--${key}`, theming[key])

    });

  }

}

So we pass to this method the theme properties for the semantic colour slots. The first thing, cause we don’t know the key of this object is that we get them from the item directly using ‘Object.keys’.

The rest then gets all key/value pairs and assigns it to the root styles of the web part. ‘this.domElement’ is the first element that belongs to our code and scope of the web part.

Using of CSS Variables in the web part CSS

So like in the examples before we can request a CSS variable for the ‘primaryButtonBackground’ but also for the ‘primaryButtonText’.

.sectionBackground {
  color: inherit;

  .container {
    max-width: 700px;
    margin: 0px auto;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
    background-color: var(--primaryButtonBackground);
    color: var(--primaryButtonText);
  }

  .row {
    @include ms-Grid-row;
    padding: 20px;
  }
  ...

Remember, CSS does not care when the variable gets to define, and even if your web part is already on the page, we might assign the variable through the code before afterwards. It happens that quick, the user won’t even notice the assignment.

The result of this CSS variable looks, to be honest excellent.

Proper style web parts with CSS variables

The first web part is on a default styled zone, the second web part uses the darkest possible background, and both are style according to their section.

React accordingly to style changes

User can pick and choose how their section should look like, which is a good thing from a UI / UX perspective. Through the ‘onInit’ call, we just defined how the web part should look like when someone loads the page the first time, but we are not able to react to design changes.

For that, we need to register like in the original documentation defined an event handler that manages the style changes.

The first change that is required is in the ‘onInit’ method. The theme provider allows registering a ‘themeChangedEvent’.

protected onInit(): Promise<void> {

   //... the other code and then
  // Register a handler to be notified if the theme variant changes
  this._themeProvider.themeChangedEvent.add(this, this._handleThemeChangedEvent);

  return super.onInit();

}

The ‘_handleThemeChangedEvent’ always get called when the user picks another theme for this section. The event handler code is tiny.

/**
 * Update the current theme variant reference and re-render.
 *
 * @param args The new theme
 */
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {

  // assign new _themeVariant
  this._themeVariant = args.theme;
  this.setCSSVariables(this._themeVariant.semanticColors);
  // this.render();
}

The ‘ThemeChangeEventArgs’ contains the theme pick by the user, so the first thing is to update the private property ‘_themeVariant’, and finally, we need to update the CSS variables as well.

The changed values will immediately get applied to the rest of the web part, and we see the change design.

The render method is commented out and originates from the original documentation. We don’t need it because the CSS variable changes are enough to force the browser to redraw the web part instead of re-render, which is also faster than ReactJS ever will be.

Selection of different styles for the zone

The result is that we can flip the coolers of the web part and have them style right defined only be our CSS.

The same event handler is also responsible if the overall theme changes for our web part not matter in which section they are.

Change of the overall page theme

The whole page and section use precisely the right colours, even for our custom code. Here is a little demo.

Of course, there is more to this, like how to use CSS variables in SASS, what slots are available, what are the names and how to use it more efficient by defining common patterns.

At least we have now a pretty solid framework for our web parts to play well together in design matters of our customer needs.

Conclusion

To get the right approach to tackle JavaScript theme slots in CSS was quite a piece of work. Finally, there is a working solution.

Sometimes a solution to a problem is right before your eyes, but I haven’t thought about the CSS variable option anymore. The presented solution is also independent of the framework you use for your code.

For the future, I guess this shown approach is also a viable option actually to have the section aware of all the CSS variables.

Luckily Microsoft announced to retire the support for IE 11 soon, but I guess this basic set of CSS variable feature could tackle by ploy fills.

Thanks to my friend Julie Turner. I had the enlightenment last night. I love when Developer and Designer work together in that way and not “I design, you develop”. The common battleground is problem-solving.

You will find the complete SPFx source code on github.

2 Comments

  1. The stupid thing is not this workaround. The stupid thing is that you have to make this workaround. Background colors for custom web parts should be transparent by default and if you want to set another color then you should be able to do it. A lot of work just to be able to make something work as expected. Appreciate the effort though! Good work.

    Reply

Leave a Reply

Required fields are marked *.


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