CSS Isolation In Razor Pages

CSS isolation was introduced in .NET 5 for Blazor components. Now it's coming to Razor Pages (and MVC views) in .NET 6, due to be released in November this year. Here's a quick look at how CSS isolation works in Razor Pages and the kind of problem that it's designed to solve.

First, let's have a look at the problem CSS isolation is intended to mitigate. As you build a web application, you will generally place CSS styles in a global style sheet that is referenced in the main layout file. That way, the declarations within the style sheet are available to all pages that make use of the styles, whether they actually need them or not. As you continue to develop the application, new styles will be added that relate to specific sections or even pages. You might want to change the default font for a single page, for example, so you add a new CSS selector to your style sheet that you can use to target elements on that page only, and update the class attributes of the target elements accordingly. Your global style sheet grows and grows. Your primary style management tool is Ctrl + F. Over time, you forget which style declarations are actually being used and which can safely be removed.

Now, it has always been possible to create page-specific style sheets and to use the Razor sections feature to inject them into the layout on a page-by-page basis. This works ok, but requires you to remember to define the section and add the relevant HTML to include the style sheet. It also means that there is an additional HTTP call for each page-specific style sheet, until they are cached by the browser - unless you configure bundling for your page-specific styles. CSS isolation in Razor Pages basically removes the reliance on sections and includes bundling for free.

So how does CSS isolation work? The feature will be enabled by default in Razor Pages, so there is no need to add additional packages or configure any services or middleware. All you have to do is to place a style sheet in the Pages folder alongside the page that it is intended to affect. You just need to follow a specific naming convention, which is the Razor page file name with .css on the end.

In my case, I only want to affect the font for <p> elements in the website's home page - the Index.cshtml file, so I add a file named Index.cshtml.css to the folder where the file is, and Visual Studio helpfully groups and nests it with the existing Razor page files:

Css Isolation in Razor Pages

The content of the file sets the font for the selector to the new one that comes with VS 2022 (or your default monospace font):

p {
    font-family'Cascadia Mono'monospace;
}

All style sheets need to be referenced, and this one is no different, except that the reference is in the format name_of_application.styles.css. In my case, the name of the project is CssIsolationDemo, so I use the nameof operator, passing in the application's namespace. The link reference goes in the layout file, just like other global style sheet references:

<link rel="stylesheet" href="@(nameof(CssIsolationDemo)).styles.css" />

When I run the application, I can see that the paragraph font on the home page has been styled appropriately:

Css Isolation in Razor Pages

whereas the style on the Privacy page is unaffected:

Css Isolation in Razor Pages

So how does it work? Well, if we look at the rendered source for the home page, we can see that an additional attribute has been injected into every element that was generated by the Index.cshtml template:

Css Isolation in Razor Pages

That attribute is used as part of the selector in the CSS file that was generated and is served from the reference that we added in the layout file (https://localhost:5001/CssIsolationDemo.styles.css):

Css Isolation in Razor Pages

If you want to add a CSS file that affects another Index.cshtml page in the application, simply add it to the folder where the target Index file resides:

Css Isolation in Razor Pages

The contents of multiple isolated CSS files are automatically bundled, You can see that a different attribute value is generated for each page:

Css Isolation in Razor Pages

The bundled file itself is placed in the wwwroot folder when the application is published.

Just one thing to note, CSS isolation is a build step, so it doesn't work with Razor Runtime Compliation and there don't appear to be any plans to change this for the foreseeable future. So if you find that this feature doesn't seem to work for you, it's worth checking that you haven't enabled runtime compilation of your Razor files as a first troubleshooting step.

Summary

SPA frameworks like Angular, React and Vuejs have supported the ability to scope CSS to individual components for a while, and Blazor had to jump on board in the last release of .NET to keep up. It's nice that this is being added to Razor Pages (and MVC, if you still want to generate HTML that way) from .NET 6 onwards.