How CSS @scope replaces BEM

One of the most common and challenging issues faced by front-end engineers is CSS naming conventions. With the popularity of the Block Element Modifier (BEM) approach, many people have become accustomed to organizing their styles according to a maintainable pattern.

The upcoming implementation of @scope in Chrome will further improve the performance of BEM by allowing block-level scoping of styles in stylesheets. This will make stylesheets easier to maintain while providing tighter control over CSS cascading.

In this article, we will show how to use the @scope attribute in Chrome and how to use it to replace BEM in front-end projects. We’ll walk you through a few examples, which you can view and follow in the example project on GitHub.

What is CSS @scope?

In the upcoming Chrome 118 release, the @scope attribute creates a block-level scope for CSS styles. This gives developers more control over CSS styling, as we can now explicitly define scopes for different parts of the view directly in the CSS file.

Take a look at the following HTML example:

<main className="sample-page">
    <h1>With Scope</h1>
    <section className="first-section">
        <p>some text</p>
        <p>
            some text and then a <a href="/">back link</a>
        </p>
    </section>
    <section className="second-section">
        <h2>Dog Picture</h2>
        <div>
            <p>second section paragraph text</p>
        </div>
        <img src={'./DOG_1.jpg'} alt="dog" />
    </section>
</main>

In this HTML, we can style the elements within the second-section style area using:

.second-section {
    display: flex;
    flex-direction: column;
    border: solid;
    padding: 40px;
    margin: 20px;
}
@scope (.second-section) {
    h2 {
        text-align: center;
    }
    img {
        max-width: 400px;
        max-height: 100%;
    }
    div {
        display: flex;
        justify-content: center;
        margin: 20px;
    }
    p {
        max-width: 200px;
        text-align: center;
        background-color: pink;
        color: forestgreen;
        padding: 10px;
        border-radius: 20px;
        font-size: 24px;
    }
}

When using @scope, you can also create a “donut” scope that defines the beginning and end of a set of styles and the elements within it. Using the same HTML as above, donut scope can define styles from the starting area of sample-page to the second-section style area:

/* donut scope */
@scope (.sample-page) to (.second-section) {
    p {
        font-size: 24px;
        background-color: forestgreen;
        color: pink;
        text-align: center;
        padding: 10px;
    }
    a {
        color: red;
        font-size: 28px;
        text-transform: uppercase;
    }
}

The best part is that it functions very similarly to using BEM shapes, but with less code.

Browser supports CSS @scope

As of October 2, 2023, CSS @scope has not been officially released, so you need to turn on the experimental network features flag to use it. To do this, first open a tab in Chrome, go to chrome://flags/, then search for and enable the “Experimental Web Platform Features” flag:

What is BEM

BEM is a way of grouping styles in an HTML view for easy navigation.

Consider that a large HTML page has many elements with different styles. After setting a few initial style names, it becomes difficult to maintain the style as the page expands. BEM attempts to alleviate this problem by building style names around the actual styles.

block is a containing HTML element. Consider HTML like this

<main className="sample-page">
    <h1 className="sample-page__title">With BEM</h1>
    <section className="sample-page__first-section">
        <p className="sample-page__first-section--first_line">
            some text
        </p>
        <p className="sample-page__first-section--second-line">
            some text and then a{' '}
            <a
                className="sample-page__first-section--second-line-link"
                href="/"
            >
                back link
            </a>
        </p>
    </section>
</main>

In this HTML

  • The block = sample-page style is the block style because it wraps a group of elements

  • element = When styling the

    element, the element is treated as an element, so an extra __ is added to the style name , thereby creating sample-page__title. The same goes for sample-page__first-section.

Modifier = when inside a

element is

When the element is styled, the style name will have an extra --first-line, creating sample-page__first-section--first-line, so:

  • The block is sample-page

  • The element is first-section

  • The modifier is first-line

BEM scales well, especially when using SASS to group styles and create similar content using the & amp; operator:

.sample-page {
    display: flex;
    flex-direction: column;
    margin-top: 10px;

     &__title {
        font-size: 48px;
        color: forestgreen;
    }

     &__first-section {
      font-size: 24px;
      border: solid;
      padding: 40px;
      margin: 20px;

       &--first-line{
        font-size: 24px;
        background-color: forestgreen;
        color: pink;
        text-align: center;
        padding: 10px;
      }
    }
}

The difficulty is that in a large project this results in very large CSS or SASS files that are still difficult to manage at scale. You can use @scope to replace BEM styles to make style definitions smaller and more manageable.

Reconstruct BEM using @scope

The best way to demonstrate the benefits of using @scope is to use @scope in an application that uses a mainstream framework or library like React. In the example app on GitHub, there is a project in the react-example folder with pages first styled using BEM and then using @scope Refactor.

You can run the application and click the WithBEM or WithScope button to see the implementation. Components and stylesheets have corresponding names, prefixed with WithBEM or WithScope , located in the pages and styles folders respectively.

Starting from the BEM style component WithBEMPage.tsx, we first see the HTML style designed with the BEM method:

<main className="sample-page">
    <h1 className="sample-page__title">With BEM</h1>
    <section className="sample-page__first-section">
        <p className="sample-page__first-section--first_line">
            some text
        </p>
        <p className="sample-page__first-section--second-line">
            some text and then a{' '}
            <a
                className="sample-page__first-section--second-line-link"
                href="/"
            >
                back link
            </a>
        </p>
    </section>
    <section className="sample-page__second-section">
        <h2 className="sample-page__second-section--title">
            Dog Picture
        </h2>
        <div className="sample-page__second-section--div">
            <p className="sample-page__second-section--div-paragraph">
                second section paragraph text
            </p>
        </div>
        <img
            className="sample-page__second-section--image"
            src={'./DOG_1.jpg'}
            alt="dog"
        />
    </section>
</main>

In the component WithScopePage.tsx we can see how clean the refactoring is with the following:

<main className="sample-page">
    <h1>With Scope</h1>
    <section className="first-section">
        <p>some text</p>
        <p>
            some text and then a <a href="/">back link</a>
        </p>
    </section>
    <section className="second-section">
        <h2>Dog Picture</h2>
        <div>
            <p>second section paragraph text</p>
        </div>
        <img src={'./DOG_1.jpg'} alt="dog" />
    </section>
</main>

To refactor BEM to @scope , just find the style group and add your scope styles appropriately. Let’s take a look at the title part first. In the original WithBEMPage.tsx file, different styles are defined for each section. In the @scope version, a more concise style is defined for specific elements:

.sample-page {
    display: flex;
    flex-direction: column;
    margin-top: 10px;
}
/* replaced */
/* .sample-page__title {
    font-size: 48px;
    color: forestgreen;
} */
/* donut scope */
@scope (.sample-page) to (.first-section) {
    h1 {
        font-size: 48px;
        color: forestgreen;
    }
}

Again, in the first part of the content, the original BEM style looks like this:

.sample-page__first-section {
    font-size: 24px;
    border: solid;
    padding: 40px;
    margin: 20px;
}
.sample-page__first-section--first_line {
    font-size: 24px;
    background-color: forestgreen;
    color: pink;
    text-align: center;
    padding: 10px;
}
.sample-page__first-section--second-line {
    font-size: 24px;
    background-color: forestgreen;
    color: pink;
    text-align: center;
    padding: 10px;
}
.sample-page__first-section--second-line-link {
    color: red;
    font-size: 28px;
    text-transform: uppercase;
}

Refactoring the first part using @scope, we now have a cleaner style definition:

.first-section {
    font-size: 24px;
    border: solid;
    padding: 40px;
    margin: 20px;
}
/* donut scope */
@scope (.sample-page) to (.second-section) {
    p {
        font-size: 24px;
        background-color: forestgreen;
        color: pink;
        text-align: center;
        padding: 10px;
    }
    a {
        color: red;
        font-size: 28px;
        text-transform: uppercase;
    }
}

Another benefit of this is that the HTML view is smaller and easier to read. considering before

<section className="sample-page__first-section">
    <p className="sample-page__first-section--first_line">
        some text
    </p>
    <p className="sample-page__first-section--second-line">
        some text and then a{' '}
        <a
            className="sample-page__first-section--second-line-link"
            href="/"
        >
            back link
        </a>
    </p>
</section>

Then

<section className="first-section">
    <p>some text</p>
    <p>
        some text and then a <a href="/">back link</a>
    </p>
</section>

With these two example components, we can refactor each part. Finally notice how it makes the style cleaner and more readable.

Other advantages of @scope over BEM

In addition to the advantages of refactoring BEM into @scope, using @scope also provides greater control over CSS cascading. CSS cascade is an algorithm that defines how web browsers handle the style conditions that make up the elements on an HTML page.

When working on any front-end project, developers may need to deal with strange results due to style cascading. By using @scope, you can control the side effects of cascading by tightly scoping elements.

The styles and some elements of the file no_scope.html are defined as follows:

<!DOCTYPE html>
<html>
    <head>
        <title>Plain HTML</title>
        <style>
            .light {
                background: #ccc;
            }
            .dark {
                background: #333;
            }
            .light a {
                color: red;
            }
            .dark a {
                color: yellow;
            }
            div {
                padding: 2rem;
            }
            div > div {
                margin: 0 0 0 2rem;
            }
            p {
                margin: 0 0 2rem 0;
            }
        </style>
    </head>
    <body>
        <div class="light">
            <p><a href="#">First Level</a></p>
            <div class="dark">
                <p><a href="#">Second Level</a></p>
                <div class="light">
                    <p><a href="#">Third Level</a></p>
                </div>
            </div>
        </div>
    </body>
</html>

The result is as follows:

819e19f1486233632320e24247453ca9.png

The problem here is that according to the defined CSS, the Third Level should be red text, not yellow. This is a side effect of CSS cascading because page styles are interpreted in terms of appearance order, so the Third Level is considered yellow instead of red. With the diagram in the original Bram.us article, we can see the order in which the CSS cascade evaluates selectors and styles:

c8461593732abb09e2884a12b70299ea.png

Without @scope , the CSS cascade goes directly from “specificity” to “appearance order”. When using @scope, the CSS cascade will consider the @scope element first. You can see the effect by adding @scope to the .light and .dark styles in the example.

First, modify the original HTML and CSS as follows:

<!DOCTYPE html>
<html>
    <head>
        <title>Plain HTML</title>
        <style>
            .light {
                background: #ccc;
            }
            .dark {
                background: #333;
            }
            div {
                padding: 2rem;
            }
            div > div {
                margin: 0 0 0 2rem;
            }
            p {
                margin: 0 0 2rem 0;
            }
            @scope (.light) {
                :scope {
                    background: white;
                }
                a {
                    color: red;
                }
            }
            @scope (.dark) {
                :scope {
                    background: black;
                }
                a {
                    color: yellow;
                }
            }
        </style>
    </head>
    <body>
        <div class="light">
            <p><a href="#">First Level</a></p>
            <div class="dark">
                <p><a href="#">Second Level</a></p>
                <div class="light">
                    <p><a href="#">Third Level</a></p>
                </div>
            </div>
        </div>
    </body>
</html>

The output is as follows

927a61bbdb9ec75203974e57355b616b.png

Summary

In this article, we explore ways to refactor a BEM-style application to use the new @scope feature in Chrome. We introduced how @scope works, then refactored a simple page from BEM to @scope .

The new @scope feature has the potential to be a big advantage for front-end developers. However, other browsers must also implement support, which may take time. Until then, this is definitely an interesting feature that could be a big help when styling front-end projects.

Welcome to long press the picture to add dishwasher as a friend and share Vue React Ts regularly.

e21f00c3fce2f3f78137c0b0a1354afa.png

at last:

vue2 and vue3 skills collection

VueUse source code interpretation

e68d904572704493d09c7c9d41b032e6.jpeg

Because the rules of the WeChat official account have been modified, if you do not star or click “Read”, you may not receive the push of my official account’s articles. Please star this official account and remember after reading the article. Like it or Looking, thank you!