Blazor's CSS isolation ::deep issue and solution :
by:
blow post content copied from Khalid Abuhakmeh
click here to view original post
Lately, I’ve been helping a few developers solve some of their issues around Blazor adoption. While I have mixed feelings about the technology, my urge to help developers overcome their challenges outweighs my trepidation with the technology itself.
Recently, I had a Blazor developer have issues with the ::deep
cascading style sheet (CSS) pseudo-selector and the style’s inability to cascade styles to child components. If you’re having a similar issue, I have a detailed issue breakdown and potential solution. Let’s get started.
What is CSS isolation?
Blazor is a component-based framework that takes much inspiration from the JavaScript ecosystem’s React component model. The model focuses on reusability through encapsulation and manifests in a single-file style that combines Razor markup and C# logic.
In a Blazor application, CSS can be scoped to a single component by adding a convention-based filename similar to a component. For example, if your application contains a Counter.razor
component, any styles defined in Counter.razor.css
would be scoped to the component definition.
Blazor scopes CSS by adding a component-unique HTML attribute to the HTML rendered on the client. Take, for example, the contents of Counter.razor.css
, which I’ve added to a new Blazor application.
h1 {
color: pink;
}
The resulting CSS is sent to the client when the Blazor build pipeline processes the file.
/* /Pages/Counter.razor.rz.scp.css */
h1[b-13zswy4nzk] {
color: pink;
}
This CSS rule reads, “any H1 in the current document with an attribute of ‘b-13zswy4nzk’ should have pink-colored text.”
Scoped CSS allows you to target components with styles without the risk of having rules conflict with other rules. But what if you want to take advantage of the cascading part of CSS?
Using the ::deep
pseudo-selector
As you saw, Blazor processes component-scoped CSS files and adds them to your application’s Blazor CSS file. This allows Blazor developers to use additional pseudo-selectors to generate different rules. One such pseudo-selector is the ::deep
selector.
The ::deep
pseudo-selector will allow you to target child HTML elements nested within a parent component. Let’s change the previous CSS rule to use this feature.
::deep h1 {
color: pink;
}
After running the Blazor application, we can see the updated CSS rule.
/* /Pages/Counter.razor.rz.scp.css */
[b-13zswy4nzk] h1 {
color: pink;
}
This new CSS rule reads, “any H1 element found within an element with the attribute of ‘b-13zswy4nzk’ will have a text color of pink.”.
Great! Ship it. What’s the issue?
The Blazor attribute problem
Let’s take a look at the Counter.razor
component and a new Child.razor
component with the previous isolated CSS in mind. First, the Counter.razor
definition.
@page "/counter"
<PageTitle>Counter</PageTitle>
<Child/>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
You’ll note the usage of the Child
component right under the PageTitle
component. Let’s see that implementation.
<h1>Counter</h1>
@code {
}
Given the isolated CSS, we would assume that the H1
element within our child component would become pink
, but that’s not the case. Let’s look at the rendered HTML to see the issue.
<article class="content px-4" b-5dhe1ruqj7>
<h1 tabindex="-1">Counter</h1>
<p role="status" b-13zswy4nzk>Current count: 0</p>
<button class="btn btn-primary" b-13zswy4nzk>Click me</button>
</article>
Do you see the issue? Blazor adds the unique attribute to all top-level HTML elements within a component. This creates a mismatch between markup and the CSS rule.
In the rendered HTML, the p
and button
elements get the attribute b-13zswy4nzk
, but our Child
component is used within the Counter
component but before the any HTML element. Worse, there are no encapsulating HTML elements.
The fix to ::deep styles
So you’ve read this far and want to know the solution to the problem. It’s relatively straightforward.
If your usage of the ::deep
pseudo-selector is not working, you need to add a wrapping HTML element within your parent component that is a parent to all usages of child components.
Let’s fix our Counter.razor
markup with a straightforward change.
@page "/counter"
<div>
<PageTitle>Counter</PageTitle>
<Child/>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
When we re-run our application, we can see that the unique attribute is now on the first HTML element along with the previous elements of p
and button
and our CSS rule now targets nested HTML elements correctly.
<div b-13zswy4nzk>
<h1 tabindex="-1">Counter</h1>
<p role="status" b-13zswy4nzk>Current count: 0</p>
<button class="btn btn-primary" b-13zswy4nzk>Click me</button>
</div>
This fixes the issue, but depending on the CSS framework library you’re using, a parent div
HTML element may break your layout. Experiment with different HTML tags to see which ones you can use as wrappers while not breaking global CSS rules.
Conclusion
CSS Isolation is a technique for continuing the philosophy of encapsulating as much within the bounds of a component, but there are times where you want the cascade to do its job. To get the ::deep
pseudo-selector element working properly, all your Blazor components should have a single-wrapping HTML element as the root element. If your Blazor components do not have a parent element, you’ll get CSS rules that don’t ultimately match your HTML markup.
Hope this post helped, and as always, thanks for reading and sharing my posts with friends and colleagues. Cheers.
March 19, 2024 at 05:30AM
Click here for more details...
=============================
The original post is available in Khalid Abuhakmeh by
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================
Post a Comment