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:
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. I also use the ~/
feature which Razor converts to an absolute path to the web root folder, or wwwroot :
<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:
whereas the style on the Privacy page is unaffected:
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:
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
):
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:
The contents of multiple isolated CSS files are automatically bundled, You can see that a different attribute value is generated for each page:
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 Compilation 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. Also, CSS isolation doesn't get applied to tag helpers because their output is generated at runtime, not build time.
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.