Introduction

A while ago, I wrote an article describing how you can inject a custom CSS stylesheet on SharePoint modern pages using an SPFx application extension. The code sample is now part of the SharePoint SP-Dev-Fx-Extensions repository on GitHub.

Since the article, I have been getting tons of e-mails asking all sorts of questions about the solution.

Since SPFx 1.6 was released, I took the opportunity to upgrade the solution to the latest and greatest version of the toolset. You can find the latest code on GitHub, or download the latest SharePoint package.

In this post, I'll (hopefully) answer some questions about how to use it.

Be smart!

You should really use the out-of-the-box customizations features before you resort to injecting custom CSS.

There are a few reasons why you shouldn't inject your own CSS:

  • Microsoft can change the HTML layout, element ids, or CSS classes at any time -- thus breaking your custom CSS.
  • Your customizations may hide or otherwise disable (or interfere with) new features Microsoft may introduce in the future.
  • Your customizations will be unsupported by Microsoft. Don't try to open support tickets (unless you're willing to pay for them, I guess).
  • Although the solution uses SPFx application extensions, the SharePoint/SPFx team will not be able to support your customizations.

That being said, there are valid reasons why you may need to inject custom CSS. Vesa and his team had to give careful consideration before accepting my solution as a code sample.

Here are some sample valid reasons for injecting your own CSS:

  • To meet your corporate branding guidelines (but consider using a custom theme first).
  • To solve unique accessibility requirements (such as importing a custom font to help with cognitive disabilities, such as dyslexia).
  • To solve an showstopping issue (you know, to shut up one of those bosses/clients that say distasteful stuff like: "we'll only use SharePoint Online/Office 365 **if** Microsoft fixes the ugly look and feel and [insert bad idea here]".
  • For limited-time customizations (like fixing an issue while you're waiting for Microsoft to fix it, or making it snow on Christmas Eve).

Ok, maybe the last one isn't such a valid reason.

Steps to inject your own CSS

  1. Download the code and build the solution, or download the pre-built solution.
  2. Go to your tenant's app catalog (usually at https://[yourtenant].sharepoint.com//sites/Apps/AppCatalog/Forms/AllItems.aspx)
  3. Drag and drop the sppkg file from step 1 onto the library (or click Upload and select the file).
  4. When it prompts you Do you trust react-application-injectcss-client-side-solution? select Deploy (provided, of course, that you trust the solution!). If you want the extension to be available on all sites. check Make this solution available to all sites in the organization before you select Deploy. You may have to check-in the file if it is checked-out.
  5. It may take a while for the application extension to show up (I once had to wait overnight for the magical SharePoint elves to deploy the extension).
  6. Meanwhile, create your own CSS file to include your customizations. Name it custom.css (don't worry, I'll show you how to change that default name later).
  7. Upload your custom.css to your root style library (located at https://[yourtenant].sharepoint.com/Style%20Library/Forms/AllItems.aspx). If you have versioning enabled on that library, you may have to check-in the file so that other people can see your custom css. Again, don't worry, I'll show you how to use a different location later.
  8. Your custom CSS should show up!

The most important part of this is that the custom.css is NOT part of the SPFx solution! It is a separate file stored in a publicly-accessible location.

Frequently Asked Questions

It doesn't work!

.ms-compositeHeader-topWrapper {
    margin-top: 5px !Important;
    background-color: green;
}

if the above CSS works (by adding an ugly green bar at the top of the page), it means that the extension works and is able to load the custom CSS. Verify your CSS.

  • Using your browser's developer extensions, check to see if you're getting any kind of HTTP 404 (Not Found) message. If you're getting a 404, your CSS is named wrong or in the wrong place.

It works, but only for me (and other administrators)

  • You probably didn't check-in and publish your CSS.

The CSS doesn't get packaged in my solution!

Why doesn't the CSS get packaged in the solution?

  • I wanted to avoid having to re-deploy the solution every time I wanted to change the CSS.
  • I wanted non-developers to be able to use the application extension.

How do I change the name of the CSS?

  1. Rename your CSS to whatever you want
  2. Upload it to your root style library
  3. Go to your Tenant Wide Extensions (located at: https://[yourtenant].sharepoint.com/sites/Apps/Lists/TenantWideExtensions/AllItems.aspx
  4. Select the InjectCssApplicationCustomizer from the list.
  5. Select Edit Item from the ribbon.
  6. In the edit form, change the value in Component Properties to use your new CSS name and hit Save. For example, if you renamed your CSS to contoso.css, you'd change the entry to be:
{
    "cssurl":"/Style%20Library/<strong>contoso</strong>.css"
}

How do I place the CSS somewhere else than the root style library?

  1. Place your CSS in a publicly accessible library
  2. Go to your Tenant Wide Extensions (located at: https://[yourtenant].sharepoint.com/sites/Apps/Lists/TenantWideExtensions/AllItems.aspx
  3. Select the InjectCssApplicationCustomizer from the list.
  4. Select Edit Item from the ribbon.
  5. In the edit form, change the value in Component Properties to use your new CSS name and hit Save. For example, if you created a new style library called InjectCss in the root site, you'd change the entry to be:
{
    "cssurl":"/<strong>InjectCSS</strong>/custom.css"
}

How do I place the CSS in a CDN?

  • I didn't test it. but in theory, you could follow the instructions above, but change the cssurl value to include the full path to your CDN.

How do I do [insert your own customization] by injecting CSS?

I'm not a CSS expert, but here's how I usually do my customizations:

  1. Using your browser, surf to a modern page.
  2. Launch your browser's developer toolbar (CTRL-Shift-I for Chrome, F12 for Edge)
  3. Use the element selector (CTRL-Shift-C for Chrome, Ctrl-B for Edge) select the element you want to customize.
  4. From the Styles pane in the developer tools, select + (New Style Rule) and enter the styles you want to change. Both Chrome and Edge has autocomplete capabilities, so feel free to explore. Don't worry, it only changes your current page, and does not gets saved if you refresh the page or load a new page.
  5. If you find that your styles are getting overwritten as soon as you apply them, try adding an !important instruction at the end of your style. (CSS experts are cringing as they read this).
  6. Once your element looks the way you want it, copy the rule to your custom CSS and upload the CSS wherever your placed it in your tenant.

Did I forget anything?

If there is anything I forgot, please let me know in the comments. I'll try to answer every question... eventually.

Author

Independent consultant. Certified SCRUM Master. SharePoint, Office 365 and Dynamics 365 are his favourite toys.

64 Comments

  1. This is the error I’m getting on the Chrome console:

    x Refused to apply style from ‘https://<>.sharepoint.com/Style%20Library/custom.css’ because its MIME type (”) is not a supported stylesheet MIME type, and strict MIME checking is enabled.

    I changed the tenant name in this thread for safety.

    I followed the instructions exactly. I redid it three times. No love. What should I do?

    I have the custom.css file in the right location. I haven’t changed a thing. In Style Library/Forms/AllItems.aspx.
    I uploaded the solution file. /sites/apps/AppCatalog/Forms/AllItems.aspx

    What am I doing wrong?

    • Hugo Bernier Reply

      Looks like it is time for me to re-write the extension to inject the CSS directly in the page instead of using a linked CSS.

      I assume that you got this error with Chrome? Can you try with a different browser to see if the behavior is consistent?

      • Jose Nava

        Hey Hugo I’m getting the same error using the code on SharePoint online modern experience.. x Refused to apply style from ‘https://.sharepoint.com/Style%20Library/custom.css’ because its MIME type (”) is not a supported stylesheet MIME type, and strict MIME checking is enabled.

      • Aaron Stimpson

        Chrome Windows – same
        Chrome Mac – same
        Safari – same
        Firefox Mac – same

  2. Hugo, I deployed the extension in test and production – in Chrome developer tools it shows my CSS file in the Style Library but the styles are not applied to the modern page. Only when I edit the CSS file in developer tools does my styles magickly appear. The order of operation for the style sheets is SuiteNav.css comes first then my CSS file. I changed the code head.insertAdjacentElement(“afterEnd”, customStyle); but still the same behavior. I’m working with SharePoint 2019 on premise. This is driving me nuts and I’m at my wits end, any ideas? Thanks Hugo!

  3. I’m trying to add the pre-build solution on a SharePoint 2019 environment – but I ran into this error

    There were errors when validating the App manifest.: Xml Validation Exception: ‘The ‘IsDomainIsolated’ attribute is not declared.’ on line ‘1’, position ‘322’.

    Anyone else had this error?
    Thanks

    • Hugo Bernier Reply

      Andreea,

      The code sample was written for SharePoint Online. Are you trying to install it on a SharePoint 2019 server by any chance? Is so, the project would need to be re-written for SPFx 1.4. Not too difficult to do.

      Let me know and I can help get you started.

      • Hi Hugo,
        I created an extension using your guide for a SharePoint 2019. The build has been successful and development testing. Publishing the extension had been successful but there is no change in the displayed css on the site. The file is checked in and I have used two different folders with public access. I added “!important” to each modification within your sample css. Is there anything else needed?

      • Hugo Bernier

        Keep in mind that some elements get added later in the page’s lifecycle (like the feedback button, for example).

        Try to see if your CSS is getting called (it should show up in your Browser’s Dev tools under the Network tab) and see if there are any console errors.

        Alternatively, use your browser’s dev tools to see what styles are applied. What may happen is that your styles actually get loaded and applied, but another selector overwrites your changes. If that’s the case, just define the same CSS selector (or one that’s more specific) and try again. I only use !important to overwrite styles that are defined at the element level (in HTML) instead of CSS changes.

        I hope this helps?

  4. Divesh Gupta Reply

    Hello….I have been trying this for a couple of days now….For some reason it is not working for me. I see no errors in the browser developer extensions and no error anywhere in general…Please help

    • Hugo Bernier Reply

      It could be an issue with the extension itself not loading. It can happen sometimes with SharePoint. Did you deploy the extension to all site collections, or are you trying to load it for one site collection only?

      Try “breaking” the extension by using the wrong URL for your CSS, or by renaming the CSS. Then use your browser’s development tools to see if you can a 404 error – page not found.

      I’ll try to find a way to detect if the extension is loading or not. I believe I had removed logging in my code. I may have to re-add it 🙂

      • Divesh Gupta

        I see this error
        refused to apply style from ‘url’ because its mime type (”) is not a supported stylesheet mime type, and strict mime checking is enabled.

      • Divesh Gupta

        I see this error:

        refused to apply style from ‘URL’ because its mime type (”) is not a supported stylesheet mime type, and strict mime checking is enabled.

      • Hugo Bernier

        Is that when you upload your CSS to another library?

  5. I have the extension loaded across multiple sites, and each site has a custom.css that gets loaded in the header with absolute url. When you switch between sites though, it has the css from the previous site cached. Is there a way around this?

    • Hugo Bernier Reply

      You could add some code to the extension to make the CSS URL unique.

      A common behavior is to add the date to the CSS URL dynamically (e.g.: styles.css?202003111315) to force it to always reload. The URL is unique so the browser doesn’t cache it, but it ignores the additional query string parameters because the content disposition is text/css.

      If you wanted to cache it on a site by site basis, you could simply append the site name or URL (e.g.: styles.css?HR)

      I hope it helps?

  6. Hi Hugo, thank you for sharing this. I tried it but unable to get it to work. Microsoft seems to change the settings on the Style Library now. I could no longer add files to this library, so I tried changing the location of the CSS file as per your instruction above, but not able to get it to work. Can you please help? Thank you.

    • Hugo Bernier Reply

      Hi Felicia,

      I hadn’t heard of Microsoft changing the settings on the Styles Library. I just tried it on one of my tenants and I was able to do it. If it is indeed an upcoming change, it is very possible that the change has not been pushed to my tenant yet. Good to know!

      Perhaps it is a permission issue? You could always try creating a new library at the root site collection and upload your CSS there, it should work.

      Please let us know whether you solved it or not!

  7. Paulo Ramos Reply

    Hi Hugo.
    Nice extension, it works as expected. If I also wanted to load a global JS file can I code it in the same extension?

    • Hugo Bernier Reply

      I haven’t tried it, but you could probably change the code as follows:

      “`
      export default class InjectCssApplicationCustomizer
      extends BaseApplicationCustomizer {

      @override
      public onInit(): Promise {
      Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);

      const cssUrl: string = this.properties.cssurl;
      if (cssUrl) {
      // inject the style sheet
      const head: any = document.getElementsByTagName(“head”)[0] || document.documentElement;
      let customStyle: HTMLLinkElement = document.createElement(“link”);
      customStyle.href = cssUrl;
      customStyle.rel = “stylesheet”;
      customStyle.type = “text/css”;
      head.insertAdjacentElement(“beforeEnd”, customStyle);
      }

      //BEGIN: ADD THE FOLLOWING
      const scriptUrl: string = this.properties.scripturl;
      if (scriptUrl) {
      // inject the javascript
      const head: any = document.getElementsByTagName(“head”)[0] || document.documentElement;
      let customScript: HTMLLinkElement = document.createElement(“script”);
      customScript.href = scriptUrl;
      head.insertAdjacentElement(“beforeEnd”, customScript);
      }
      //END: ADD
      return Promise.resolve();
      }
      }
      “`

      Let us know if it works

      • Hugo Bernier

        Thanks for confirming!

  8. Hi Hugo,

    I tried directly downloading the package and adding it to the site collection app catalog by uploading the custom.css file to the style library of tenant. It doesnt seem to reflect the change. Neither did it work when I tried adding it to the app catalog of tenant. I added the css file and published it as well. What could be wrong?

    • Hugo Bernier Reply

      Krithika,

      I have noticed that sometimes SharePoint takes a long time deploying extensions. I had the same issue when I was testing other extensions. Did the problem ever go away?

  9. Pankaj Sukheja Reply

    I am not want to deploy it for all sites and css path should not be from root site collection.is this is possible?

    • Pankaj Sukheja Reply

      Can we specific css path other than root site collection? And i have deploy extension and make available on subsites on demand. By default it is not available for all. And it is working fine for me.can we give css path other than root site collection?

      • Hugo Bernier

        You can specify any path you wish, but you need to make sure the CSS can be accessed from any site collection where you want the extension to run.

        I hope this answers your question?

      • Pankaj Sukheja

        It is not working for me. Can you please show me how it will be? Thanks for your help

    • Hugo Bernier Reply

      Pankaj,

      Yes, you can configure each extension to use its own CSS path. Just change the settings in the JSON file to specify a local URL for your CSS.

      I hope this helps?

  10. Pankaj Sukheja Reply

    I have try same and it is working fine for root site collection. But when i try to use it another site collection it is not working. Please let me know what changes i have to do to use it in another site collection.

    • Hugo Bernier Reply

      Hi Pankaj,

      The extension must be deployed on all sites you wish to apply the custom CSS.

      If that is already done, make sure that your CSS path is an absolute path (i.e.: starts with a ‘/’), so that the CSS path can be resolved from all sites.

      One way to verify in your browser is look for a ‘404’ (Page Not Found) error on the Network tab of your browser’s developer toolbar. If the extension is running but the CSS does not apply, I suspect that it just can’t find the resource.

      I hope this helps?

      • Pankaj Sukheja

        Hi,
        I not want to deploy it on all site collections.and also css library path should not be on root site collection.can we place css file on any other site collection ?

      • Hugo Bernier

        No, unfortunately the extension must run on all site collections that require the custom CSS.

        When you deploy the extension, you should be prompted to deploy to all site collections. It is the option you should use to do that.

      • Hi Hugo, please ignore my last comment. I managed to get it to work.. user error 🙂 I forgot to add the site url for the cssurl path (d’oh!). Anyhow, I also managed to deploy it in a Site Collection, not at tenant level (to all site collections). It is a bit fiddly but it works.

        To do it:
        1. you would need to know the ComponentsID – which can be obtained by deploying the app as per above instruction at first, then note down the config items added to the Tenant Wide Extensions list). Then, remove the app, and re-add it without deploying it at the tenant level, as follow:
        2. Once you add the app to the app catalogue, untick the one that says “make this solutions available to all sites”, then click Deploy.
        3. Once deployed, add the app onto the App Catalog site collection by going New > Add > {name of the app}
        4. Add a new item in the Tenant Wide Extensions list – with details as follow: (please modify the cssurl path to your css location. if you use the pre-build solution, use the Component Id below. Otherwise, please update it to the correct Component Id)
        Title InjectCss
        Component Id 5a1fcffd-dfeb-4844-b478-1feb4325a5a7
        Component Properties {“cssurl”:”/SiteAssets/custom.css”}
        Web Template
        List Template 0
        Location ClientSideExtension.ApplicationCustomizer
        Sequence 0
        Host Properties
        Disabled No
        5. Add the app to each of the site collection where you want to inject the CSS to.

        Hope this helps @pankaj

        Cheers

      • Hugo Bernier

        Thanks for following up and sharing your solution.

      • There seems to be a little confusion here: you can deploy the solution as a tenant wide extension OR as an “app” that can be added to specific collections by selecting or deselecting the deploy to all site collections option. I’ve used it to add 1 tenantwide stylesheet and several other stylesheets specific to certain sites. In addition to Hugo’s suggestion, you can also easily check what’s actually getting loaded by navigating to the files in the browser inspector’s “sources” tab

  11. Yash Shrivastava Reply

    every time I reload the page with new css, I had to clear the cache first and then reload. Is there any way to resolve this issue like if we can add a version number in css so that every time it gets freshly loaded

    • Hugo Bernier Reply

      You could change the extension configuration JSON file to add a unique value (e.g.: ?v=1, ?v=2, etc.) every time you want to force refreshing a new version?

  12. solution deployed. css injected. Seems to work. Only thing I absolutely CANNOT change is anything regarding the global nav. NOTHING I add works. It it ignored.

    Has ANYONE had any success? I really just want simple changes (font-weight, etc.)

    • Hugo Bernier Reply

      Fleagle,

      What do you mean by “global nav”? Do you mean the suite navigation (a.k.a. the “Waffle”)? It is often injected **after** the page is loaded, so it may be difficult to manipulate it.

  13. Evgeny Trifonov Reply

    Hi, thnx for your solution. I spent about 2 day to found it. My case was in change CSS in modern list view with grouping and I did it with your help.

    But I have a question. Your solution is wide-web and one css-file affects on all lists. Is there the some way to select list? Because now all my lists, even system looks alike and I need aplly custom.css to only one.

    I think, we can add this opportunity to solution, but I am not programmer 🙁

    P.S. Sorry for my crazy English.

  14. Hugo – if I wanted to add multiple links to stylesheets in ClientSideComponentProperties, are they separated by a comma, or nothing? And is this the best practice? I tried it and both stylesheets appear in the source code, but only the 2nd one seems to get rendered.

    • Hugo Bernier Reply

      Matthew,

      I designed the extension to handle one CSS at a time, but you can add the extension as many times as you’d like. It wouldn’t take much to change the code to handle multiple CSS, though. Let me know if you can’t add the extension more than once and I’ll find some time to update the sample.

      • Thanks Hugo! We’re looking to add a couple of security trimmed stylesheets to manage what users can access, and I think it would be useful to have everything in one extension, if you get the time to update the code. Meanwhile I’ll add another extension or 2..

  15. Florian Hein Reply

    Been looking for a week for a viable solution and I’m so glad I found yours. Thanks for your contribution.

    • Hugo Bernier Reply

      Thank you! I’m glad I could help!

  16. Henry Radke Reply

    Hey, thanks for this article! I managed it to apply the .css via gulp serve but had problems while trying to deploy it for production (gulp bundle –ship -> gulp package-solution –ship).

    The problem was that I added the .sppkg file on the http:// AppCatalog-URL. So I had to open the AppCatalog via https:// and then the deployment worked flawlessly!

    Maybe this is something you could add to your FAQ section, because I think that this could be the same problem, the other two commentators may had :).

    • Hugo Bernier Reply

      That’s a great catch! I’ll make a note in the article, thank you!

    • Hugo Bernier Reply

      Nandita, I’d be happy to help. Can you tell me what steps you followed so that I can diagnose the issues?

    • Ednar Echeverría Reply

      This happened to me too. A was capable of solving this after many days of trying. In my case I deleted the app catalog site in the sharepoint admin panel. Then recreated it from scratch and waited some minutes to make the app catalog site available again.

      When I opened it and reuploaded the sppkg again, it worked with no problems.

      PD: Be sure you only have one app catalog site, when I deleted it I noticed I had a second one in the folder /teams/apps alongside /sites/apps.

  17. Pasqualino Reply

    Hugo thank you so much, your extension and your advices have been invaluable to me

Leave a Reply to Bilal Cancel reply

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

%d bloggers like this: