Skip to Content

Migrating to v2

v2.0.0 changes three behaviors and adds one required peer dependency. Most codebases only need the install step.

Install the new peer dependency

@emotion/css is now required. It backs className() and classNames, and it is imported unconditionally — so it is needed even if you only ever use the css prop.

- pnpm add emotion-variants @emotion/react
+ pnpm add emotion-variants @emotion/react @emotion/css

undefined now falls back to defaultVariants

In v1 the config and props were merged with object spread, so an explicit undefined erased the default. v2 resolves each key with ??.

const button = ev({
  variants: {
    size: {
      small: {},
      large: {},
    },
  },
  defaultVariants: {
    size: 'small',
  },
});

button({ size: undefined });
// v1: no size style at all
// v2: 'small'

What to remove. Any workaround that stripped undefined before calling, or that duplicated the default at the prop boundary:

  function Button({ size }: Props) {
-   const variants = size !== undefined ? { size } : {};
-   return <button css={button(variants)}>Click</button>;
+   return <button css={button({ size })}>Click</button>;
  }
- function Button({ size = 'small' }: Props) {
+ function Button({ size }: Props) {
    return <button css={button({ size })}>Click</button>;
  }

false and 0 are unaffected — they are real variant values and were never replaced by the default.

The call always returns an array

v1 returned Interpolation<unknown>, which was a bare SerializedStyles object when exactly one style matched. v2 always returns SerializedStyles[].

- (variants?: EvVariants<T>): Interpolation<unknown>;
+ (variants?: EvVariants<T>): SerializedStyles[];

The css prop accepts both, so css={button(...)} needs no change. Code that inspected the result does:

- const styles = button({ size: 'large' });
- const list = Array.isArray(styles) ? styles : [styles];
+ const list = button({ size: 'large' });

classNames returns real class names

In v1 every entry was the string "[object Object]" — String() applied to a SerializedStyles. v2 injects each style through @emotion/css and returns the generated class.

button.classNames.variants.size.small;
// v1: '[object Object]'
// v2: 'css-def456'

Nothing is injected until you first read classNames, and the result is memoized, so css-prop-only code pays nothing.

If you worked around the old behavior by building class names yourself, delete that code. Note that classNames still covers base and single variant values only — for compound variants use className().

New: className(variants)

v2 adds a method returning one class for the whole combination, including compound variants. Useful for className props and non-React code.

<button className={button.className({ size: 'large', color: 'primary' })}>Click me</button>;

New: EvClassNames type

EvClassNames<T> is now exported. See Types.

Edit on GitHub

Last updated on

On this page