DEV Community

sal lancaster
sal lancaster

Posted on

StyleX + ESLint: When Best Practices Fight Each Other

or... "How the @stylexjs/sort-keys rule silently breaks responsive styles"

The Setup: Following All the Rules

After solving the StyleX + Vite "Invalid empty selector" mystery, I thought I had StyleX figured out. I was following every documented best practice:

Vite config - using enableMediaQueryOrder: true as recommended in v0.15.0:

// vite.config.js
styleXUnplugin.vite({
  enableMediaQueryOrder: true,
})
Enter fullscreen mode Exit fullscreen mode

ESLint config - using the recommended StyleX plugin rules:

// eslint.config.mjs
rules: {
  "@stylexjs/valid-styles": "error",
  "@stylexjs/sort-keys": "warn",  // Keep styles organized!
}
Enter fullscreen mode Exit fullscreen mode

Responsive styles - using inline strings (learned that lesson) with correct cascade order:

const styles = stylex.create({
  container: {
    padding: {
      default: "2rem",
      "@media (max-width: 768px)": "1.5rem",  // tablet
      "@media (max-width: 640px)": "1rem",    // mobile
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Everything looked correct. The order was right—larger breakpoints first so smaller ones can override them in the CSS cascade.

The Problem: A Helpful ESLint Warning

Then I ran npm run lint and saw:

warning  StyleX property key "@media (max-width: 640px)"
         should be above "@media (max-width: 768px)"
Enter fullscreen mode Exit fullscreen mode

Being a good developer who trusts their linter, I ran npm run lint -- --fix. ESLint helpfully "fixed" my code:

padding: {
  default: "2rem",
  "@media (max-width: 640px)": "1rem",    // Now first (640 < 768 alphabetically)
  "@media (max-width: 768px)": "1.5rem",  // Now last
},
Enter fullscreen mode Exit fullscreen mode

The Breakage: Mobile Gets Tablet Styles

Here's what happens on a 500px mobile screen:

  1. Both max-width: 640px and max-width: 768px match (500 < 640 < 768)
  2. With enableMediaQueryOrder: true, StyleX makes "later source order wins"
  3. After ESLint sorting, 768px is later in source
  4. Result: Mobile devices get 1.5rem instead of 1rem

The responsive design is now broken, and ESLint told me to do it.

Why This Happens

The @stylexjs/sort-keys rule sorts all object keys alphabetically. For most properties, this is fine—it keeps your styles organized and consistent.

But media query strings sort by their numeric values as strings:

  • "@media (max-width: 640px)" → "640" comes first
  • "@media (max-width: 768px)" → "768" comes second

For max-width queries, this is exactly backwards. CSS cascade requires:

  • Larger breakpoints first (768px)
  • Smaller breakpoints last (640px) to override

The ESLint rule doesn't understand CSS cascade semantics—it just sorts alphabetically.

The Irony

The StyleX team added enableMediaQueryOrder specifically to handle media query ordering:

"You can now write overlapping media queries in the order you desire, and the compiler will rewrite them so that later queries take precedence over earlier ones."
StyleX v0.15.0 Release Notes

This feature relies on source order. But the StyleX ESLint plugin's sort-keys rule destroys that source order. The left hand doesn't know what the right hand is doing.

The Workaround

Until this is fixed upstream, you need ESLint disable blocks around responsive properties:

const styles = stylex.create({
  container: {
    /* eslint-disable @stylexjs/sort-keys -- max-width cascade: larger breakpoints first */
    padding: {
      default: "2rem",
      "@media (max-width: 768px)": "1.5rem",
      "@media (max-width: 640px)": "1rem",
    },
    /* eslint-enable @stylexjs/sort-keys */
  },
});
Enter fullscreen mode Exit fullscreen mode

The disable/enable block pattern works because:

  • // eslint-disable-next-line only affects the next line
  • The warning is reported on the nested keys, not the property line
  • Block comments cover everything between them

Key Takeaways

  1. Don't blindly trust --fix — Auto-fixes can break semantic ordering that linters don't understand

  2. Test responsive styles after linting — If you run eslint --fix, verify your breakpoints still work

  3. Document your exceptions — The -- max-width cascade: larger breakpoints first comment explains WHY the rule is disabled

  4. File upstream issues — I've filed an issue requesting the sort-keys rule be media-query-aware

The Fix We Need

The @stylexjs/sort-keys rule should either:

  1. Skip sorting inside media query objects — Detect @media keys and preserve author order
  2. Sort by specificity — Understand that max-width needs descending order, min-width needs ascending
  3. Provide configuration — Let users opt-out for specific patterns

Until then, keep those ESLint disable comments handy.


Related Posts:

GitHub Issue:

Top comments (0)