diff --git a/.changeset/weak-cooks-compare.md b/.changeset/weak-cooks-compare.md
new file mode 100644
index 0000000000..8056ad0abe
--- /dev/null
+++ b/.changeset/weak-cooks-compare.md
@@ -0,0 +1,5 @@
+---
+'@emotion/react': minor
+---
+
+Automatic labeling at runtime is now an opt-in feature. Define `globalThis.EMOTION_RUNTIME_AUTO_LABEL = true` before Emotion gets initialized to enable it.
diff --git a/docs/labels.mdx b/docs/labels.mdx
index 6e7a1c628c..dbdf9c2b57 100644
--- a/docs/labels.mdx
+++ b/docs/labels.mdx
@@ -2,7 +2,7 @@
title: 'Labels'
---
-Emotion adds a css property called `label`, the value of it is appended to the end of the class name, so it's more readable than a hash. `@emotion/babel-plugin` adds these labels automatically based on the variable name and other information, so they don't need to be manually specified.
+Emotion adds a CSS property called `label` which is appended to the generated class name to make it more readable. `@emotion/babel-plugin` adds these labels automatically based on the variable name and other information, so they don't need to be manually specified.
```jsx
// @live
@@ -29,3 +29,16 @@ render(
)
```
+
+## Automatic Labeling at Runtime
+
+If you are not using `@emotion/babel-plugin`, you can still get automatic labels in development by setting the following global flag:
+
+```js
+globalThis.EMOTION_RUNTIME_AUTO_LABEL = true
+```
+
+This feature is opt-in because:
+
+- If you use server-side rendering and test your site in Safari, you may get spurious hydration warnings because the label computed on the server does not match the label computed in Safari.
+- This feature may degrade performance if the number of elements using the `css` prop is very large.
diff --git a/packages/react/__tests__/css.js b/packages/react/__tests__/css.js
index 2d599f4df0..1c8579a553 100644
--- a/packages/react/__tests__/css.js
+++ b/packages/react/__tests__/css.js
@@ -10,6 +10,10 @@ import createCache from '@emotion/cache'
console.error = jest.fn()
console.warn = jest.fn()
+beforeEach(() => {
+ delete globalThis.EMOTION_RUNTIME_AUTO_LABEL
+})
+
afterEach(() => {
jest.clearAllMocks()
safeQuerySelector('body').innerHTML = ''
@@ -185,7 +189,27 @@ test('speedy option from a custom cache is inherited for styles', () =
expect(safeQuerySelector('body style').textContent).toEqual('')
})
+it('does not autoLabel without babel or EMOTION_RUNTIME_AUTO_LABEL', () => {
+ let SomeComp = props => {
+ return (
+
+ something
+
+ )
+ }
+ const tree = renderer.create()
+
+ expect(tree.toJSON().props.className).toMatch(/css-[^-]+/)
+})
+
test('autoLabel without babel', () => {
+ globalThis.EMOTION_RUNTIME_AUTO_LABEL = true
+
let SomeComp = props => {
return (
{
})
test('autoLabel without babel (sanitized)', () => {
+ globalThis.EMOTION_RUNTIME_AUTO_LABEL = true
+
let SomeComp$ = props => {
return (
diff --git a/packages/react/__tests__/rehydration.js b/packages/react/__tests__/rehydration.js
index 113a96e2c2..ae9cdc85c4 100644
--- a/packages/react/__tests__/rehydration.js
+++ b/packages/react/__tests__/rehydration.js
@@ -53,8 +53,8 @@ beforeEach(() => {
test("cache created in render doesn't cause a hydration mismatch", () => {
safeQuerySelector('body').innerHTML = [
'
',
- '',
- '
Hello world!
',
+ '',
+ '
Hello world!
',
'
'
].join('')
@@ -141,7 +141,7 @@ test('initializing another Emotion instance should not move already moved styles
data-s=""
>
- .stl-1pdkrhd-App{color:hotpink;}
+ .stl-168r6j{color:hotpink;}
@@ -189,7 +189,7 @@ test('initializing another Emotion instance should not move already moved styles
data-s=""
>
- .stl-1pdkrhd-App{color:hotpink;}
+ .stl-168r6j{color:hotpink;}
diff --git a/packages/react/src/emotion-element.js b/packages/react/src/emotion-element.js
index 211dbac662..2c2b7cc3cd 100644
--- a/packages/react/src/emotion-element.js
+++ b/packages/react/src/emotion-element.js
@@ -40,10 +40,16 @@ export const createEmotionProps = (
newProps[typePropName] = type
- // For performance, only call getLabelFromStackTrace in development and when
- // the label hasn't already been computed
+ // Runtime labeling is an opt-in feature because:
+ // - It causes hydration warnings when using Safari and SSR
+ // - It can degrade performance if there are a huge number of elements
+ //
+ // Even if the flag is set, we still don't compute the label if it has already
+ // been determined by the Babel plugin.
if (
process.env.NODE_ENV !== 'production' &&
+ typeof globalThis !== 'undefined' &&
+ !!globalThis.EMOTION_RUNTIME_AUTO_LABEL &&
!!props.css &&
(typeof props.css !== 'object' ||
typeof props.css.name !== 'string' ||