stylex icon indicating copy to clipboard operation
stylex copied to clipboard

Specifying `@media` at rules and pseudo selectors `:last-child` together

Open SukkaW opened this issue 1 month ago • 5 comments

Describe the issue

So I have the following styles specified:

const styles = stylex.create({
  container: {
    marginBottom: {
      default: '0.5rem',
      '@media screen and (min-width: 800px)': '0.75rem',
      ':last-child': 0
    }
  }
})

Expected behavior

I want my margin-bottom to always be 0 when as a last child element, otherwise 0.5rem and 0.5rem on larger screens. However ':last-child' never works on larger screen. I'd like to know the proper way to do so.

Steps to reproduce

import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  container: {
    marginBottom: {
      default: '0.5rem',
      '@media screen and (min-width: 800px)': '0.75rem',
      ':last-child': 0
    }
  }
});

<div>
  <div {...stylex.props(styles.container)}>
    Hello!
  </div>
  <div {...stylex.props(styles.container)}>
    Hello!
  </div>
  <div {...stylex.props(styles.container)}>
    Hello!
  </div>
</div>

Test case

No response

Additional comments

No response

SukkaW avatar Nov 02 '25 12:11 SukkaW

Well, the right way to do this:

const styles = stylex.create({
  container: {
    marginBottom: {
      default: '0.5rem',
      ':last-child': 0,
      '@media screen and (min-width: 800px)': {
        default: '0.75rem',
        ':last-child': 0,
      },
    }
  }
});

But this is not so obvious, and there are way too many nested things here.

SukkaW avatar Nov 02 '25 12:11 SukkaW

I am using stylexswc-nextjs-plugin and this has been confirmed in my environment.

.marginBottom-xhbfen4:not(#\#):not(#\#):not(#\#){margin-bottom:.5rem}
.marginBottom-xzboxd6:last-child:not(#\#):not(#\#):not(#\#){margin-bottom:0}
@media screen and (min-width: 800px){.marginBottom-x1x6kbue.marginBottom-x1x6kbue:not(#\#):not(#\#):not(#\#){margin-bottom:.75rem}}

media query: .marginBottom-x1x6kbue.marginBottom-x1x6kbue You can see that the specificity of the media query class has been increased by one. I don't know if this is intentional, but

refirst11 avatar Nov 05 '25 04:11 refirst11

We double class within media queries for reliable style composition, so the specificity bump is expected. Generally we advise for you to make changes within JS to style conditionally instead of using :last-child altogether

mellyeliu avatar Nov 08 '25 08:11 mellyeliu

You can nest differently to reduce the duplication:

const styles = stylex.create({
  container: {
    marginBottom: {
      default: {
        default: '0.5rem',
        '@media screen and (min-width: 800px)': '0.75rem',
      },
      ':last-child': 0,
    }
  }
});

This should do exactly what you intended without the duplication needed in the solution @SukkaW gave (which is also valid FWIW).


I also want to re-emphasize what @mellyeliu already said:

Generally we advise for you to make changes within JS to style conditionally instead of using :last-child altogether

One of the goals of StyleX is to use atomic CSS to minimize the total amount of CSS. It's preferable to avoid pseudo elements and pseudo-classes like :last-child as much as possible.

nmn avatar Nov 09 '25 05:11 nmn

I also want to re-emphasize what @mellyeliu already said:

Generally we advise for you to make changes within JS to style conditionally instead of using :last-child altogether

One of the goals of StyleX is to use atomic CSS to minimize the total amount of CSS. It's preferable to avoid pseudo elements and pseudo-classes like :last-child as much as possible.

@mellyeliu @nmn

And that means I will have to write more obscure and potentially inefficient code:

const styles = stylex.create({
  item: {
    marginBottom: {
      default: {
        default: '0.5rem',
        '@media screen and (min-width: 800px)': '0.75rem',
      },
-      ':last-child': 0,
    }
  },
+  item_last_child: {
+    marginBottom: 0
+  }
});

- array.map(item => <div {...stylex.props(styles.item)} />)
+ array.map((item, index, arr) => <div {...stylex.props(styles.item, index === arr.length - 1 && styles.item_last_child)} />)

And the stylex compiler can't optimize this; If I use :last-child, it is all CSS, and the stylex compiler can strip/eliminate all runtime codes and calls.

SukkaW avatar Nov 10 '25 07:11 SukkaW