{"id":8146,"date":"2026-01-05T12:46:37","date_gmt":"2026-01-05T17:46:37","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=8146"},"modified":"2026-01-13T11:23:24","modified_gmt":"2026-01-13T16:23:24","slug":"how-to-scope-css-now-that-its-baseline","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/how-to-scope-css-now-that-its-baseline\/","title":{"rendered":"How to @scope CSS Now That It\u2019s Baseline"},"content":{"rendered":"\n<p>Firefox 146 now supports <code>@scope<\/code> in CSS, joining Chrome and Safari, meaning that it\u2019s now supported in all major web browsers, earning it the \u201cBaseline: Newly Available\u201d tag.<\/p>\n\n\n\n<p>This&nbsp;<code>@scope<\/code>&nbsp;at-rule defines a new scope context in CSS. The&nbsp;<code>:scope<\/code>&nbsp;pseudo-class represents the root of said context (otherwise known as the \u2018scope root\u2019), and all this means is that we have some new and exciting ways of writing and organizing CSS, so today I\u2019ll demonstrate the different ways of using&nbsp;<code>@scope<\/code>&nbsp;and the benefits of each one.<\/p>\n\n\n\n<p>You can use an <code>@scope<\/code> block in any CSS, be it within a stylesheet that you <code>&lt;link><\/code> up or a <code>&lt;style><\/code> block within HTML. In fact, the latter has some interesting properties we\u2019ll get to.<\/p>\n\n\n\n<p>Whichever way you use CSS, the rules are, by default, <em>globally<\/em> scoped. For example, in the demo below, even though the CSS rules are nested within the <code>&lt;main&gt;<\/code>, they apply to the <em>whole<\/em> document:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">style<\/span>&gt;<\/span><span class=\"css\">\n    <span class=\"hljs-comment\">\/* No scoping applied. Global scope. *\/<\/span>\n    <span class=\"hljs-selector-tag\">header<\/span>, <span class=\"hljs-selector-tag\">footer<\/span> {\n      <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from green r g b \/ <span class=\"hljs-number\">30%<\/span>);\n    }\n  <\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">footer<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">footer<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">footer<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">footer<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bNpZExp\" src=\"\/\/codepen.io\/anon\/embed\/bNpZExp?height=450&amp;theme-id=1&amp;slug-hash=bNpZExp&amp;default-tab=html,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bNpZExp\" title=\"CodePen Embed bNpZExp\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Styles in a CSS stylesheet are also globally scoped by default.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>@scope<\/code> in a <code>&lt;style&gt;<\/code> Block<\/h2>\n\n\n\n<p>However, <code>@scope<\/code> can be used to limit the CSS to the \u2018scope root\u2019 (which in this case is <code>&lt;main&gt;<\/code>, because the <code>&lt;style&gt;<\/code> is a direct child of <code>&lt;main&gt;<\/code>). In addition, within the <code>@scope<\/code> at-rule, <code>:scope<\/code> selects this scope root:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span>&lt;<span class=\"hljs-selector-tag\">header<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">header<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>&lt;<span class=\"hljs-selector-tag\">main<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">style<\/span>&gt;\n<\/span><\/span><mark class='shcb-loc'><span>    <span class=\"hljs-keyword\">@scope<\/span> {\n<\/span><\/mark><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/* Scope root *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-selector-pseudo\">:scope<\/span> { <span class=\"hljs-comment\">\/* Selects the &lt;main&gt; *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/* &lt;header&gt;\/&lt;footer&gt; within scope root *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        header, footer {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from green r g b \/ <span class=\"hljs-number\">30%<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  &lt;\/<span class=\"hljs-selector-tag\">style<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">header<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">header<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">section<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">section<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">footer<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">footer<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>&lt;\/<span class=\"hljs-selector-tag\">main<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>&lt;<span class=\"hljs-selector-tag\">footer<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">footer<\/span>&gt;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_pvyqPyx\" src=\"\/\/codepen.io\/anon\/embed\/pvyqPyx?height=450&amp;theme-id=1&amp;slug-hash=pvyqPyx&amp;default-tab=html,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed pvyqPyx\" title=\"CodePen Embed pvyqPyx\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>If needed, we can narrow the scope, or actually, <em>stop<\/em> the scoping at a particular second selector. In the example below I\u2019ve added a \u2018scope limit\u2019 \u2014 specifically, I\u2019ve defined the scope as from <code>&lt;main&gt;<\/code> (implicitly) to <code>&lt;section&gt;<\/code>. The scope is non-inclusive, so <code>:scope &gt; *<\/code> selects the <code>&lt;header&gt;<\/code> and <code>&lt;footer&gt;<\/code> (excluding <code>&lt;section&gt;<\/code>, since it\u2019s outside of the scope). <code>main<\/code> and <code>section<\/code> don\u2019t select anything as, again, they\u2019re out-of-scope, but we can continue to select <code>&lt;main&gt;<\/code> by using <code>:scope<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span>&lt;<span class=\"hljs-selector-tag\">main<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">style<\/span>&gt;\n<\/span><\/span><mark class='shcb-loc'><span>    <span class=\"hljs-keyword\">@scope<\/span> to (section) {\n<\/span><\/mark><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/* Selects nothing *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-selector-tag\">main<\/span>, <span class=\"hljs-selector-tag\">section<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attribute\">color<\/span>: red;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/* However, this selects &lt;main&gt; *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-selector-pseudo\">:scope<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attribute\">font-weight<\/span>: bold;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/* Selects scoped direct children *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-selector-pseudo\">:scope<\/span> &gt; * {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from green r g b \/ <span class=\"hljs-number\">30%<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/* This also works *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      &amp; &gt; * {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from green r g b \/ <span class=\"hljs-number\">30%<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/* As does this *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      &gt; * {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from green r g b \/ <span class=\"hljs-number\">30%<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  &lt;\/<span class=\"hljs-selector-tag\">style<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">header<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">header<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">section<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">section<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>  &lt;<span class=\"hljs-selector-tag\">footer<\/span>&gt;&lt;\/<span class=\"hljs-selector-tag\">footer<\/span>&gt;\n<\/span><\/span><span class='shcb-loc'><span>&lt;\/<span class=\"hljs-selector-tag\">main<\/span>&gt;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_JoXxmVG\" src=\"\/\/codepen.io\/anon\/embed\/JoXxmVG?height=450&amp;theme-id=1&amp;slug-hash=JoXxmVG&amp;default-tab=html,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed JoXxmVG\" title=\"CodePen Embed JoXxmVG\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>When you use the <code>to<\/code> keyword it is <a href=\"https:\/\/www.stubbornella.org\/2011\/10\/08\/scope-donuts\/\">known as<\/a> <a href=\"https:\/\/www.stubbornella.org\/2011\/10\/08\/scope-donuts\/\">\u2018<\/a><a href=\"https:\/\/css-tricks.com\/almanac\/rules\/s\/scope\/#donut-scope\">donut scope<\/a><a href=\"https:\/\/www.stubbornella.org\/2011\/10\/08\/scope-donuts\/\">\u2019<\/a>. In the image below you can see why (the rings of the donuts include the scope root and scope limit, but it\u2019s what\u2019s in the donut hole that\u2019s actually included in the scope):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/paper-attachments.dropboxusercontent.com\/s_05A451BC8E0C7AF681B379AA604FF16D6795164F6C12DE76D454EB6091BC131E_1766605018994_donut-scope.png?ssl=1\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Nothing inside that <code>&lt;section&gt;<\/code> could be selected at all, because the &#8220;donut&#8221; stops there. That&#8217;s the hole in the donut. <\/figcaption><\/figure>\n\n\n\n<div class=\"wp-block-group learn-more\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p>The <code>&lt;style&gt;<\/code> element itself can be selected by the universal selector (<code>*<\/code>), so if you were to, for example, set <code>display<\/code> to anything other than <code>none<\/code>, the CSS would hilariously output as a raw string (but still work, somehow):<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_PwNMJYd\" src=\"\/\/codepen.io\/anon\/embed\/PwNMJYd?height=250&amp;theme-id=1&amp;slug-hash=PwNMJYd&amp;default-tab=css,result\" height=\"250\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed PwNMJYd\" title=\"CodePen Embed PwNMJYd\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>To get really weird, style the <code>&lt;style&gt;<\/code> element like you would a <code>&lt;pre&gt;<\/code> tag and add the <code>contenteditable<\/code> attribute! <\/p>\n<\/div><\/div>\n\n\n\n<p>Notably, you don&#8217;t <em>need<\/em> to use the <code>:scope<\/code> selector or an equivalent, that&#8217;s just helpful for clarity or adding specificity to the selector if needed. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">style<\/span>&gt;<\/span><span class=\"css\">\n    <span class=\"hljs-keyword\">@scope<\/span> {\n      <span class=\"hljs-selector-tag\">color<\/span>: <span class=\"hljs-selector-tag\">red<\/span>;\n    }\n  <\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n  \n  I'll be red.\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>I'll be whatever color is inherited.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The potential benefits of scoping in a <code>&lt;style&gt;<\/code> block are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>HTML and CSS are kept together<\/li>\n\n\n\n<li>No external resource to download (even if you load CSS asynchronously to stop it from render-blocking, which risks <a href=\"https:\/\/web.dev\/articles\/cls\">Cumulative Layout Shift<\/a> anyway, external resources must be downloaded in full before they can be rendered, which isn\u2019t ideal)<\/li>\n\n\n\n<li>CSS always renders with the HTML, which means no Cumulative Layout Shift, and when not deferring non-<a href=\"https:\/\/www.smashingmagazine.com\/2015\/08\/understanding-critical-css\/\">critical CSS<\/a>, is the best way to prioritize resources efficiently<\/li>\n<\/ul>\n\n\n\n<p>Keep in mind that the CSS will output more times than is necessary if you\u2019re reusing the component, which is very anti-<a href=\"https:\/\/medium.com\/@nipun.rajput6586s\/the-dry-principle-in-programming-a-comprehensive-guide-e0504a4b393a\">DRY<\/a> and why you\u2019ll want to combine this type of scoped CSS with other types of CSS where appropriate. With that in mind, let\u2019s talk about using <code>@scope<\/code> with internal and external CSS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code>@scope<\/code> in a CSS file<\/h2>\n\n\n\n<p>When using <code>@scope<\/code> with a CSS file, we must specify the scope root (and optionally the end scope) within the <code>@scope<\/code> at-rule manually, like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@scope<\/span> (main) to (section) {\n  &gt; * {\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(from green r g b \/ <span class=\"hljs-number\">30%<\/span>);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This is actually true for <code>&lt;style><\/code> blocks as well. If you specify the scope with a selector (with or without the <code>to<\/code> selector), it will behave the same way. The distinction with a <code>&lt;style><\/code> block is when you <em>don&#8217;t<\/em> specify a selector, the scope becomes the parent element.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_WbwLjrJ\" src=\"\/\/codepen.io\/anon\/embed\/WbwLjrJ?height=450&amp;theme-id=1&amp;slug-hash=WbwLjrJ&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed WbwLjrJ\" title=\"CodePen Embed WbwLjrJ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The benefit of this method is that it\u2019s DRY, so you won\u2019t be repeating yourself. However, there are quite a few drawbacks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CSS is a render blocking resource<\/li>\n\n\n\n<li>Potential Cumulative Layout Shift (if loading external CSS asynchronously)<\/li>\n\n\n\n<li>HTML and CSS <em>aren\u2019t<\/em> kept together<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"other-ways-to-use-scope\">Other ways to use <code>:scope<\/code><\/h2>\n\n\n\n<p>What\u2019s interesting is that, if we use <code>:scope<\/code> outside of <code>@scope<\/code>, it selects the <em>global<\/em> scope root, which in HTML is <code>&lt;html&gt;<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* Global scope root *\/<\/span>\n<span class=\"hljs-selector-tag\">html<\/span> { }\n\n<span class=\"hljs-comment\">\/* Selects the same thing *\/<\/span>\n<span class=\"hljs-selector-pseudo\">:root<\/span> { }\n\n<span class=\"hljs-comment\">\/* Selects the same thing! *\/<\/span>\n<span class=\"hljs-selector-pseudo\">:scope<\/span> { }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I don\u2019t know why we\u2019d select <code>:scope<\/code> instead of <code>:root<\/code> or <code>html<\/code>, but it makes sense that we can do so, and explains why <code>:scope<\/code> was supported before <code>@scope<\/code>.<\/p>\n\n\n\n<p><code>:scope<\/code> can also be used in the <code>querySelector()<\/code>, <code>querySelectorAll()<\/code>, <code>matches()<\/code>, and <code>closest()<\/code> JavaScript DOM APIs, where <code>:scope<\/code> refers to the element on which the method is called. Take the following HTML markup, for example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    Child div\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Grandchild div<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>While trying to select the direct child <code>&lt;div&gt;<\/code> only:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>section.querySelectorAll(\"div\").forEach(e =&gt; e.style.marginLeft = \"3rem\")<\/code> undesirably but expectedly selects both <code>&lt;div&gt;<\/code>s<\/li>\n\n\n\n<li><code>section.querySelectorAll(\"&gt; div\").forEach(e =&gt; e.style.marginLeft = \"3rem\")<\/code> doesn\u2019t work (even though, as demonstrated earlier, <code>&gt; div<\/code> <em>would<\/em> work in CSS)<\/li>\n\n\n\n<li>Luckily, <code>section.querySelectorAll(\":scope &gt; div\").forEach(e =&gt; e.style.marginLeft = \"3rem\")<\/code> targets the child div only, as desired<\/li>\n\n\n\n<li><code>section.querySelectorAll(\"&amp; > div\").forEach(e => e.style.marginLeft = \"3rem\")<\/code> also works, as it would in CSS<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GgZPgpR\" src=\"\/\/codepen.io\/anon\/embed\/GgZPgpR?height=450&amp;theme-id=1&amp;slug-hash=GgZPgpR&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GgZPgpR\" title=\"CodePen Embed GgZPgpR\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Fun fact, we can also use <code>&amp;<\/code> instead of <code>:scope<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">&amp; {\n  <span class=\"hljs-comment\">\/* Instead of html, :root, or :scope *\/<\/span>\n}\n\n<span class=\"hljs-keyword\">@scope<\/span> (main) to (section) {\n  &amp; {\n    <span class=\"hljs-comment\">\/* Instead of :scope *\/<\/span>\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id=\"a-well-balanced-approach-to-serving-and-writing-scoped-css\">A Well-Balanced Approach to Serving and Writing Scoped CSS<\/h2>\n\n\n\n<p>I was really looking forward to <code>@scope<\/code>, and it securing full browser support in the last minute of 2025 made it my feature of the year. Regardless of what types of websites you build, you\u2019ll find all ways of implementing <code>@scope<\/code> quite useful, although I think you\u2019ll often use all implementations together, in harmony.<\/p>\n\n\n\n<p>It will depend on the type of website that you\u2019re building and how much you want to balance CSS organization with web performance.<\/p>\n\n\n\n<p>Personally, I like splitting CSS into reusable modules, including them as internal CSS using templating logic only when needed (e.g., <code>forms.css<\/code> on <code>\/contact<\/code>), and then using in-HTML scoped <code>&lt;style&gt;<\/code>s for one-time or once-per-page components. That way we can avoid render-blocking external CSS without causing Cumulative Layout Shift (CLS) and still have fairly organized CSS. One thing to consider though is that CSS isn\u2019t cached with these methods, so you\u2019ll need to determine whether they\u2019re worth that.<\/p>\n\n\n\n<p>If you\u2019re building heavy front-ends, caching external CSS will be better and fewer bytes overall, but you can totally serve CSS using all methods at once (as appropriate) and use <code>@scope<\/code> with all of them.<\/p>\n\n\n\n<p>In any case though, the ultimate benefit is, of course, that we\u2019re able to write much simpler selectors by defining new scope roots.<\/p>\n\n\n\n<p>All in all, the future of CSS could look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* global.css *\/<\/span>\n<span class=\"hljs-selector-tag\">body<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#111<\/span>;\n}\n\n<span class=\"hljs-selector-tag\">section<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#eee<\/span>;\n\n  h2 {\n    <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#000<\/span>;\n  }\n}\n\n\n<span class=\"hljs-comment\">\/* home.css *\/<\/span>\n<span class=\"hljs-keyword\">@scope<\/span> (section.home-<span class=\"hljs-keyword\">only<\/span>) {\n  <span class=\"hljs-selector-pseudo\">:scope<\/span> {\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#111<\/span>;\n\n    h2 {\n      <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#fff<\/span>;\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-meta\">&lt;!DOCTYPE <span class=\"hljs-meta-keyword\">html<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- Site-wide styles --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"stylesheet\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"global.css\"<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n    \/* Reusable BUT critical\/above-the-fold, so not for global.css *\/\n    @scope (header) {\n      height: 100vh;\n    }\n\n    \/* Include home.css conditionally *\/\n    {% if template.name == \"index\" %}{% render \"home.css\" %}{% endif %}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Critical\/above-the-fold content<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Default section (styles from external CSS)<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"home-only\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Home-only section (styles from internal CSS)<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"home-only\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Home-only section (styles from internal CSS)<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"home-only\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Home-only section (styles from internal CSS)<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n        @scope {\n          :scope {\n            background: #f00;\n\n            h2 {\n              color: #fff;\n            }\n          }\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Unique section (styles from in-HTML CSS)<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This is a very simple example. If we imagine that <code>section.home-only<\/code> is a much more complex selector, <code>@scope<\/code> enables us to write it once and then refer to it as <code>:scope<\/code> thereafter.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_emzYPaZ\" src=\"\/\/codepen.io\/anon\/embed\/emzYPaZ?height=450&amp;theme-id=1&amp;slug-hash=emzYPaZ&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed emzYPaZ\" title=\"CodePen Embed emzYPaZ\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n","protected":false},"excerpt":{"rendered":"<p>There is a way to declare a scope on a specific selector, a specific selector *down to* another selector, or with no selector at all (which scopes to its parent in the DOM). <\/p>\n","protected":false},"author":37,"featured_media":8157,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[243,7],"class_list":["post-8146","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-scope","tag-css"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/01\/scope.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8146","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/37"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=8146"}],"version-history":[{"count":19,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8146\/revisions"}],"predecessor-version":[{"id":8262,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8146\/revisions\/8262"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/8157"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=8146"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=8146"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=8146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}