From site.css to component styles

October 7, 2020 · 4 minute read · Tags: blazor | css | javascript

Developers are human beings…

If there’s one thing we know about human beings it’s that they’re hardwired to take the shortest route from A to B…

Which probably explains why you have a single stylesheet in your app where ALL the CSS styles go.

One stylesheet to rule them all

You’re working on your app and realise you need to tweak the CSS…

… so you go to the one place where all the styles live.

Once there you realise you need a new style but where should you put it?

I mean… you’re already in this file which has lots of other CSS styles, and creating a new one would mean naming it, and linking to it from your main HTML page.

That all sounds like work and it’s nearly lunchtime… maybe, just this once, you could add one teeny-tiny little style to the bottom of this file?

Go round this hamster wheel a few times; site.css gets bigger, new styles are chucked on the end and tweaking your styles takes longer, and longer (as does figuring out why anything looks the way it does).

.NET 5 offers a solution - CSS Isolation

Enter .NET 5 and CSS Isolation.

With CSS Isolation you can define styles for your components right there, next to the component.

All you need to do is:

  1. Create a new <component>.razor.css file
  2. Define your component’s styles in that file
  3. Get on with the rest of your day (or you know, go for lunch)

Say you want to tweak the styles for the Counter component which ships with the Blazor project templates.

With .NET 5 you can create a new Counter.razor.css file.

In there, define your styles as normal at which point Blazor will automatically wire everything up so Counter uses these styles.

Counter.razor.css

h1 {
    font-size: 1.6em;
    color: red;
    font-weight: bold;
    font-style: italic;
}

The big win is that these styles only apply to the specific component. So you can happily set styles for generic elements like h1, confident that it will only affect this component’s h1 elements and not the other h1 elements in your application.

Index.razor

<h1>Regular H1 (outside of Counter component)</h1>

<Counter />

Go ::deep

By default these styles will only apply to the specific component, not child components.

For example:

Counter.razor

<div>
    <CustomHeading>Counter</CustomHeading>
    
    <p>Current count: @currentCount</p>
    
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>

If the Counter component includes another component (CustomHeading in this example), and that component has an h1 tag:

<h1 class="heading">@ChildContent</h1>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; } 
}

The style we defined in Counter.razor.css won’t apply to the h1 in CustomHeading.

However, you can make it apply using the ::deep combinator.

Counter.razor.css

::deep h1 {
    font-size: 1.6em;
    color: red;
    font-weight: bold;
    font-style: italic;
}

Now any child components with h1 elements will take on this style as well.

How it works

When you use this feature in .NET 5 Blazor automatically gives your elements unique names/identifiers and rewrites your CSS rules to use these identifiers (at build time).

It also compiles all the various CSS styles into a file called {project_name}.styles.css.

So your HTML will end up looking something like this (when you inspect the source):

<div b-dmd9kyn336="">
    <h1 class="heading">Counter</h1>
    <!-- other markup -->
</div>

And the corresponding file can be seen in {project_name}.styles.css.

/* /Pages/Counter.razor.rz.scp.css */
[b-dmd9kyn336] h1 {
    font-size: 1.6em;
    color: red;
    font-weight: bold;
    font-style: italic;
}

If we omit the ::deep combinator from our example we get slightly different CSS in {project_name}.styles.css.

/* /Pages/Counter.razor.rz.scp.css */
h1[b-dmd9kyn336] {
    font-size: 1.6em;
    color: red;
    font-weight: bold;
    font-style: italic;
}

This shows the clear difference between using or not using ::deep.

In summary

Once you start thinking in terms of components it follows you’ll want fine-grained control of each component’s appearance using CSS.

CSS Isolation in .NET 5 makes this much easier and keeps the CSS close to the markup it affects (avoiding the problem of everything getting thrown into one big stylesheet, making maintenance much more difficult).

Join the Practical ASP.NET Newsletter

Ship better Blazor apps, faster. One practical tip every Tuesday.

I respect your email privacy. Unsubscribe with one click.

    Next up

    Preventing double clicks in Blazor components
    Guard against duplicate events in your Blazor app
    Avoiding interactivity with Blazor?
    Sometimes a little HTML and CSS is all you need
    How to upload a file with Blazor SSR in .NET 8?
    How to handle file uploads without using an interactive render mode?