Two frontend engineers collaborating at a laptop while reviewing CSS code in a modern office.

A More Specific Selector Can Lose After Adding @layer

Wed, Oct 7, 2026

In a CSS refactoring, you might move component styles into a new @layer. Unexpectedly, doing so can make a more specific selector lose to a weaker legacy rule. In the controlled example below, the “Review” button (#app #action.button) is expected to be blue (rgb(0, 80, 180)) before the change and red (rgb(180, 0, 0)) after its component rule moves into @layer components. The selector (#app #action) does not change and is more specific than .button, yet the unlayered legacy rule has priority. Before shipping, we need a decision: should we accept this change or roll it back?

To answer, we will write down the button’s required color, its contract, build a controlled test that reproduces the failure, deliberately fix the layer ordering, audit any !important rules, and collect evidence via getComputedStyle. This evidence will guide our release decision. The diagnosis is separate from other CSS changes, including native anchored positioning, and assumes familiarity with basic HTML and CSS. The outcomes described here are expectations, not a record of an executed browser test.

1. Define the component contract before moving a rule

First, explicitly state what the component must do. In our case, the “Review” button must end up with the color rgb(0, 80, 180) under normal conditions. We label the legacy framework’s red (rgb(180, 0, 0)) and the component’s intended blue (rgb(0, 80, 180)) so there is no confusion. This is the product’s contract: the button must be blue as a tested requirement, not just whatever the current CSS happens to do by default. The proposed change is to wrap the component’s blue rule in a layer (for example, @layer components) while leaving the legacy .button rule unlayered. The component’s design team owns this contract and should sign off on any change.

Importantly, this CSS layer migration is separate from other CSS refactors. For example, moving to native CSS anchor positioning is a separate native CSS migration decision. Here we focus only on the cascade of this button’s color. No anchors or positioning features are involved in our test. We define the contract and proposed change without mixing unrelated changes. Before shipping, confirm that the button’s resolved color still meets the defined contract.

2. Build an isolated browser environment

We create a self-contained HTML test harness with exactly one #app #action.button target in each case document. For each test case, we load a fresh <iframe> containing the HTML and <style> under test. Each frame remains attached and rendered while its target is inspected. Our fixture has no external dependencies: no external stylesheets, no inline styles on the target, no transitions or animations, no custom properties or @import rules, and no inheritance-dependent color output. Shadow roots, nested layers, @scope, other cascade origins, and unrelated selectors remain outside the executable core. The aim is a minimal static HTML/CSS page. Readers who need prerequisite knowledge can review HTML and CSS foundations.

  • Isolation per case: Each CSS variant is tested in its own <iframe> with srcdoc. We keep the iframe visible and rendered, not display:none, so getComputedStyle inspects a connected element in a rendered browsing context. Reusing a document or hiding the frame would weaken these experimental controls.

  • Element setup: The case document contains <div id="app"><button id="action" class="button">Review</button></div> and a <style> block with id="under-test" containing the case’s CSS. We verify exactly one #app #action.button match and one #action ID. Multiple targets or a missing target invalidate the case.

  • Exclusions: No scripts or DOM manipulations change the target’s style after load. The literal-color environment excludes active forced colors or unexpected preferences that alter the tested output. The harness detects active forced colors and rejects that run; it does not change accessibility preferences. The scope is one HTML file, one static button, one longhand property, and the two fixed declaration candidates.

Record the browser and environment

Record the exact browser version and build from its About page, the operating environment, and the testing date. Treat navigator.userAgent as supplementary evidence, not a substitute for the manually checked build. Include the fixture revision and whether forced colors are active. Unsupported controls, unexpected preferences, and timeouts block approval. Each iframe has a three-second load timeout; a loading error, missing parsed stylesheet, or assertion mismatch must remain visible in the report.

A hidden iframe, such as one styled with display:none, would not satisfy the rendered-context requirement used here. The CSSOM method algorithm ties resolved-value collection to a connected element whose document belongs to a rendered browsing context. Reusing a document could also preserve prior layer declarations or styles. A new rendered frame for every case avoids that carry-over and makes the recorded inputs easier to review.

3. Establish the unlayered baseline and expected manifest

Before adding any @layer, verify the original stylesheet behavior and define our expectations. In the baseline, we put both rules unlayered in order:

.button { color: rgb(180, 0, 0); }
#app #action { color: rgb(0, 80, 180); }

Here, the more specific selector #app #action supplies blue and appears after .button, which supplies red. With both declarations normal and unlayered, blue is the expected winner. A recorded blue result would satisfy the original local contract in the declared environment, not prove universal component correctness.

This step checks the starting point. Without layers, the component’s blue declaration is expected to win. It also checks that the environment and selectors are set up correctly. No values or selectors change here. The baseline must produce blue before its result can support the remaining analysis.

Write the expected outcomes before execution

List all cases, including their layer and importance combinations, and predict their colors before running any tests. This manifest is independent of execution. Do not derive its expected values from browser output. The nine expected outcomes are:

Case

Arrangement

Expected color

C01

Both normal and unlayered; legacy red before more specific component blue

rgb(0, 80, 180)

C02

Legacy red normal/unlayered; component blue normal/in components

rgb(180, 0, 0)

C03

Prelude legacy, components; both normal in their named layers

rgb(0, 80, 180)

C04

Same prelude as C03; but place components block first in source

rgb(0, 80, 180)

C05

Prelude components, legacy; both normal in their named layers

rgb(180, 0, 0)

C06

Prelude legacy, components; both declarations !important in their layers

rgb(180, 0, 0)

C07

Important legacy red unlayered; important component blue in components layer

rgb(0, 80, 180)

C08

Same layer (components); more specific blue rule then less specific red

rgb(0, 80, 180)

C09

Same layer (components); same selector .button blue then red in source order

rgb(180, 0, 0)

Table 1. Expected outcomes for the nine cascade cases. Compare the browser’s copied rgb() string with the literal expected color. The manifest follows the declared CSS inputs; it is not a transcript of a browser run.

4. Reproduce the partial-migration failure

We now run the first variant that represents the partial migration: only wrap the component rule in a layer. In CSS:

.button { color: rgb(180, 0, 0); }
@layer components { #app #action { color: rgb(0, 80, 180); } }

We do not declare a layer-order prelude in this case. According to the manifest, C02 should yield red (rgb(180, 0, 0)), violating the blue product contract. This is the controlled failure we expect, not an observed run reported here. At this point, do not try to fix the result by adding selectors or !important. Instead, identify the changed cascade input. Only @layer components was added around the blue rule; the HTML, selectors, and literal values are unchanged from the baseline.

Read layer priority before specificity

The CSS cascade sorting rules consider layer priority before specificity within the author stylesheet declarations tested here. A declaration outside an explicit layer occupies the implicit final layer position. In C02, the legacy .button declaration is unlayered, after the named components layer in that ordering. For these normal declarations, the later layer position has priority. The more specific #app #action selector therefore cannot make layered blue outrank unlayered red. Later source placement inside that lower-priority layer does not change the result.

MDN’s discussion of normal and important layer priorities qualifies its abbreviated statement about unlayered styles: normal unlayered author stylesheet declarations outrank normal layered ones, but important declarations require the different analysis in Section 6. The specification’s reset-layer example illustrates the normal-declaration principle with an <audio> element: a less-specific unlayered declaration outranks a more-specific declaration inside an explicit reset layer. That is the same ordering mechanism isolated by C02.

Annotate the inputs explicitly: component blue is in components, while legacy red is in the implicit final unlayered position. Both selectors match the target, and both declarations are normal. In this restricted two-candidate setup, expected red identifies the legacy declaration as the winner. Introducing @layer changed layer membership, which is considered before selector specificity here. The same-layer controls below show where specificity still matters.

5. Repair the layer hierarchy deliberately

To fix the issue, we must explicitly define the intended layer order before any rules. For example, we prepend:

@layer legacy, components;
@layer legacy { .button { color: rgb(180, 0, 0); } }
@layer components { #app #action { color: rgb(0, 80, 180); } }

This is C03. The legacy .button declaration now lives in legacy, and the blue #app #action declaration lives in components. Declaring legacy first and components second establishes the intended normal layer priority. MDN’s explanation of named layer ordering describes the same relationship. Blue is the expected result, restoring the chosen contract if the isolated browser test confirms it.

Wrapping only the component rule causes C02’s expected failure. For the chosen repair, place both relevant declarations into their intended layers and establish their relative order. Moving only .button into a layer while leaving #app #action unlayered would also favor normal blue in this restricted fixture, but it would be a different intentional arrangement. Appending a more-specific selector inside components cannot overcome a normal unlayered candidate. The remedy must address the actual layer hierarchy rather than treating specificity as the problem.

Before applying this to a larger codebase, inventory every other declaration affecting the target. Could an additional unlayered legacy rule override the component? Decide which remaining unlayered candidates are intentional exceptions, which should migrate, and which require deferral. Preserve the original complete CSS artifact and its associated revision so it remains available for rollback.

A targeted migration can be valid when all relevant competing declarations have been reviewed. Deferring the migration and retaining the previous artifact is also a valid decision. Neither wrapping one side nor moving every rule indiscriminately is a substitute for an intentional hierarchy and evidence that the product contract survives.

6. Audit important declarations as a separate contract

Now consider !important. Test C06, with both color declarations important inside layers, and C07, with important unlayered legacy red competing against important layered component blue. In C06, the prelude is @layer legacy, components;. The documented important layer priority reverses the normal order: earlier legacy has priority over later components, so red is expected despite the component selector’s greater specificity.

In C07, blue is inside a layer and red is unlayered. MDN’s description of important declarations within layers explains why layered important blue outranks unlayered important red in this author-stylesheet comparison. C07 is therefore expected to resolve to blue.

These expectations distinguish the two scoped orderings. Under the cascade’s layer rules, later layers have priority for the normal declarations tested here, while earlier layers have priority for important declarations. MDN also distinguishes layered and unlayered important declarations. This author-stylesheet fixture does not establish a universal ranking across other origins or inline target styles.

Separate normal and important contracts

Because !important reverses layer priority, important exceptions need their own contract review. Before approval, inventory every important declaration affecting the target. The same legacy, components hierarchy gives normal and important declarations different priorities.

Classify each exception as intentional, removable after a separate review, or unresolved. Do not remove all important annotations merely to make a test pass. An urgent override may still be intentional; another declaration may be outdated or suitable for refactoring. Document its purpose and reviewer before changing it.

Use MDN’s explanation of normal versus important layer ordering and its guidance on important declaration priorities when reviewing these exceptions. A needed important declaration must continue to meet its intended contract after migration. An unresolved exception is a reason to hold approval, not to add another annotation or silently change the expected color.

7. Distinguish first declaration from later block placement

To confirm what matters for layer precedence, compare C03, C04, and C05. C03 declares @layer legacy, components;, then places the legacy block before the components block. C04 reverses only those later blocks while preserving the prelude. Its expected result remains blue. Once that initial order is established, appending rules to the named layers does not move them in the layer hierarchy.

By contrast, C05 changes the prelude to @layer components, legacy;. The declared layer order reverses, so the expected result is red. MDN’s explanation of the initial order of named layers distinguishes first declaration from later block placement. In C04, legacy, components remains the established order; in C05, components, legacy is established instead.

Each case gets a fresh document so a previous case’s layer declarations cannot influence it. Record the first effective declaration of each relevant layer in the release evidence, not merely the order of later blocks in a source file.

8. Confirm where specificity and source order still apply

Specificity and source order still apply when the earlier cascade criteria tie. C08 puts both normal declarations in components: more-specific blue (#app #action) appears before less-specific red (.button). Blue is expected because the higher-specificity selector wins within that same layer.

C09 puts two normal .button declarations in components, blue followed by red. Origin, importance, layer, and selector are equal, so later source order decides the tie and red is expected. The specification’s specificity and source order rules support these two controls. They prevent “layers remove specificity” from replacing the original misconception: the result depends on which earlier cascade criteria have already resolved the competition.

9. Run the complete local fixture

The complete HTML fixture below enumerates C01 through C09, injects each fixed CSS case into a fresh <iframe>, and compares the target’s resolved color with the independent literal expectation. Before running it, replace the browserBuild configuration value with the exact build checked on the browser’s About page, save the file, and open it locally in that browser.

The script writes its JSON report into <pre id="report">. No browser execution is reported in this article, so the supplied outcomes remain expected rather than observed. Save each actual report before rerunning; do not replace it with a summary that hides failures.

<!doctype html>

<meta charset="utf-8">

<title>CSS layer migration lab</title>

<div id="stage"></div>

<pre id="report">Running local cases...</pre>

<script>

"use strict";

const browserBuild = "RECORD_EXACT_BROWSER_BUILD";

const ids = Object.freeze(["C01","C02","C03","C04","C05","C06","C07","C08","C09"]);

const cases = Object.freeze({

  C01: ".button{color:rgb(180,0,0)} #app #action{color:rgb(0,80,180)}",

  C02: ".button{color:rgb(180,0,0)} @layer components{#app #action{color:rgb(0,80,180)}}",

  C03: "@layer legacy,components; @layer legacy{.button{color:rgb(180,0,0)}} @layer components{#app #action{color:rgb(0,80,180)}}",

  C04: "@layer legacy,components; @layer components{#app #action{color:rgb(0,80,180)}} @layer legacy{.button{color:rgb(180,0,0)}}",

  C05: "@layer components,legacy; @layer legacy{.button{color:rgb(180,0,0)}} @layer components{#app #action{color:rgb(0,80,180)}}",

  C06: "@layer legacy,components; @layer legacy{.button{color:rgb(180,0,0)!important}} @layer components{#app #action{color:rgb(0,80,180)!important}}",

  C07: ".button{color:rgb(180,0,0)!important} @layer components{#app #action{color:rgb(0,80,180)!important}}",

  C08: "@layer components{#app #action{color:rgb(0,80,180)} .button{color:rgb(180,0,0)}}",

  C09: "@layer components{.button{color:rgb(0,80,180)} .button{color:rgb(180,0,0)}}"

});

const expected = Object.freeze({

  C01:"rgb(0, 80, 180)", C02:"rgb(180, 0, 0)", C03:"rgb(0, 80, 180)",

  C04:"rgb(0, 80, 180)", C05:"rgb(180, 0, 0)", C06:"rgb(180, 0, 0)",

  C07:"rgb(0, 80, 180)", C08:"rgb(0, 80, 180)", C09:"rgb(180, 0, 0)"

});

function requireEqual(actual, wanted, label) {

  if (actual !== wanted) throw new Error(label + ": expected " + wanted + "; got " + actual);

}

function errorText(error) { return String(error && error.message || error); }

function readCase(id, css) {

  return new Promise((resolve, reject) => {

    const frame = document.createElement("iframe");

    frame.title = "Case " + id;

    frame.width = "640";

    frame.height = "100";

    let timer;

    let finished = false;

    function finish(error, value) {

      if (finished) return;

      finished = true;

      clearTimeout(timer);

      frame.removeEventListener("load", loaded);

      frame.remove();

      if (error) reject(error); else resolve(value);

    }

    function loaded() {

      try {

        const doc = frame.contentDocument;

        requireEqual(doc.documentElement.dataset.case, id, "document identity");

        requireEqual(doc.querySelectorAll("#app #action.button").length, 1, "target count");

        requireEqual(doc.querySelectorAll("#action").length, 1, "unique target ID");

        const target = doc.querySelector("#action");

        if (!target.isConnected || !frame.getClientRects().length) throw new Error("unrendered fixture");

        const sheet = doc.querySelector("#under-test").sheet;

        if (!sheet || !sheet.cssRules.length) throw new Error("missing parsed stylesheet");

        const actual = frame.contentWindow.getComputedStyle(target).getPropertyValue("color");

        const parsedRules = Array.from(sheet.cssRules, rule => rule.cssText);

        finish(null, {actual, parsedRules, documentCase: doc.documentElement.dataset.case});

      } catch (error) { finish(error); }

    }

    timer = setTimeout(() => finish(new Error("case load timed out")), 3000);

    frame.addEventListener("load", loaded);

    try {

      frame.srcdoc = '<!doctype html><html data-case="' + id + '"><head><meta charset="utf-8">' +

        '<style id="under-test">' + css + '</style></head><body>' +

        '<div id="app"><button id="action" class="button">Review</button></div></body></html>';

      document.querySelector("#stage").append(frame);

    } catch (error) { finish(error); }

  });

}

(async () => {

  const report = {

    fixtureRevision:"cascade-lab-1", startedAt:new Date().toISOString(),

    browserBuild, userAgent:navigator.userAgent,

    forcedColors:matchMedia("(forced-colors: active)").matches,

    laboratoryStatus:"FAIL", releaseApproval:"NOT_EVALUATED", rejectionControl:false, rows:[]

  };

  try {

    if (browserBuild === "RECORD_EXACT_BROWSER_BUILD" || !browserBuild.trim()) throw new Error("record exact browser build first");

    if (report.forcedColors) throw new Error("forced colors active: literal-color environment requires separate review");

    requireEqual(Object.keys(cases).join("|"), ids.join("|"), "fixture membership");

    requireEqual(Object.keys(expected).join("|"), ids.join("|"), "manifest membership");

    for (const id of ids) {

      const row = {id, css:cases[id], expected:expected[id], status:"FAIL"};

      try {

        Object.assign(row, await readCase(id, cases[id]));

        requireEqual(row.actual, row.expected, id);

        row.status = "PASS";

      } catch (error) { row.error = errorText(error); }

      report.rows.push(row);

    }

    const baseline = report.rows.find(row => row.id === "C01");

    if (baseline.status === "PASS") {

      try { requireEqual(baseline.actual, "rgb(180, 0, 0)", "deliberately wrong expectation"); }

      catch (error) { report.rejectionControl = true; report.rejectionEvidence = errorText(error); }

    }

    requireEqual(report.rows.map(row => row.id).join("|"), ids.join("|"), "result membership");

    requireEqual(report.rows.every(row => row.status === "PASS"), true, "all expected cases");

    requireEqual(report.rejectionControl, true, "comparator rejection control");

    report.laboratoryStatus = "PASS";

  } catch (error) { report.error = errorText(error); }

  finally {

    report.finishedAt = new Date().toISOString();

    document.querySelector("#report").textContent = JSON.stringify(report, null, 2);

  }

})();

</script>

Treat every missing result as a failure

The fixture checks document identity, target cardinality, the unique target ID, and exact case membership in the CSS definitions, manifest, and result rows. Each frame has a three-second load timeout and is removed on success or error. A missing parsed stylesheet, load failure, or comparison mismatch remains visible in the JSON. All nine normal case records must pass their own expected values.

The separate rejection control compares an observed passing C01 result with deliberately wrong red. The comparator must reject that mismatch, recorded as rejectionControl: true. Keep the raw JSON exactly as produced. An actual C02 value of rgb(180, 0, 0) would be laboratory success because it matches the predicted failure mechanism, but it rejects that partial migration against the original blue contract. The fixture leaves releaseApproval as NOT_EVALUATED.

10. Reconcile resolved values with known declarations

After execution, reconcile each copied color with the case’s known declarations. The following C02 record is an illustrative evidence format, not a measured browser result. Its example observed value shows what a manifest-matching C02 run would contain:

Field

C02 illustrative evidence record

Case

C02

CSS (literal)

.button{color:rgb(180,0,0)}

@layer components{#app #action{color:rgb(0,80,180)}}

Layer order

components, followed by the implicit final unlayered position occupied by the legacy declaration.

Selectors

#app #action: two ID selectors, more specific.

.button: one class selector, less specific.

Important?

Neither declaration has !important.

Expected color

rgb(180, 0, 0)

Observed color

Illustrative value: rgb(180, 0, 0). No browser result is reported here.

Browser/build

A real run must record the exact About-page browser/build. This unexecuted example has no recorded build.

A real report must include the exact browser/build, fixture revision, operating environment, and run timestamps. If the actual C02 scalar is red under these fixed inputs, the legacy .button declaration in the implicit final layer is the supported winning-candidate interpretation. Keep that interpretation separate from the literal stylesheet and the browser’s copied result. Neither the raw CSSOM rule list nor getComputedStyle is a source-rule provenance report.

MDN documents getComputedStyle resolved values and their live, read-only return object. For the opaque sRGB colors used here, the comparison uses comma-separated rgb() strings. Copy the scalar string immediately into the report rather than retaining a live object for later inspection. The CSSOM resolved-value model supplies the underlying method context; the experiment depends on the scalar value, not a constructor-name assertion.

If both candidates had the same color, or if inheritance, other origins, conditional rules, or additional states were involved, a color alone would not identify the winning source. Here the two literal values are distinguishable and the candidate set is deliberately restricted. That restriction supports the interpretation; it does not turn every resolved-color reading into source identity.

The raw JSON includes parsedRules, the stylesheet rules read through CSSOM. That records what the browser parsed, not which rule won. Reconciliation combines the literal CSS, known layer order, selectors, and importance flags with the observed scalar. The inputs explain the conditions; the copied value records the output. Preserve both rather than substituting the interpretation for raw evidence.

11. Revalidate the stylesheet that will actually ship

The native laboratory isolates the layer-ordering mechanism. The next step is to check the actual CSS artifact proposed for release. Record its revision and content, then inspect the following:

  • Identify the first effective @layer declarations and established order. Does the emitted artifact establish legacy, components, or another order? Verify the declared layer ordering in the actual output rather than assuming source-file placement survived the build.

  • Check for target declarations still outside the intended layers. For example, is .button { color: red } still unlayered? Identify any other matching declarations that could affect the tested property.

  • List remaining !important annotations and their reviewed purpose. Distinguish intentional exceptions from removable declarations and unresolved cases.

  • Name the affected component states selected for acceptance, such as ordinary, hover, or disabled. At minimum, inspect the ordinary state covered by the blue contract; do not assume it represents every state.

Keep the nine-case laboratory and its independent expectations unchanged. In a separate product-specific check, load the actual emitted CSS in a fresh rendered document containing the relevant component. Use the same scalar-reading discipline, but compare the result with the approved product-state contract rather than rewriting the nine laboratory cases around production output.

Connect this evidence to the release process alongside production build migration checks. Build tools may transform stylesheet contents, so inspect the actual emitted order instead of assuming it from source files. The laboratory explains the mechanism; the shipping artifact’s recorded behavior establishes whether the chosen component contract survives. Matching that contract supports only the declared states and environment, not every CSS behavior.

12. Apply explicit acceptance and rejection criteria

Use the recorded evidence to make an explicit acceptance, rejection, or hold decision:

Condition (evidence)

Decision

C01 baseline resolves to blue.

Baseline accepted for the local environment; proceed to the remaining checks.

C02 partial migration changes the required blue to red.

Reject this migration candidate. Its expected laboratory result violates the blue product contract.

C03 deliberate hierarchy resolves to blue.

Candidate repair identified; proceed to important-exception and built-artifact review.

Important exceptions affecting the product contract remain unexplained.

Hold or revise. C06’s expected red is not itself a harness failure; unresolved product exceptions block approval.

A case is skipped, a result is missing, or the environment is incompatible.

Block approval until the fixture and environment support a complete valid run.

The built artifact differs from the reviewed hierarchy or behavior.

Hold and test the actual artifact. Restore the previous approved artifact when necessary.

A successful laboratory run requires all nine expected outcomes and rejectionControl: true. That includes expected red in C02, C05, C06, and C09. It is not a requirement that every laboratory case be blue. C02’s red result rejects the proposed partial migration against the blue product contract; a recorded C03 blue result identifies a candidate repair. Missing cases, unexpected values, or unexplained important exceptions block approval.

Do not mistake laboratoryStatus: PASS for final release approval. The release decision also requires the actual shipping artifact to satisfy its declared component contracts in the selected states and environment. The fixture itself keeps releaseApproval: NOT_EVALUATED.

This color contract is narrow. Layout, contrast, keyboard focus, and responsive behavior need broader rendering regression coverage. One resolved-color assertion cannot certify those properties. Approve only the specific contract that the product owner declared and the recorded artifact actually met; otherwise reject, repair, or defer the change.

Prove the comparator rejects a known mismatch

The rejection-control step deliberately compares C01’s observed passing blue result with a literal red expectation. The comparator must reject it and set rejectionControl to true. This checks that the comparison can detect a known mismatch; it is separate from the nine normal cases and from product acceptance.

Do not adjust expectations to match a failing observation or omit cases to force a pass. If the comparator accepts the known mismatch, the harness is not trustworthy and approval must stop. The release decision still concerns the declared product contract, not an accidental output or the harness label alone.

13. Assign ownership and retain a rollback path

Assign an owner to the first layer declarations and the intended hierarchy. The engineer or team responsible for that shared prelude must maintain its ordering deliberately. Name reviewers for important exceptions, such as the design-system maintainer or relevant feature lead. The component’s feature or design-system team owns the output contract. Record who approves this bounded part of the release.

Keep the previous complete CSS artifact available with its associated revision. If a release unexpectedly violates the tested contract, restore the last approved artifact through the established deployment process, then verify the affected contract again. Preserve the failed-run JSON and notes describing the uncovered state. With that evidence retained, the team can repair the hierarchy or address the missed condition before another migration attempt.

Do not treat another specificity increase or an unreviewed !important annotation as a durable rollback strategy. Restore the known-good artifact through the normal release process, then continue the migration in development. This keeps the recovery path auditable and avoids hiding the original ordering problem.

14. Build the frontend skills needed to maintain the contract

Keep a concise evidence packet: the controlled failing case, the intended layer order, important exceptions, and the release decision. This becomes a useful record for future maintenance. Readers who need broader preparation can review frontend development foundations.

For structured study, the Refonte Learning Frontend Development program lists Web HTML/CSS Foundations, JavaScript Modern Features, React single-page applications, Responsive Design Practices, Advanced CSS Techniques, practical projects, and a capstone. The page lists a commitment of 10–12 hours per week and names David A. Thompson among its educational mentors. It also lists a Training Certificate and a Certificate of Internship upon successful completion. Its listed foundations and project work are relevant preparation for maintaining browser-interface contracts.

A controlled environment and an understanding of cascade sorting, layer ordering, and important exceptions make a specific CSS contract reviewable. A passing product check means the declared output survived under the tested conditions, not that every interface property is correct. Keep the expectations, raw evidence, and bounded release decision together so later changes can be approved or rejected against the same contract.