Skip to content

[POC] chore(eui): flag dynamic styles in emotion #8797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

weronikaolejniczak
Copy link
Contributor

@weronikaolejniczak weronikaolejniczak commented Jun 13, 2025

Note

This is strictly a PoC. It's not meant to be the final solution. It has to be thoroughly tested.

Summary

This PR monkey-patches the cache.insert function and keeps a map of unique selectors to determine whether styles are dynamic or not.

Screen.Recording.2025-06-13.at.13.21.02.mov

Assumptions

  • If for the same base class (i.e., the part of the selector after the hash) multiple different hashes are generated, it is likely a dynamic style (e.g., a dynamic value passed to the css prop or a style object).
  • If for the same base class only one hash is ever generated, it is likely a static style (i.e., the style is determined solely by static props and does not change at runtime).
  • If the base class itself changes, it is likely due to a change in static props (such as color, size, etc.), which is expected and not considered a dynamic style.
  • The patch cannot directly determine which prop (e.g., css, style, color) caused the style change; it only infers dynamic styles by observing multiple hashes for the same base class.
  • The presence of multiple hashes for the same base class is a strong indicator of runtime-generated (dynamic) styles, which are best handled with the style prop for performance and maintainability.

Advantages

  • early detection - flags dynamic styles at development time, helping teams catch performance and maintainability issues before production (relies on developers actually looking at the browser console),
  • developer guidance - provides actionable warnings, encouraging best practices (e.g., using the style prop for dynamic values),
  • non-Intrusive - only runs in development (and not in production or test), so there’s no runtime or bundle size impact for end users,
  • reusable & modular - the patch can be applied to any Emotion cache, making it easy to use across projects or packages,
  • no app code changes needed - works transparently by monkey-patching Emotion’s cache, so no changes are required in component or app code.

Disadvantages

  • heuristic-based - relies on pattern matching and inference, which may produce false positives or miss some edge cases,
  • limited context - cannot tell exactly which prop css vs. color caused the style change - only that a dynamic style was generated - but could point to underlying issues in EUI as well,
  • console noise - may produce many warnings in large or complex apps, which could annoy developers if not tuned,
  • emotion-specific - tightly coupled to Emotion’s class naming and cache internals; may break if Emotion’s implementation changes,

Feasibility

The approach is feasible and practical for most Emotion-based React projects. It leverages public APIs and common patterns. Can be added to a provider or setup file with minimal effort. Since it’s dev-only, there’s no impact on production builds or performance.

Limitations

  • no prop source tracking - cannot distinguish between dynamic css prop usage and other sources of dynamic styles (e.g., dynamic theme, context, or computed props),
  • pattern dependency - assumes Emotion’s class naming convention (.css-xxxxxx-baseClass) and if this changes, the detection may fail,
  • not 100% accurate - some legitimate use cases (e.g., theming, responsive styles) may trigger warnings even if they’re not “bad” dynamic styles,
  • no automatic fixes - only warns; it does not prevent or fix dynamic style usage.

Why are we making this change?

As recommended by Emotion, you should use style for dynamic styles and css only for static styles.

By dynamic styles, we mean styles that change frequently or are unique to a single element.

Impact to users

This is not a breaking change. It doesn't affect EUI visually.

It's strictly in development to help advocate the usage of style prop for dynamic styles.

QA

Specific checklist

  • Unit tests added are passing in CI
  • There is no regression noticeable in Storybook or documentation website after monkey-patching cache.insert
  • There is no warning about dynamic styles for static styles
  • There is a warning when dynamic styles are used (e.g., generated with a function, changed on every render, changed on interaction, based on props or state, passing an object to css prop that changes reference on every render)
  • There are no warnings in a production build
  • There are warnings locally

@weronikaolejniczak weronikaolejniczak added skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) and removed skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) labels Jun 13, 2025
@weronikaolejniczak weronikaolejniczak self-assigned this Jun 13, 2025
@weronikaolejniczak weronikaolejniczak added the skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) label Jun 13, 2025
@weronikaolejniczak weronikaolejniczak force-pushed the poc/dynamic-style-flagging branch from 53c42fb to f2b6263 Compare June 13, 2025 11:41
@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @weronikaolejniczak

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

cc @weronikaolejniczak

* Applies a monkey patch to the given Emotion cache to flag dynamic styles.
* This is modular and can be reused in other places.
*/
export function patchCacheForDynamicStyles(cache: EmotionCache) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good place to use the decorator pattern

* A utility to normalize selectors by removing the dynamic part
* of the class name generated by Emotion.
*/
function getBaseClass(selector: string): string | null {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that this implementation is configuration-sensitive and may break when a custom labelFormat is used

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 The regex only works with the base cache setup, which is by default named as starting with "css". If a cache is created with createCache which requires a custom key (code) and that key is not "css" then this matching will not work.
We could consider exporting a set of cache names that are matched against to limit the risk. Or provide means to define the cache names.

Copy link
Contributor Author

@weronikaolejniczak weronikaolejniczak Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it is mentioned in one of the limitations in the description. The list of cache names could be limiting but is an idea we can explore.

if (hash) {
hashes.add(hash);
if (hashes.size > 1) {
console.warn(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning should likely be emitted only once per style to reduce console spam in cases of frequently updated styles (e.g., related to scroll positions).

flagDynamicStyles(selector, serialized);
return originalInsert.call(this, selector, serialized, sheet, shouldCache);
};
(cache as any).__patched = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this flag at all?


const panelStyles = (isPrimary: boolean) => {
return css`
background-color: ${isPrimary ? 'blue' : 'gray'};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good example of a technically dynamic style that may or may not be harmful. If it changes too frequently, it might, but overall, this example is okay. It's more of a question of how we want users to define styles specific to component props (or other flags and settings). I usually prefer to follow BEM-like structure and treat props as modifiers to primary component styles, but that's not always handy - it all depends on the structure of the styles (e.g., if you need to style child classes you don't control, this might be your only solid solution).

Generally speaking, we can recommend that Kibana should use BEM-like modifier classes whenever dealing with props affecting component styles, but that's just internal. I wonder if it makes a lot of a difference considering the switch to speedy being enabled by default there (and possibly becoming the default for EUI soon).

Do you know how does this detection work with Emotion's cssPropOptimization?

[Slightly off-topic]

We need to benchmark if there's a difference between:

  1. A single style with dynamically changing value
  2. An array of styles which we compose based on props
  3. Using style the normal way
  4. Using style or setProperty to pass a local CSS variable to use in the regular styles and letting browsers optimize it to reduce the number of style calculations and paints (I don't think this will be as performant as it sounds, though)

Without any data, I'd say that:

  • A single style with a dynamically changing value will take some time to recompute all styles when any input value changes. This is usually very fast but could easily take 20ms for large stylesheets
  • An array of styles could theoretically only recompute the stylesheet with just the single value (property) changing. This would be faster than recomputing the whole stylesheet, but it would also have to recreate the styles array and would be heavily dependent on other styles' caching which also has some resource cost associated with it
  • style attribute updates will cause a rerender and repaint, similar to element.style.setProperty
  • Even if just a single class name changes, the element will need to have its styles recomputed and will likely cause a rerender. I'm not aware of any browser optimizations that would not rerender unless the new class names point to styles that are exactly the same

@weronikaolejniczak weronikaolejniczak changed the title chore(eui): flag dynamic styles in emotion [POC] chore(eui): flag dynamic styles in emotion Jun 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants