We've got @scope in CSS now, and it's got it's uses. But the concept of scope in CSS is a wider idea.
Info
This is a written adaptation of my talk at CSS Day 2025. It was a lovely event, but I realize life is complicated and not everyone can make it to events like this. There are videos up paywalled at conffab.com. I figure this written version can make my points as well.
Chris Coyier presenting on stage at a conference, with a large audience seated in front of them. A screen displays coding content related to CSS.I hate to break it to ya, but CSS is⊠mostly scope. The act of writing CSS is to:
Write some scope
Write some styles
Even something like .cool is pretty extreme scoping. It only effects elements in the DOM that happen to have that exact class. OoooOoo rare.Itâs easy to think of CSS skill of being about knowing how to accomplish all the fancy styling bits. But the best CSS developers are also talented at knowing where and how to apply those styles. Scope.Selectors are a big part of scoping, but there are other things in CSS that apply scope like @media queries, @support, and other at-rules.Say we need to style a real-world design like this.This part we could easily call a .card and apply reusable styles accordingly.Later, a new element comes up that is fairly card-like. Should we re-use the class? Weâre at a mental CSS crossroads. The padding is a bit different, the colors are a bit different, there is no border-radiusâŠâŠ meh. Screw it. Letâs call this one .challenge-card.
Weâve accomplished scoping with our little olâ fingers and brain.99% of all the CSS scoping Iâve done in my life has been just like the above. Just use a different name and selector in order to avoid unwanted style collisions. Done.There are problems that can come up here though. And there are tools to help with those problems.
But letâs reach for tools when we actually have the problem, not because the theoretical problem exists.Regular ol CSS is âglobalâ in nature.
Maybe your brain keeps thinking of using .card as a class name, only to discover itâs already being used. Worse, you write up your styles using that name, it all looks great, and you donât realize youâve steamrolled other parts of the site with your new clashing styles. Derp.Two different developers at different times can choose the same names and selectors and cause issues, possibly without even knowing it. Thatâs probably the worst and most insidious problem.We could call that âThe Barstool Problemâ.
That is, writing CSS that affects styles elsewhere that you didnât intend.If you face problems like this, which Iâd say is a reasonable concern as soon as 2 or more people are working on a project, or the project is of Mediumâą size or bigger, it may be time to tool the problem away.There is stuff in the web platform to help⊠sort of. Weâll get to that. But letâs do userland tools first.One approach to scoping styles is to process the HTML/CSS/JavaScript that we write such that the selectors being applied are unique and wonât cause The Barstool Problem.CSS Modules is interesting in that this kind of scoping is the only thing that it does. Itâs whole reason for existence is scoping class names.
Itâs also 10 years old just recently, congrats CSS Modules!
I love how it was worked on in 2015 and thatâs basically it. Feature complete for 10 years.Hereâs how it works.
You write regular CSS (nice!).
The CSS that ends up on the page is that class plus some gibberish, which accomplishes the scoping part.How does the HTML know to match to that class? You import the styles in your JavaScript. What you get is an object that maps the class names you wrote to the gibberish ones that will match.
Thatâs it really. So CSS Modules is only relevant in JavaScript-produced markup.No barstools will be kicked over in this situation. If 50 developers all use .card as a class name, they wonât clash.
In practice, giving the top-most element of the component a class name of root is nice because standard conventions mean you donât have to think too hard about it.I also enjoy how CSS Modules is just an idea. Tons of bundler-type applications support it, but they do so by just following the spec of how it should work.Out in âthe real worldâ, hereâs a React component that imports a CSS files that uses CSS modules conventions (which also use Sass just for fun, itâs being processed anyway). The className is applied.
Crucially, this co-locates the styles and the component.When you scope and co-locate styles, it really lessens the issue of âunused CSSâ. All-global CSS tends to grow over time and lead to CSS files that developers are (rightfully) afraid to touch (barstools) and that are essentially wasted bandwidth.
Scoped, co-located CSS means itâs less likely there is unused CSS in those files. If the component isnât used, the CSS doesnât load at all (typically).Junk the component, junk the styles along with it. No more orphaned styles.This style co-location happens in the concept of CSS-in-JS as well. Usually not my favorite approach to CSS, but I do like the idea of keeping the styling job close to the components themselves.I do not think the world is better off because React is unopinionated about styles. We would have been better off if they just had a blessed styling technique.Vue has a blessed styling technique which does scoping and itâs perfectly nice.I like how the [data-attribute] it adds means you get to keep the exact class you authored too, which wonât change over time, so youâre maintaining nice hooks for peopleâs user stylesheets.Svelte also has a blessed styling technique which scopes and itâs fine.Can the web itself help us with selector scoping? Maybe not exactly like this, but weâll see. The scoping it traditionally provides is either super loose (just use a different class) or super hardcore.Websites can include other⊠websites. By way of the venerable <iframe>, of course. They are fraught with challenges, but they were foundational to the last three companies I worked at, so respect.Also known as iFrames, of course.Designed by Apple in California.For real though, you can show web content with them, and thereâs no way CSS from the outside will leak in or CSS from inside leak out. But they are a brick wall and not a practical choice just for this purpose alone.Shadow DOM is also pretty hardcore scoping. Styles that inherit can sneak through a shadow DOM boundary (including custom properties), but little else. This hardcore scoping is largely why it exists.The first time I came across the shadow DOM was when I advocated for <use> in SVG as a better alternative for icons than using icon fonts. When you <use> another bit of SVG, it clones it into a shadow root and becomes an <svg> of itâs own.
Form elements often use shadow roots to abstract away their UI implementations as well.We can make our own shadow DOM now with Web Components. But bear in mind you donât have to. Keeping to the light DOM can be awfully nice.As weâve covered, scoping can be kinda great, and the fact that we can opt-in to it in Web Components is nice. Itâs a one-liner to make a custom element have a shadow DOM.
Here, Iâm injecting a <style> tag into that shadow DOM, which will inherently be scoped.The selector used in that style tag is simply: button. And notice that only the <button> that is within the Web Component is styled with it, not the <button> outside of it. Sometimes thatâs exactly the goal.Imagine not just a button but an entire system of components that only look the way they do because of baked-in scoped styles on themselves. No worries of their styles affecting other elements, or, for the most part, other styles coming in and screwing things up.
Sounds pretty good for design systems right?Thatâs not just an âoh yeah, hmmm, maybe that could workâ situation. Pretty much all the major design systems ship with shadow DOM scoping.When we talk about styling via classes, there used to be several different names of projects we could point to, but these days letâs be real: itâs Tailwind.Since you arenât writing classes or other selectors to do styling in Tailwind, the styles you are applying are already scoped.
Even if itâs not the main reason people choose a tool like Tailwind, it is a potential benefit.Even if I admit the approach isnât for me, Tailwind does have some objectively good characteristics. One being that the CSS output is generally a good bit smaller than ânormalâ because of the de-duplication. Perhaps the HTML is a bit bigger, but itâs CSS that is more of a blocking resource, so itâs probably a net-win.
I mostly donât like it because it feels like you need to know CSS anyway, and now youâve got to use this leaky abstraction on top of it, and when Iâve tried it I feel like Iâm not buying much.CSS actually has a thing called @scope now. Props to Miriam Suzanne for brainchilding and shepherding it.
My knowledge of @scope is just from playing around and reading the work of people who write on MDN and for Googleâs sites and fellow bloggers. So thank you to everyone who makes technical content. I probably read it.If youâre wondering about browser support, basically the story is itâs waiting for Firefox, which already has it under a flag. Itâs in Interop 2025, so it shouldnât be too long.
Iâd say it doesnât progressively enhance terribly well, so chill on it a sec.The first glance at the syntax had me feeling it left, uh, something to be desired.Hereâs some perfectly reasonable HTML and some perfectly reasonable CSS to go with it.Rather than writing .card {} we could write @scope (.card) {} and itâs⊠almost exactly the same functionality just more characters to type.
At least, that was my initial impression.For the record, we should be able to test the support of it like this. But we canât. Because at-rule(), while agreed upon (w3c/csswg-drafts), isnât implemented anywhere yet. When it is, itâs likely any browser that supports it will also support @scope so this will probably never be a useful combo.While that very basic demo of the syntax isnât dripping with usefulness, there are some somewhat niche things that @scope can do that do have their uses.Nicole Sullivan blogged scope donuts in 2011 and itâs just now a thing. So thatâs a lesson for you. If you want something in the web platform, make a good case for it then wait 15 years.
Donut scoping is a way to select an element and allow descendent selectors as usual, up until a point you specify where descendent selectors no longer apply.I always think of element that contain body copy as a use case. Like chunks of converted markdown are special little bubbles with their own styles and you want to prevent other styles leaking int there that you donât want.
Hereâs an example of an article on WIRED where we can clearly see some body copy (and non-body copy).Links within body copy should be underlined. And they do that. Good job WIRED.Itâs arguable, but links in some other places may not need to be underlined as there is enough visual affordance that they are links anyway, like in navigation or article cards.A common way to handle links-in-some-areas vs not-in-others is to let links be underlined by default, then select areas to remove them and remove them.The reverse of that is to remove link underlines everywhere, and re-apply them places you want them, like in body copy content.Donut scoping gives us a rather elegant way to express this idea. Remove links everywhere except within element with a class name of content. That reads decently well to me.Donuts can have more than one hole, depending on what youâre doing in the DOM.Massive upheaval in how we write CSS? No. But itâs a nice little niche tool and CSS is better for having it.Proximity scope is rather amazing in that itâs an entirely new âlevelâ by which the browser decides which CSS rules to apply. Weâll see that in a moment as we look at a basic use case.I wouldnât say itâs ultra common, but one way to implement color themes is to use class names on wrapper elements that signify which theme is to be used there.Say we have a dark and a light theme, those themes have more jobs to do that set one background-color and one color. It might change lots of stuff.
Think about link colors. Blue links may need to be lighter on a dark color and darker on a light color. So weâre expressing that here with an oklch() color that adjusts the lightness.Themes could be nested. Again maybe not ultra-common, but if they are just classes, it could be done. Imagine an entire page in light mode, but the footer swaps to dark mode as itâs a nice look. That might end up nested if weâre talking likeâŠ
If we define our themes as single classes one after another, we might run into problems. Each theme will have an indentical specificity, so the last one declared will be the âmost powerfulâ because of source order.
See the links now. Those links are technically within an element with theme-lightandtheme-dark. But theme-light is âmore powerfulâ as itâs declared last, and thus we get the wrong color.Scope can help here. By replacing those theme selectors with like @scope (.theme-dark) {} now proximity scoping kicks in.Proximity is less powerful than specificity of selectors, but itâs more powerful than source order. Thatâs a little weird to wrap your mind around at first, I think. But this simple demo hopefully helps.Because the theme-light class is âcloserâ in the DOM to those links, it will âwinâ. Source order doesnât matter here anymore, it matters which of the otherwise-equal selectors has higher proximity.Big sea change in CSS? Again, no, but CSS is better off for having this niche tool.Anytime source order might be a concern, itâs possibly proximity styling can step in to help.Imagine variation classes on a âcardâ element. If the variation classes have the same specificity as the base class, the worry is the base class will override what the variation is doing.
This could be the case in systems that bundle CSS in ways you donât fully understand, or load CSS on demand in ways that even user actions may affect the order.See this bit of HTML. Two <div>s, one of them with a <style> tag inside and one without.
Inside that <style> tag we immediately see an @scope at-rule, with no parens at all. What is the scope then? Used like this, the scope is the parent element of the <style>, so the <div> parent. From there, we can select that <div> or any descendants.All the sudden we have a way to style from just one particular element downwards without even having to name it order use any selector at all.I said the web platform doesnât really have selector scoping help, but this is basically that. This is probably as close as weâll ever get.
It feels like a pretty powerful concept to me.What if we just did this âinsteadâ of linking up CSS files and finding scoping solutions there? Maybe this is just how we style most everything on the page. Perhaps from the âcomponentâ level on down. Is that a reasonable thing to do?I did attempt to build a demo (chriscoyier) with 1,000 cards where one page loaded CSS âregularlyâ with one stylesheet that applies to all the cards. Then another page where each of the 1,000 cards has a @scope-d <style> block within it.Youâd think the 1,000 extra <style> blocks would be awful for performance, but, quite weirdly, the difference was almost undetectable. I think this was a poor test though as there was so much of the exact same code the browser was probably great at optimizing it. A better test would be tons of totally different components and include interactivity.At a minimum, sprinkling in scoped styles into the DOM where you just want a bit of styles that will never âleak outâ and screw up anything else is super cool.Slide with a blue background and yellow text titled 'The Takeaways'.
CSS is naturally scoped. That might be all you need.
Selector scoping is pretty sweet on large sites with teams. Lots of existing tools help with selector scoping, or avoid the need for it.
Native CSS @scope is nice to have. Donus scoping and proximity are a small upgrade to CSS.
<style> block @scope might be great. Tool-free component scoping. Can we make it performant?