Skip to content

@swc/plugin-emotion ≥ 14.8.0 wraps array css props in css() when the element spreads props, dropping theme-function styles #635

Description

@elado

Describe the bug

When a JSX element has an array css prop and spreads props (<div css={[a, b]} {...props} /> — the standard forwardRef pattern), @swc/plugin-emotion 14.8.0 and later wrap the array in a render-time css([...]) call. That css() runs without theme context, so any theme-function element ((theme) => css({ ... })) is stringified instead of resolved (Emotion logs "Functions that are interpolated in css calls will be stringified"), and its declarations are silently dropped from the output.

14.7.0 does not wrap — it leaves the array raw, so Emotion's JSX runtime resolves the theme function correctly.

The wrapping only happens when {...props} is present; an array css prop without a spread is left raw even on 14.14.0.

Input (input.tsx)

import { css } from '@emotion/react';
import { forwardRef } from 'react';

const styles = {
  row: (theme) => css({ display: 'grid', gap: theme.spacing.sm }),
};

// array css prop + {...props} spread
export const Row = forwardRef((props, ref) => <div ref={ref} css={[styles.row, {}]} {...props} />);

Repro (repro.js)

const { transformSync } = require('@swc/core');
const fs = require('fs');

const out = transformSync(fs.readFileSync('input.tsx', 'utf8'), {
  filename: 'input.tsx',
  jsc: {
    parser: { syntax: 'typescript', tsx: true },
    transform: { react: { runtime: 'automatic', importSource: '@emotion/react' } },
    experimental: {
      plugins: [[require.resolve('@swc/plugin-emotion'), { autoLabel: 'never' }]],
    },
  },
});
console.log(out.code);
npm i @swc/core @swc/plugin-emotion@<version>
node repro.js

Actual (@swc/plugin-emotion 14.8.0 – 14.14.0)

css: /*#__PURE__*/ (0, _react.css)([styles.row, {}]),

At runtime css([styles.row, {}]) is evaluated with no theme → styles.row is stringified → display: grid and gap never reach the DOM.

Expected (matches @swc/plugin-emotion 14.7.0)

css: [styles.row, {}],

The raw array is passed through, and Emotion's JSX runtime resolves the theme function with the active theme, so display: grid / gap are applied.

Bisect

Version Output Theme fn resolved?
14.7.0 raw array ✅ yes
14.8.0 → 14.14.0 wrapped css([...]) ❌ no (stringified)

Impact

Every forwardRef component that spreads props onto an element with a theme-function array css prop loses those styles. This is a common pattern, so the effect is broad (in our case it broke notebook layout — display: grid missing on a row). Workaround is pinning @swc/plugin-emotion to 14.7.0.

Version

  • @swc/plugin-emotion: 14.8.0 – 14.14.0 (14.7.0 OK)
  • @swc/core: 1.15.43 (reproduces across versions)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions