Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      May 16, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      May 16, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      May 16, 2025

      How To Prevent WordPress SQL Injection Attacks

      May 16, 2025

      Microsoft has closed its “Experience Center” store in Sydney, Australia — as it ramps up a continued digital growth campaign

      May 16, 2025

      Bing Search APIs to be “decommissioned completely” as Microsoft urges developers to use its Azure agentic AI alternative

      May 16, 2025

      Microsoft might kill the Surface Laptop Studio as production is quietly halted

      May 16, 2025

      Minecraft licensing robbed us of this controversial NFL schedule release video

      May 16, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      The power of generators

      May 16, 2025
      Recent

      The power of generators

      May 16, 2025

      Simplify Factory Associations with Laravel’s UseFactory Attribute

      May 16, 2025

      This Week in Laravel: React Native, PhpStorm Junie, and more

      May 16, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Microsoft has closed its “Experience Center” store in Sydney, Australia — as it ramps up a continued digital growth campaign

      May 16, 2025
      Recent

      Microsoft has closed its “Experience Center” store in Sydney, Australia — as it ramps up a continued digital growth campaign

      May 16, 2025

      Bing Search APIs to be “decommissioned completely” as Microsoft urges developers to use its Azure agentic AI alternative

      May 16, 2025

      Microsoft might kill the Surface Laptop Studio as production is quietly halted

      May 16, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Solved by CSS: Donuts Scopes

    Solved by CSS: Donuts Scopes

    November 22, 2024

    Imagine you have a web component that can show lots of different content. It will likely have a slot somewhere where other components can be injected. The parent component also has its own styles unrelated to the styles of the content components it may hold.

    This makes a challenging situation: how can we prevent the parent component styles from leaking inwards?

    This isn’t a new problem — Nicole Sullivan described it way back in 2011! The main problem is writing CSS so that it doesn’t affect the content, and she accurately coined it as donut scoping.

    “We need a way of saying, not only where scope starts, but where it ends. Thus, the scope donut”.

    Diagram showing a rectangle colored salmon inside another rectangle colored dark red. The larger rectangle is the donut and the smaller rectangle is the hole.

    Even if donut scoping is an ancient issue in web years, if you do a quick search on “CSS Donut Scope” in your search engine of choice, you may notice two things:

    1. Most of them talk about the still recent @scope at-rule.
    2. Almost every result is from 2021 onwards.

    We get similar results even with a clever “CSS Donut Scope –@scope” query, and going year by year doesn’t seem to bring anything new to the donut scope table. It seems like donut scopes stayed at the back of our minds as just another headache of the ol’ CSS global scope until @scope.

    And (spoiler!), while the @scope at-rule brings an easier path for donut scoping, I feel there must have been more attempted solutions over the years. We will venture through each of them, making a final stop at today’s solution, @scope. It’s a nice exercise in CSS history!

    Take, for example, the following game screen. We have a .parent element with a tab set and a .content slot, in which an .inventory component is injected. If we change the .parent color, then so does the color inside .content.

    CodePen Embed Fallback

    How can we stop this from happening? I want to prevent the text inside of .content from inheriting the .parent‘s color.

    Just ignore it!

    The first solution is no solution at all! This may be the most-used approach since most developers can live their lives without the joys of donut scoping (crazy, right?). Let’s be more tangible here, it isn’t just blatantly ignoring it, but rather accepting CSS’s global scope and writing styles with that in mind. Back to our first example, we assume we can’t stop the parent’s styles from leaking inwards to the content component, so we write our parent’s styles with less specificity, so they can be overridden by the content styles.

    body {
      color: blue;
    }
    
    .parent {
      color: orange; /* Initial background */
    }
    
    .content {
      color: blue; /* Overrides parent's background */
    }
    CodePen Embed Fallback

    While this approach is sufficient for now, managing styles just by their specificity as a project grows larger becomes tedious, at best, and chaotic at worst. Components may behave differently depending on where they are slotted and changing our CSS or HTML can break other styles in unexpected ways.

    Two CSS properties walk into a bar. A barstool in a completely different bar falls over.

    Thomas Fuchs

    You can see how in this small example we have to override the styles twice:

    Dev Tools showing the body styles getting overridden twice

    Shallow donuts scopes with :not()

    Our goal then it’s to only scope the .parent, leaving out whatever may be inserted into the .content slot. So, not the .content but the rest of .parent… not the .content… :not()! We can use the :not() selector to scope only the direct descendants of .parent that aren’t .content.

    body {
      color: blue;
    }
    
    .parent > :not(.content) {
      color: orange;
    }

    This way the .content styles won’t be bothered by the styles defined in their .parent:

    CodePen Embed Fallback

    You can see an immense difference when we open the DevTools for each example:

    Dev Tools Comparison between specificity overrides and donut scopes

    As good as an improvement, the last example has a shallow reach. So, if there were another slot nested deeper in, we wouldn’t be able to reach it unless we know beforehand where it is going to be slotted.

    CodePen Embed Fallback

    This is because we are using the direct descendant selector (>), but I couldn’t find a way to make it work without it. Even using a combination of complex selectors inside :not() doesn’t seem to lead anywhere useful. For example, back in 2021, Dr. Lea Verou mentioned donut scoping with :not() using the following selector cocktail:

    .container:not(.content *) {
      /* Donut Scoped styles (?) */
    }

    However, this snippet appears to match the .container/.parent class instead of its descendants, and it’s noted that it still would be shallow donut scoping:

    TIL that all modern browsers now support complex selectors in :not()! 😍

    Test: https://t.co/rHSJARDvSW

    So you can do things like:
    – .foo :not(.foo .foo *) to match things inside one .foo wrapper but not two
    – .container :not(.content *) to get simple (shallow) “donut scope”

    — Dr Lea Verou (@LeaVerou) January 28, 2021

    Donut scoping with @scope

    So our last step for donut scoping completion is being able to go beyond one DOM layer. Luckily, last year we were gifted the @scope at-rule (you can read more about it in its Almanac entry). In a nutshell, it lets us select a subtree in the DOM where our styles will be scoped, so no more global scope!

    @scope (.parent) {
     /* Styles written here will only affect .parent */
    }

    What’s better, we can leave slots inside the subtree we selected (usually called the scope root). In this case, we would want to style the .parent element without scoping .content:

    @scope (.parent) to (.content) {
      /* Styles written here will only affect .parent but skip .content*/
    }

    And what’s better, it detects every .content element inside .parent, no matter how nested it may be. So we don’t need to worry about where we are writing our slots. In the last example, we could instead write the following style to change the text color of the element in .parent without touching .content:

    body {
      color: blue;
    }
    
    @scope (.parent) to (.content) {
      h2,
      p,
      span,
      a {
        color: orange;
      }
    }

    While it may seem inconvenient to list all the elements we are going to change, we can’t use something like the universal selector (*) since it would mess up the scoping of nested slots. In this example, it would leave the nested .content out of scope, but not its container. Since the color property inherits, the nested .content would change colors regardless!

    And voilà! Both .content slots are inside our scoped donut holes:

    CodePen Embed Fallback

    Shallow scoping is still possible with this method, we would just have to rewrite our slot selector so that only direct .content descendants of .parent are left out of the scope. However, we have to use the :scope selector, which refers back to the scoping root, or .parent in this case:

    @scope (.parent) to (:scope > .content) {
      * {
        color: orange;
      }
    }

    We can use the universal selector in this instance since it’s shallow scoping.

    CodePen Embed Fallback

    Conclusion

    Donut scoping, a wannabe feature coined back in 2011 has finally been brought to life in the year 2024. It’s still baffling how it appeared to sit in the back of our minds until recently, as just another consequence of CSS Global Scope, while it had so many quirks by itself. It would be unfair, however, to say that it went under everyone’s radars since the CSSWG (the people behind writing the spec for new CSS features) clearly had the intention to address it when writing the spec for the @scope at-rule.

    Whatever it may be, I am grateful we can have true donut scoping in our CSS. To some degree, we still have to wait for Firefox to support it. 😉

    This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

    Desktop

    ChromeFirefoxIEEdgeSafari
    118NoNo11817.4

    Mobile / Tablet

    Android ChromeAndroid FirefoxAndroidiOS Safari
    131No13117.4

    Solved by CSS: Donuts Scopes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleCollective #883
    Next Article Leveraging AI and Playwright for Test Case Generation

    Related Posts

    Security

    Nmap 7.96 Launches with Lightning-Fast DNS and 612 Scripts

    May 17, 2025
    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-40906 – MongoDB BSON Serialization BSON::XS Multiple Vulnerabilities

    May 17, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    The latest KB5049981 Patch Tuesday for Windows 10 causes SgrmBroker.exe to be terminated and users are quite worried about it

    Operating Systems

    Russian State Hackers Biggest Cyber Threat to US, UK and EU Elections

    Development

    Singapore updates OT security blueprint to focus on data sharing and cyber resilience

    Development

    These 6 gadgets are lifesavers for my backyard parties

    Development

    Highlights

    Hands on: Windows 11’s Start menu feature lets you send files to Android phone

    February 24, 2025

    Windows 11’s Start menu has a new feature called “Send files”, which allows you to…

    CVE-2025-27720 – Pixmeo Osirix MD Unencrypted Credential Disclosure

    May 8, 2025

    Java maven – IllegalArgumentException: Input must be set

    April 21, 2024

    The best Black Friday VPN deals 2024: Early sales live now

    November 1, 2024
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.