{"id":"l-css-1-2","courseId":"css-mastery","lang":"en","kind":"lesson","summary":"Calculate the specificity of any selector, and use :is() to group selectors and :where() to write base styles that components can override without a fight.","html":"<p>Waypoint’s schedule page started life as a quick prototype, and its stylesheet shows it. Somewhere near the top sits <code dir=\"ltr\">#schedule article a { color: #1d4ed8; }</code>, and every time the design team adds a button-styled link inside a session card, the button text turns blue. Someone “fixed” it last month with <code dir=\"ltr\">!important</code>. Now nobody can theme the button.</p>\n<p>This is a specificity problem, and the fix isn’t a bigger hammer. It’s choosing how much weight each selector should carry.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Specificity is three numbers, not one</h2>\n<p>The browser scores every selector as a triple, often written (A, B, C):</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Column</th>\n<th>Counts</th>\n<th>Examples</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>A</td>\n<td>ID selectors</td>\n<td><code dir=\"ltr\">#schedule</code></td>\n</tr>\n<tr>\n<td>B</td>\n<td>classes, attributes, pseudo-classes</td>\n<td><code dir=\"ltr\">.session</code>, <code dir=\"ltr\">[type=&quot;checkbox&quot;]</code>, <code dir=\"ltr\">:hover</code></td>\n</tr>\n<tr>\n<td>C</td>\n<td>type selectors, pseudo-elements</td>\n<td><code dir=\"ltr\">article</code>, <code dir=\"ltr\">a</code>, <code dir=\"ltr\">::before</code></td>\n</tr>\n</tbody>\n</table></div><p>The universal selector <code dir=\"ltr\">*</code> and combinators (space, <code dir=\"ltr\">&gt;</code>, <code dir=\"ltr\">+</code>, <code dir=\"ltr\">~</code>) add nothing. To compare two selectors, compare column A; only if it ties, compare B; only if that ties, compare C. There is no carrying: eleven classes <code dir=\"ltr\">(0,11,0)</code> still lose to one ID <code dir=\"ltr\">(1,0,0)</code>. The old advice to “add up the points” (100 per ID, 10 per class) breaks exactly there, so forget it.</p>\n<p>Score the Waypoint conflict:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">CSS</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#schedule</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> article</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1d4ed8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* (1, 0, 2) */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.btn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#fff</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }                     </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* (0, 1, 0) */</span></span></code></pre></div>\n<p>The ID in the first selector wins in column A, so the button text turns blue. Moving <code dir=\"ltr\">.btn</code> lower in the file changes nothing.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why IDs hurt in stylesheets</span></p>\n<p>An ID selector outranks every class-based selector you will ever write. One ID in a base rule forces every component that touches the same property to include an ID too, or reach for <code dir=\"ltr\">!important</code>. Keep IDs for anchors and JavaScript hooks; style with classes.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\"><code dir=\"ltr\">:is()</code>: grouping with a catch</h2>\n<p><code dir=\"ltr\">:is()</code> takes a selector list and matches if any of them match. It saves you from repeating long selectors:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">CSS</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* Before */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> h4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">text-wrap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">balance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* After */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> :is</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#116329;--shiki-dark:#7EE787\">h4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">text-wrap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">balance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span></code></pre></div>\n<p>The catch is the specificity rule: <code dir=\"ltr\">:is()</code> takes the specificity of its <strong>most specific argument</strong>, whichever one actually matched. <code dir=\"ltr\">:is(#featured, .session) h2</code> scores (1,0,1) on every <code dir=\"ltr\">.session h2</code>, even ones with no ID nearby. <code dir=\"ltr\">:not()</code> and <code dir=\"ltr\">:has()</code> follow the same rule.</p>\n<p><code dir=\"ltr\">:is()</code> also uses a <strong>forgiving selector list</strong>. In a plain list like <code dir=\"ltr\">.a, .b:unknown</code>, one unrecognised selector invalidates the whole rule. Inside <code dir=\"ltr\">:is()</code> the browser drops only the bad argument and keeps the rest.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\"><code dir=\"ltr\">:where()</code>: the zero-weight wrapper</h2>\n<p><code dir=\"ltr\">:where()</code> matches exactly like <code dir=\"ltr\">:is()</code>, with one difference: its specificity is always <strong>zero</strong>, no matter what’s inside. That makes it the tool for any style that is meant to be a default:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">base.css</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* Base link style: (0,0,1) because only the trailing `a` counts */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">:where</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#schedule</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1d4ed8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  text-decoration</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">underline</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* Component: (0,1,0) beats (0,0,1) */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.btn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#fff</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#6d28d9</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  text-decoration</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">none</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The base rule still only applies inside <code dir=\"ltr\">#schedule</code> articles, so its <em>reach</em> is unchanged. Only its <em>weight</em> dropped. Now any class-based component wins without tricks.</p>\n<p>A useful default for a whole codebase: wrap resets and element defaults in <code dir=\"ltr\">:where()</code>, write components with a single class, and keep state selectors (<code dir=\"ltr\">.btn:hover</code>, <code dir=\"ltr\">.btn[aria-pressed=&quot;true&quot;]</code>) one step heavier than the component.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Wrapping the whole selector when only part should be light</span></p>\n<p><code dir=\"ltr\">:where(.session .title)</code> scores zero, so even a stray <code dir=\"ltr\">h2 { color: … }</code> in some other file beats it. Wrap only the context you want weightless and leave the part that identifies the element outside: <code dir=\"ltr\">:where(.session) .title</code> keeps (0,1,0).</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Four ways to win a specificity fight</h2>\n<p>When a rule loses, you have four options. Here they are in the order I reach for them:</p>\n<ol>\n<li><strong>Lower the loser.</strong> If the winning rule is a base style that shouldn’t be heavy, wrap its context in <code dir=\"ltr\">:where()</code>. This fixes the cause, and every future component benefits.</li>\n<li><strong>Move whole groups apart with layers.</strong> When the conflict is between kinds of CSS (resets versus components versus utilities), cascade layers settle it for good. That’s the next lesson.</li>\n<li><strong>Raise the winner.</strong> Repeating a class, as in <code dir=\"ltr\">.btn.btn</code>, adds (0,1,0) without changing what matches. It’s a legitimate, local hack, but every repetition is a debt the next override has to pay.</li>\n<li><strong><code dir=\"ltr\">!important</code>.</strong> Keep it for utilities that must always win and for accessibility overrides. Used to patch a single conflict, it starts an arms race, because the only way to beat it is another <code dir=\"ltr\">!important</code>.</li>\n</ol>\n<h2 id=\"sec-5\" tabindex=\"-1\">Reading specificity in DevTools</h2>\n<p>You don’t have to count by hand forever. Hover a selector in the Chrome or Firefox Styles panel and the tooltip shows its specificity triple. Use it to check your mental arithmetic for a week; after that, you’ll rarely need it.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">Your turn</h2>\n<p>The exercise gives you the Waypoint session card with the prototype’s link rule. Lower the base rule’s weight so the button looks like a button again, while normal links keep their blue underline. You’ll fix this at a larger scale in the next lesson, where cascade layers let you decide which <em>groups</em> of styles win, before specificity is even consulted.</p>\n","toc":[{"id":"sec-1","text":"Specificity is three numbers, not one","level":2},{"id":"sec-2","text":":is(): grouping with a catch","level":2},{"id":"sec-3","text":":where(): the zero-weight wrapper","level":2},{"id":"sec-4","text":"Four ways to win a specificity fight","level":2},{"id":"sec-5","text":"Reading specificity in DevTools","level":2},{"id":"sec-6","text":"Your turn","level":2}],"takeaways":["Specificity is three separate counts (IDs, then classes/attributes/pseudo-classes, then types/pseudo-elements) compared column by column, never added into one number.","<code dir=\"ltr\">:is()</code>, <code dir=\"ltr\">:not()</code> and <code dir=\"ltr\">:has()</code> take the specificity of their most specific argument, even when a less specific argument is the one that matched.","<code dir=\"ltr\">:where()</code> always has zero specificity, which makes it the right wrapper for resets and base styles that should lose to any component.","<code dir=\"ltr\">:is()</code> and <code dir=\"ltr\">:where()</code> use forgiving selector lists: one invalid selector inside them doesn’t throw away the whole rule."],"further":[{"title":"Specificity (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Specificity"},{"title":":where() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:where"},{"title":":is() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:is"}],"quiz":[{"q":"<p>What is the specificity of <code dir=\"ltr\">nav#main a.active:hover</code>?</p>","options":[{"text":"(1, 2, 2)","why":"Correct. One ID (<code dir=\"ltr\">#main</code>); one class plus one pseudo-class (<code dir=\"ltr\">.active</code>, <code dir=\"ltr\">:hover</code>); two types (<code dir=\"ltr\">nav</code>, <code dir=\"ltr\">a</code>)."},{"text":"(1, 1, 2)","why":"Pseudo-classes such as <code dir=\"ltr\">:hover</code> count in the middle column alongside classes, so there are two there, not one."},{"text":"(1, 2, 1)","why":"Both <code dir=\"ltr\">nav</code> and <code dir=\"ltr\">a</code> are type selectors, so the last column is 2."},{"text":"(0, 3, 2)","why":"<code dir=\"ltr\">#main</code> is an ID selector, which belongs in the first column, not with the classes."}],"answer":0},{"q":"<p>Which color does the link get?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">CSS</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">:is</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#sidebar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">red</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.card</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> .card-link</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">blue</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"card\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"card-link\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> href</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"#\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Read&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n","options":[{"text":"blue, because the element matched through <code dir=\"ltr\">.card</code>, not <code dir=\"ltr\">#sidebar</code>.","why":"<code dir=\"ltr\">:is()</code> doesn’t care which argument matched. It always takes the specificity of its most specific argument, here an ID."},{"text":"red, because <code dir=\"ltr\">:is()</code> takes the specificity of <code dir=\"ltr\">#sidebar</code>.","why":"Correct. The first rule scores (1,0,1) and beats (0,2,0), even though the element has no ID at all."},{"text":"blue, because it comes later in the stylesheet.","why":"Order only matters when specificity ties, and (1,0,1) beats (0,2,0) outright."}],"answer":1},{"q":"<p>Your reset has <code dir=\"ltr\">ul[class] { list-style: none; margin: 0; }</code> and a component’s <code dir=\"ltr\">.tags { margin-block: 1rem; }</code> loses. What is the cleanest fix?</p>","options":[{"text":"Add <code dir=\"ltr\">!important</code> to the component’s margin.","why":"That wins this fight but starts an escalation; the next override needs <code dir=\"ltr\">!important</code> too, and you lose the ability to theme."},{"text":"Raise the component selector to <code dir=\"ltr\">ul.tags.tags</code>.","why":"It works, but every component now has to out-score the reset, which is the arms race you want to end."},{"text":"Rewrite the reset as <code dir=\"ltr\">:where(ul[class]) { … }</code>.","why":"Correct. The reset drops to zero specificity, so any class selector in a component overrides it."}],"answer":2},{"q":"<p>A rule is written <code dir=\"ltr\">:is(.card, :future-state) h3 { … }</code> and the browser doesn’t recognise <code dir=\"ltr\">:future-state</code>. What happens?</p>","options":[{"text":"The whole rule is dropped, as with any invalid selector list.","why":"That’s the behaviour of a plain comma list, but <code dir=\"ltr\">:is()</code> and <code dir=\"ltr\">:where()</code> use forgiving selector lists."},{"text":"The rule still applies to <code dir=\"ltr\">.card h3</code>; only the unknown selector is ignored.","why":"Correct. Forgiving parsing discards the invalid argument and keeps the rest."},{"text":"Chrome throws a console error and stops parsing the stylesheet.","why":"CSS never stops parsing on errors; it discards what it can’t understand and carries on."}],"answer":1}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<main id=\"schedule\">\n  <article class=\"session\">\n    <h2>Keynote: The Platform Is the Framework</h2>\n    <p>Read the <a class=\"abstract-link\" href=\"#abstract\">full abstract</a>.</p>\n    <a class=\"btn\" href=\"#save\">Save to my schedule</a>\n  </article>\n</main>","css":"/* Base link style from the prototype */\n#schedule article a {\n  color: #1d4ed8;\n  text-decoration: underline;\n}\n\n/* Button component: leave this rule as it is */\n.btn {\n  display: inline-block;\n  padding: 0.4em 0.9em;\n  border-radius: 999px;\n  color: #ffffff;\n  background: #6d28d9;\n  text-decoration: none;\n}\n"},"solution":{"html":"<main id=\"schedule\">\n  <article class=\"session\">\n    <h2>Keynote: The Platform Is the Framework</h2>\n    <p>Read the <a class=\"abstract-link\" href=\"#abstract\">full abstract</a>.</p>\n    <a class=\"btn\" href=\"#save\">Save to my schedule</a>\n  </article>\n</main>","css":"/* Base link style: same reach, zero-weight context */\n:where(#schedule article) a {\n  color: #1d4ed8;\n  text-decoration: underline;\n}\n\n/* Button component: leave this rule as it is */\n.btn {\n  display: inline-block;\n  padding: 0.4em 0.9em;\n  border-radius: 999px;\n  color: #ffffff;\n  background: #6d28d9;\n  text-decoration: none;\n}\n"},"checks":[{"id":"btn-white","test":"return style('.btn', 'color') === 'rgb(255, 255, 255)';"},{"id":"btn-no-underline","test":"return style('.btn', 'text-decoration-line') === 'none';"},{"id":"link-still-blue","test":"return style('.abstract-link', 'color') === 'rgb(29, 78, 216)' && style('.abstract-link', 'text-decoration-line') === 'underline';"},{"id":"no-important","test":"return ![...document.querySelectorAll('style')].some((s) => s.textContent.includes('!important'));"},{"id":"markup-intact","test":"return exists('#schedule article .btn') && exists('#schedule article .abstract-link');"}],"title":"Rescue the button from an ID selector","prompt":"<p>The prototype rule <code dir=\"ltr\">#schedule article a</code> turns the <strong>Save to my schedule</strong> button blue and underlined. Change the <strong>base link rule only</strong> so that <code dir=\"ltr\">.btn</code> gets its white, non-underlined style back, while the normal <code dir=\"ltr\">.abstract-link</code> stays blue (<code dir=\"ltr\">#1d4ed8</code>) and underlined. Don’t edit the <code dir=\"ltr\">.btn</code> rule, don’t touch the HTML, and don’t use <code dir=\"ltr\">!important</code>.</p>\n","hints":["Score both selectors: <code dir=\"ltr\">#schedule article a</code> is (1,0,2) and <code dir=\"ltr\">.btn</code> is (0,1,0). Which column decides it?","You want the base rule to keep applying only inside <code dir=\"ltr\">#schedule</code> articles, but with less weight.","Wrap the context in <code dir=\"ltr\">:where()</code>: <code dir=\"ltr\">:where(#schedule article) a { … }</code> scores (0,0,1)."],"explanation":"<p><code dir=\"ltr\">:where()</code> keeps the selector’s reach but contributes zero specificity, so <code dir=\"ltr\">:where(#schedule article) a</code> scores (0,0,1). The <code dir=\"ltr\">.btn</code> rule scores (0,1,0) and now wins on color and text decoration, while plain links inside the article still match the base rule. Removing the ID entirely (for example <code dir=\"ltr\">article a</code>) would also pass, but <code dir=\"ltr\">:where()</code> lets you keep the scoping the prototype intended.</p>\n","checkLabels":{"btn-white":"The button text is white","btn-no-underline":"The button is not underlined","link-still-blue":"The abstract link is still blue and underlined","no-important":"No !important anywhere in the CSS","markup-intact":"The HTML is unchanged"}},"words":761,"readMinutes":4}