Styled-jsx: the problem with the priority of CSS selector

Created on 22 Mar 2018  Â·  13Comments  Â·  Source: vercel/styled-jsx

What is the current behavior?

I'm using styled-jsx with nextjs.
but, I think the priority of CSS selector is a something wrong. it's expected that color of click text is blue. because the priority of class is higher than tags.
https://gist.github.com/mjj2000/5873872

<div>
  <div>
    <span className="clickTest">click</span>
  </div>
</div>
<style jsx>{`
  div > div > span {
    color: red;
  }
  .clickTest {
    color: blue;
  }
`}</style>

What is the expected behavior?

but, click text rendered color red.
div > div > span selector must not complie to like div.jsx-1217126016>div.jsx-1217126016>span.jsx-1217126016. because selector like [tag].[classname] is higher than just .[classname] selector.
2018-03-22 3 02 20
2018-03-22 3 02 33

Environment (include versions)

  • OS: macOS HighSierra
  • Browser: Chrome
  • styled-jsx: 2.2.3
bug

Most helpful comment

just add a scoped class. first or last one.
but, I'm not sure this is right solution.
what do you think?

div > div > span is [0,0,0,3]
.clickTest is [0,0,1,0]

When scoped instead:

div.jsx-123 > div > span is [0,0,1,3]
.clickTest.jsx-123 is [0,0,2,0]

All 13 comments

Good point. The specificity:

  • div > div > span is [0,0,0,3]
  • .clickTest is [0,0,1,0]

When scoped instead:

  • div.jsx-123 > div.jsx-123 > span.jsx-123 is [0,0,3,3]
  • .clickTest.jsx-123 is [0,0,2,0]

I guess the only way to fix this is to balance the specificity when we add the scoped classes. In this case:

  • .clickTest.jsx-123.jsx-123.jsx-123

Do you have better ideas on how we could fix this?

Maybe we should prefix only first token in selectors. This will round specificity for elements and pseudo-elements with other 🤔

  • div > div > span → 0,0,0,3
  • .clickTest → 0,0,1,0
  • div.jsx-XXX > div > span → 0,0,1,3
  • .clickTest.jsx-XXX → 0,0,2,0

the styles for those unscoped selectors would leak into the descendants

Yeah, but this is the price for using nested tag styles.

P.S. Styling elements with tags is usually an exceptional or bad idea.

If we had to break something then I'd rather rewrite selectors to have all the same specificity e.g.:

div > div > span -> .div > .div > .span
.clickTest -> .clickTest

And state that in styled-jsx types have all the same specificity.

just add a scoped class. first or last one.
but, I'm not sure this is right solution.
what do you think?

div > div > span is [0,0,0,3]
.clickTest is [0,0,1,0]

When scoped instead:

div.jsx-123 > div > span is [0,0,1,3]
.clickTest.jsx-123 is [0,0,2,0]

@blueshw actually that is a great idea. The only adjustment to it would be that we only scope the last selector (not the first):

div > div > span.jsx-123
.clickTest.jsx-123

I need to verify it though to make sure that this couldn't lead to conflicts or unwanted results.

for the record we realized that we also need an upper boundary https://github.com/thysultan/stylis.js/issues/101#issuecomment-375853514

@blueshw's suggestion would actually be really great, because right now styled-jsx is kind of clunky when it comes to styling third-party components without having to sprinkle :global the whole way down. It would be awesome to have local scope with an unaltered cascade. Then the following would work:

<div className="foo">
  <ThirdPartyComponent className="bar">
  <style jsx>{`
    .foo .bar .someInnerClass a {
      ...
    }`</style>
</div>

as it would get transformed to

<div className="foo.jsx-123">
  <ThirdPartyComponent className="bar">
</div>

with styles like

.foo.jsx-123 .bar .someInnerClass a { ... }

The third-party component styling situation would be so much better with a feature like this. 2-birds-1-stone kind of situation.

@merrywhether this issue is not about making changes to :global or how to reach to descendants without using :global but to fix the specificity issue while keeping the scoping behavior valid :)

The solution is to always scope the innermost and outermost selectors. To not break specificity then we need to add the scoped class twice when the selector is single eg. div.jsx-123.jsx-123

True enough, was just pointing out the similarity in the use-cases. OP could've used :global in a similar way to downscale the element selectors' specificity to achieve the same outcome as your suggestion: :global(div) > :global(div) > span (or div > :global(div) > :global(span) to achieve @blueshw's suggestion).

It seems like it'd be weird to have to use :global to drop class injection in some cases but then have it done automatically in other cases.

Is this still an issue?

I'm seeing this when try to move my styles to a separate JS file and try to import them.
I have tried using css.resolve, but no luck.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nicolastelfer picture nicolastelfer  Â·  4Comments

maxchehab picture maxchehab  Â·  3Comments

jaydenseric picture jaydenseric  Â·  5Comments

davibe picture davibe  Â·  5Comments

soulchainer picture soulchainer  Â·  5Comments