Skip to content

소개 ​

emotion-variants는 @emotion/react 위에 얹는 얇은 레이어예요. 컴포넌트의 모든 시각적 조합(base 스타일, variants, 복합 variants, 기본값)을 하나의 설정 객체에 모아 정의하면, 타입이 완벽하게 추론되는 스타일 함수를 받아 쓸 수 있어요.

풀고자 하는 문제 ​

실제 버튼 컴포넌트에는 보통 여러 시각 축이 있어요. size, color, 채움 방식, 상태 같은 것들이죠. 바닐라 Emotion으로 이걸 다 다루다 보면 스타일이 여기저기로 흩어져요. 룩업 테이블 따로, 조건부 spread 따로, prop 타입 따로요.

tsx
// emotion-variants 없이
import { css } from '@emotion/react';

// 1. 룩업 테이블 — 컴포넌트와 따로 분리됨
const sizeStyles = {
  small: {
    padding: '8px 16px',
    fontSize: 12,
  },
  medium: {
    padding: '10px 20px',
    fontSize: 14,
  },
  large: {
    padding: '12px 24px',
    fontSize: 16,
  },
};

const colorStyles = {
  primary: {
    background: 'blue',
    color: 'white',
  },
  danger: {
    background: 'red',
    color: 'white',
  },
};

// 2. prop 타입은 손으로 작성
type ButtonProps = {
  size?: 'small' | 'medium' | 'large';
  color?: 'primary' | 'danger';
};

// 3. 컴포넌트가 기본값, 조합, 복합 조건까지 모두 떠안음
function Button({ size = 'medium', color = 'primary' }: ButtonProps) {
  return (
    <button
      css={css({
        borderRadius: 8,
        cursor: 'pointer',
        ...sizeStyles[size],
        ...colorStyles[color],
        // 복합 규칙이 컴포넌트 안에 섞여 들어가요
        ...(size === 'large' && color === 'primary' && {
          fontWeight: 'bold',
        }),
      })}
    >
      Click
    </button>
  );
}

새 size 값을 하나 추가하려면 룩업 테이블, prop 타입 union, 그리고 복합 규칙에 얽혀 있다면 컴포넌트까지 손대야 해요.

emotion-variants의 해결 ​

emotion-variants는 이 모든 걸 설정 객체 하나에 모아요.

tsx
// emotion-variants 사용
import { ev } from 'emotion-variants';

const button = ev({
  base: {
    borderRadius: 8,
    cursor: 'pointer',
  },
  variants: {
    size: {
      small: {
        padding: '8px 16px',
        fontSize: 12,
      },
      medium: {
        padding: '10px 20px',
        fontSize: 14,
      },
      large: {
        padding: '12px 24px',
        fontSize: 16,
      },
    },
    color: {
      primary: {
        background: 'blue',
        color: 'white',
      },
      danger: {
        background: 'red',
        color: 'white',
      },
    },
  },
  compoundVariants: [
    {
      variants: {
        size: 'large',
        color: 'primary',
      },
      style: {
        fontWeight: 'bold',
      },
    },
  ],
  defaultVariants: {
    size: 'medium',
    color: 'primary',
  },
});

type ButtonProps = NonNullable<Parameters<typeof button>[0]>;

function Button({ size, color }: ButtonProps) {
  return <button css={button({ size, color })}>Click</button>;
}

새 size 값을 추가하고 싶나요? variants.size에 한 줄 추가하면 끝이에요. prop 타입은 자동으로 따라와요.

한눈에 비교 ​

emotion-variants 없이emotion-variants 사용
스타일 정의 위치룩업 테이블, 여러 파일에 흩어짐설정 객체 한 곳
Variant prop 타입손으로 union 작성설정에서 자동 추론
복합 규칙컴포넌트 안 인라인 조건문compoundVariants 배열
기본값함수 인자 default 값defaultVariants
variant 값 하나 추가룩업, 타입, 컴포넌트 모두 수정variants만 수정
생성된 class names외부에서 접근 불가button.classNames
코드를 처음 읽는 사람파일 3개를 봐야 스타일을 이해객체 하나로 끝

적합하지 않은 경우 ​

  • 컴포넌트에 variant가 없거나 하나뿐이라면, 그냥 css()를 쓰는 게 더 간단해요.
  • 렌더링마다 다른 CSS 변수를 해석해야 하는 런타임 테마가 필요하다면, emotion-variants도 동작하지만 Emotion의 theme을 직접 쓰는 편이 나을 수 있어요.

다음 단계 ​

  • 패키지와 peer dependency를 설치하세요.
  • 시작하기에서 첫 컴포넌트를 만들어 보세요.
  • 핵심 개념에서 base / variants / compoundVariants / defaultVariants를 자세히 알아보세요.

MIT 라이선스에 따라 배포돼요.