styled-components 완벽 가이드 | CSS-in-JS·동적 스타일·테마·실전 활용

styled-components 완벽 가이드 | CSS-in-JS·동적 스타일·테마·실전 활용

이 글의 핵심

styled-components로 CSS-in-JS를 구현하는 완벽 가이드입니다. Tagged Template Literals, Props, Theming, SSR까지 실전 예제로 정리했습니다.

실무 경험 공유: CSS 파일을 styled-components로 전환하면서, 스타일 충돌이 사라지고 컴포넌트 재사용성이 크게 향상된 경험을 공유합니다.

들어가며: “CSS 관리가 어려워요”

실무 문제 시나리오

시나리오 1: 클래스명 충돌이 발생해요
전역 스코프라 충돌합니다. styled-components는 자동으로 고유한 클래스명을 생성합니다.

시나리오 2: 동적 스타일이 필요해요
인라인 스타일은 제한적입니다. styled-components는 Props로 동적 스타일을 지원합니다.

시나리오 3: 테마가 필요해요
수동 관리가 복잡합니다. ThemeProvider로 쉽게 관리합니다.


1. styled-components란?

핵심 특징

styled-components는 React CSS-in-JS 라이브러리입니다.

주요 장점:

  • 컴포넌트 기반: CSS와 JS 통합
  • 자동 Vendor Prefix: 호환성
  • 동적 스타일: Props 기반
  • 테마: ThemeProvider
  • SSR: 서버 사이드 렌더링

2. 설치 및 기본 사용

설치

npm install styled-components
npm install -D @types/styled-components

기본 사용

import styled from 'styled-components';

const Button = styled.button`
  background: #3498db;
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    opacity: 0.8;
  }
`;

export default function App() {
  return <Button>Click me</Button>;
}

3. Props 기반 스타일

interface ButtonProps {
  $variant?: 'primary' | 'secondary';
  $size?: 'small' | 'large';
}

const Button = styled.button<ButtonProps>`
  background: ${(props) => (props.$variant === 'primary' ? '#3498db' : '#2ecc71')};
  color: white;
  padding: ${(props) => (props.$size === 'small' ? '0.25rem 0.5rem' : '0.5rem 1rem')};
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

// 사용
<Button $variant="primary" $size="small">Small Primary</Button>
<Button $variant="secondary">Secondary</Button>

4. 상속

const Button = styled.button`
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

const PrimaryButton = styled(Button)`
  background: #3498db;
  color: white;
`;

const SecondaryButton = styled(Button)`
  background: #2ecc71;
  color: white;
`;

5. as Prop

const Button = styled.button`
  padding: 0.5rem 1rem;
  background: #3498db;
  color: white;
`;

// button으로 렌더링
<Button>Button</Button>

// a로 렌더링
<Button as="a" href="/about">Link</Button>

6. Theming

import { ThemeProvider } from 'styled-components';

const lightTheme = {
  colors: {
    primary: '#3498db',
    background: '#ffffff',
    text: '#333333',
  },
};

const darkTheme = {
  colors: {
    primary: '#3498db',
    background: '#1a1a1a',
    text: '#ffffff',
  },
};

const Button = styled.button`
  background: ${(props) => props.theme.colors.primary};
  color: ${(props) => props.theme.colors.text};
`;

export default function App() {
  const [isDark, setIsDark] = useState(false);

  return (
    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
      <Button onClick={() => setIsDark(!isDark)}>Toggle Theme</Button>
    </ThemeProvider>
  );
}

7. Global Styles

import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  body {
    font-family: Arial, sans-serif;
    background: ${(props) => props.theme.colors.background};
    color: ${(props) => props.theme.colors.text};
  }
`;

export default function App() {
  return (
    <>
      <GlobalStyle />
      {/* 나머지 컴포넌트 */}
    </>
  );
}

8. CSS Helper

import styled, { css } from 'styled-components';

const sharedStyles = css`
  padding: 0.5rem 1rem;
  border-radius: 4px;
`;

const Button = styled.button`
  ${sharedStyles}
  background: #3498db;
`;

const Input = styled.input`
  ${sharedStyles}
  border: 1px solid #ccc;
`;

9. Attrs

const Input = styled.input.attrs<{ $size?: 'small' | 'large' }>((props) => ({
  type: 'text',
  placeholder: 'Enter text',
  size: props.$size === 'small' ? 10 : 20,
}))`
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

10. SSR (Next.js)

_document.tsx

import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

정리 및 체크리스트

핵심 요약

  • styled-components: CSS-in-JS
  • 컴포넌트 기반: CSS와 JS 통합
  • 동적 스타일: Props 기반
  • 테마: ThemeProvider
  • 자동 Prefix: 호환성
  • SSR: 서버 사이드 렌더링

구현 체크리스트

  • styled-components 설치
  • 기본 스타일 작성
  • Props 기반 스타일
  • 상속 활용
  • Theming 구현
  • Global Styles
  • SSR 설정
  • TypeScript 타입

같이 보면 좋은 글

  • React 완벽 가이드
  • Emotion 완벽 가이드
  • CSS-in-JS 가이드

이 글에서 다루는 키워드

styled-components, CSS-in-JS, React, Theming, TypeScript, Frontend, SSR

자주 묻는 질문 (FAQ)

Q. Emotion과 비교하면 어떤가요?

A. 비슷하지만, Emotion이 조금 더 빠르고 번들 크기가 작습니다.

Q. 성능은 어떤가요?

A. 런타임 오버헤드가 있지만, 대부분의 경우 문제없습니다.

Q. Tailwind CSS와 함께 사용할 수 있나요?

A. 가능하지만, 스타일 접근 방식이 달라 권장하지 않습니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, 수많은 React 프로젝트에서 안정적으로 사용하고 있습니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3