How CSS-in-JS Compiles Dynamic Styles at Runtime
CSS-in-JS libraries like styled-components and Emotion dynamically compile styling rules during JavaScript execution by intercepting component-level style definitions, evaluating dynamic expressions against component props, generating deterministic class names, and injecting standard CSS rules into the browser DOM or CSS Object Model (CSSOM). This runtime mechanism bridges dynamic JavaScript state with the browser’s native styling engine.
1. Interception and Dynamic Evaluation
When a component is rendered, the CSS-in-JS library evaluates its style definitions, which are typically defined using tagged template literals or JavaScript style objects. If the style declaration contains dynamic functions or interpolations dependent on props or state, the library executes these functions using the current runtime values. The result is flattened into a static CSS string or key-value representation for that specific render cycle.
2. Hash Generation and Scoping
To prevent global scope collisions and avoid duplicate style
generation, the evaluated CSS string is processed through a fast hashing
algorithm, such as MurmurHash. The output hash serves as a unique,
scoped CSS class name (for example, .css-a1b2c3). If the
computed CSS is identical across renders or components, the resulting
hash will match, enabling built-in caching.
3. Parsing and Preprocessing
The flattened CSS is passed to an embedded, lightweight CSS parser
(such as Stylis). At this stage, the runtime processor handles: * Nested
selectors and pseudo-classes (e.g., &:hover). * Media
queries. * Automatic vendor prefixing for cross-browser compatibility. *
Scope isolation by prepending the generated hash class name to all
target rules.
4. DOM and CSSOM Injection
Once the CSS string is fully preprocessed, the library injects the
style into the document. Modern CSS-in-JS runtimes use two primary
injection strategies: * CSSOM API
(insertRule): For production performance,
libraries directly call
CSSStyleSheet.prototype.insertRule() on a shared
<style> element. This bypasses DOM node re-parsing,
allowing the browser to update styles rapidly. *
<style> Tag Text Node Insertion:
Primarily used during development, the library appends text nodes inside
a <style> element in the document
<head>, keeping styles inspectable in browser
developer tools.
5. Caching and Garbage Collection
To minimize runtime overhead, the library maintains an in-memory
cache (often a Set or Map) tracking all
previously injected class hashes. Before parsing or inserting any new
style, the runtime checks whether the hash already exists in the cache.
If it does, the injection step is bypassed, and the library simply
applies the cached class name to the rendered HTML element. When dynamic
props change, a new hash is generated, the new rule is inserted, and the
element’s class attribute is updated at runtime.