Skip to content

Make the cascade more intuitive by setting selector specificities.

License

Notifications You must be signed in to change notification settings

wlib/postcss-set-specificity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

postcss-set-specificity - Set selector specificity to equal another's


Example Open in CodeSandbox


/* Input */
@set-specificity :root {
  #id::before {
    content: "earlier, more specific selector should not match";
  }

  .class::before {
    content: "later, less specific selector should match";
  }
}

@set-specificity {
  .a, .b, .c {
    --test: "";
  }
}

/* Output */
:is(*,:not(._)):where(#id)::before {
  content: "earlier, more specific selector should not match";
}

:is(*,:not(._)):where(.class)::before {
  content: "later, less specific selector should match";
}

:where(.a), :where(.b), :where(.c) {
  --test: "";
}

Installing

npm i -D postcss-set-specificity

postcss.config.js

module.exports = {
  plugins: [
    require("postcss-set-specificity") // After nesting plugin, if used
  ]
}

Consult PostCSS documentation for alternative usage.

Browser Support

Requires support for these CSS Selectors Level 4 features:

This corresponds to a browserslist roughly like:

Chrome >= 88
Safari >= 14
Firefox >= 78

How It Works

Given that:

  • :is() and :not() have specificities equal to the maximum specificity within them
  • :is(*, :not(x)) matches like the universal selector (*), but with the specificity of x
  • :where() always has a specificity of zero
  • :is(*, :not(x)):where(y) matches like y, but with the specificity of x
  • the specificity of a selector can be matched in an optimal way:
    • element#id.class:pseudo-class[attribute]::pseudo-element element * => 1,3,3
    • _+_+_#_._._._ => 1,3,3
  • pseudo elements can't be used in :where(), :is(), or :not()

If provided with:

@set-specificity x {
  y::before {}
}

The plugin produces:

:is(*,:not(_)):where(y)::before {}

In cases where there is no selector provided, we can simply use :where() alone.

Caveats

  • Does not yet interoperate with CSS Nesting, but wouldn't be hard to with @nest.
  • Pseudo elements are treated as exceptions and add 0,0,1 to the specificity, as they cannot be set to zero, and simply subtracting will lead to inconsistency.
  • Pseudo elements must be written with the :: prefix instead of with their old : prefix

Unimplemented Optimizations

  • Calculate specificities
    • Partial shared specificity
      • :is(*,:not(a#b.c)):where(d#e) to
      • d#e:is(*,:not(.c))
    • Full shared specificity
      • :is(*,:not(a#b.c)):where(d#e.f) to
      • d#e.f
  • Parse selector lists for correctness
    • Valid selector list (invalid selector lists would not fail because :where() is supposed to be forgiving)
      • :where(.a), :where(.b), :where(.c) to
      • :where(.a,.b,.c)
  • Assumptions/loose behavior
    • Assume selector never to match
      • :is(*,:not(#🔝)):where(.a) to
      • :not(#🔝):where(.a)
    • Assume selector will always match (e.g. must use something like <html id="🔝" class="🔝">)
      • :is(*,:not(#_)):where(.a) to
      • #🔝 :where(.a)

About

Make the cascade more intuitive by setting selector specificities.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published