profile
viewpoint

Ask questionsProblems surrounding SSR injection of <style> and unreliability of :first-child selectors

<!-- Thanks for your interest in the project. I appreciate bugs filed and PRs submitted! Please make sure that you are familiar with and follow the Code of Conduct for this project (found in the CODE_OF_CONDUCT.md file).

Please fill out this template with all the relevant information so we can understand what's going on and fix the issue.

I'll probably ask you to submit the fix (after giving some direction). If you've never done that before, that's great! Check this free short video tutorial to learn how: http://kcd.im/pull-request -->

Problem description:

In emotion v10+, a new warning is thrown if you use :first-child, :nth-child, or :nth-last-child selectors in a styled component or css prop value. This is because, when using SSR, the style element is injected immediately above (prepended) the associated component. (More details.)

There are a number of problems with this approach:

Note: I made a test case demo to illustrate which selectors are unreliable.

  1. In our codebase and matching years of experience for myself and peers, the "first" selectors are used much more often than the "last". Making these selectors unreliable is a big blow to those expecting standard CSS features to work.
  2. The suggested workaround of changing, e.g. :first-child to :first-of-type, only works if all sibling elements are of the same type. This is probably fairly common for components like lists, grids, etc... But it's far from a guaranteed solution. It could also discourage the use of semantic markup by driving devs to "just use divs" for everything, since it's a simpler "fix".
  3. The list of unsafe selectors is incorrect.
    • The adjacent sibling pattern, * + [anything], is unreliable, but not listed.
    • :only-child is unreliable, but not listed.
    • :nth-last-child is still reliable when prepending the style element and should not be listed.
  4. Consumers not interested in SSR should be able to turn off the warning (#1105)

Suggested solution:

Let's tackle each problem, in turn:

  1. "First" vs. "last" selectors (or prepending vs. appending the style element)

    If emotion injected the style element after (appended) the associated component, then the following selectors would be unreliable: :last-child, :nth-last-child, :only-child.

    Compare that to the currently unreliable selectors: * + [anything], :first-child, :nth-child, :only-child.

    By changing to appending the style element, emotion would no longer block use of the common adjacent sibling selector and the "last" (rather than "first") varieties of the selectors would be affected, which I would argue is a better tradeoff more in line with actual usage of each [citation needed].

  2. "of-type" workaround

    The warning's suggestion should be reworded to make clear that it may not work in all scenarios.

  3. Incorrect unsafe list of selectors

    • :only-child needs to be added (changing this to :only-of-type is a good workaround, though!
    • If the style element remains prepended
      • The * + [anything] selector pattern needs to be flagged
      • :nth-last-child needs to be removed
    • If the style element is appended
      • Update list to include selectors in (1), above.
  4. Mute the unsafe selectors warning

    At the very least, emotion should allow consumers to do so.

Alternative solutions:

Another suggested workaround is to somehow replace :first-child selectors with :first-child:not(style), style:first-child + * in the styles output. While a clever bit of CSS selecting, this suggestion has two main problems I can think of: 1) it won't work for non-:first-child selectors (though maybe we could come up with other rewrites for the others) and 2) replacing :first-child in a selector is not trivial, as it is affected by whether or not its used on the immediate element being styled or a descendent element.

The maintainers of emotion and its community could also simply decide that making these selectors unreliable is not worth the tradeoff of SSR "just working" and abandon the approach of injecting colocated style elements entirely. I have no idea how palatable such a change would be, but wanted to enumerate all the options.


This issue is intended to open discussion around these problems. If we can decide on forward actions, I'm happy to help implement wherever I can.

emotion-js/emotion

Answer questions elliotdickison

Here's a potential workaround for folks looking for a style-only alternative (i.e. no walking through children, no requiring the extractCritical SSR API) to the first-child or nth-child selectors.

These styles work by stacking up the adjacent sibling selector to reach the desired index. This means that a) you can only target a specific index (e.g. 0, 3) and b) they'll need to be programmatically generated for index you need (but hey, this is CSS-in-JS).

These styles make me kind of sad, but I'm hoping to just wrap them up in a helper and forget about this whole thing.

/*
 * This will select the 3rd child when no style
 * tag is present (client-side rendering, or SSR
 * with the critical-styles API). This style will
 * not apply if a style tag is present.
 */
.container > *:first-child:not(style) + * + * { /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */
  background-color: red;
}

/*
 * This will select the 3rd non-style child when a style
 * tag is present (normal SSR). This will not apply
 * if no style tag is present.
 */
.container > style + * + * + * {
  background-color: red;
}

Demo in JS Bin: https://jsbin.com/xejekumuyu/1/edit?html,css,output

useful!

Related questions

Cannot turn off "potentially unsafe when doing server-side rendering" noise hot 2
Component selectors not working with `@emotion/babel-preset-css-prop` hot 2
How to use babel-emotion-plugin with Storybook + Typescript? hot 1
Snapshot tests showing components as '<ForwardRef />' instead of component name hot 1
Using emotion 9 and 10 in the same window hot 1
Emotion 10 - "React is not defined" hot 1
How to get auto-prefixing with vanilla Emotion? hot 1
Typescript definitions for @emotion/native hot 1
Component selectors can only be used in conjunction with babel-plugin-emotion hot 1
Component selectors can only be used in conjunction with babel-plugin-emotion hot 1
&:focus not working hot 1
regexp not work in IE hot 1
transpile typescript to babel-plugin-emotion doesn't work hot 1
Cannot turn off "potentially unsafe when doing server-side rendering" noise hot 1
How to get auto-prefixing with vanilla Emotion? hot 1
Github User Rank List