csscss-selectorscss-variables

CSS Variables Not Respecting Selector Precedence?


I've got some code I'm writing for a website builder that's causing me issues. We're using CSS variables to define global styles, with both desktop and mobile layout options, e.g.:

:root {
  --section-padding-top: 2rem;
  --section-padding-right: 8rem;
  --section-padding-bottom: 2rem;
  --section-padding-left: 8rem;
}

@media only screen and (max-width: 767px) {
  #main {
    --section-padding-top: 3rem;
    --section-padding-right: 0rem;
    --section-padding-bottom: 3rem;
    --section-padding-left: 0rem;
  }
}

We're using the #main selector in the mobile overrides to ensure these stick. The reason is we allow users to override the global styles defined above for specific sections, upon which our code will generate a CSS block in page that looks something like this:

<style>
  .box-794ccd158158463bae325e0eb4c1c939 {
    --section-padding-top: 0rem;
    --section-padding-right: 8rem;
    --section-padding-bottom: 0rem;
    --section-padding-left: 8rem;
  }
  @media only screen and (max-width: 767px) {
    #main .box-794ccd158158463bae325e0eb4c1c939 {
      /* No mobile overrides in this example */
    }
  }
</style>

The HTML for the section with the overridden padding looks roughly like this:

<body id="main">
  <section data-id="d1b57912-1e94-4014-929e-02a38d893710" class="section component-1ca0a679fc4a4ba7a1e6b1fd9bec611e section-d1b579121e944014929e02a38d893710">
    <div class="box box-794ccd158158463bae325e0eb4c1c939 section-box">
      <div class="box-content">
        <!-- Content -->
      </div>
    </div>
  </section>
</body>

And the CSS applying the variables looks like this:

.section-box {
  padding: var(--section-padding-top) var(--section-padding-right) var(--section-padding-bottom) var(--section-padding-left);
}

The idea here is that the desktop overrides, being scoped to a class (.box-794ccd158158463bae325e0eb4c1c939), would not override the global mobile variables, which are scoped to an ID (#main).

What's actually happening is the browser (Chrome) is respecting the desktop override, which is defined later (on-page), and ignoring the ID-scoped global mobile variables, which were defined earlier in an external stylesheet loaded in the .

See here:

Chrome CSS Variable Overrides

Any ideas on why this may be? Thanks!

Edit: Update to add I tried this with !important as well, and that's still getting overridden.

Important overridden too

The expected behavior here is that the global definition will take precedence, given that it's using an ID selector (and now an !important definition, too).

Is anyone familiar with a behavior in CSS variables that ignores selector precedence? I can't find this documented anywhere.


Solution

  • From MDN:

    "Styles for a directly targeted element will always take precedence over inherited styles, regardless of the specificity of the inherited rule."

    So it appears that this rule:

    .box-794ccd158158463bae325e0eb4c1c939 {
      --section-padding-top: 0rem;
      --section-padding-right: 8rem;
      --section-padding-bottom: 0rem;
      --section-padding-left: 8rem;
    }
    

    takes precedence over this rule:

    @media only screen and (max-width: 767px) {
      #main {
        --section-padding-top: 3rem;
        --section-padding-right: 0rem;
        --section-padding-bottom: 3rem;
        --section-padding-left: 0rem;
      }
    }
    

    This can be corrected by targeting the specific CSS Variables to the generic .box class, so they're scoped the same as the overrides, e.g.:

    @media only screen and (max-width: 767px) {
      .box {
        --section-padding-top: 3rem!important;
        --section-padding-right: 0rem!important;
        --section-padding-bottom: 3rem!important;
        --section-padding-left: 0rem!important;
      }
    }