Styled-components 기초
styled-components로 CSS를 컴포넌트에 결합하고 props 기반 동적 스타일·태그 변경·스타일 확장을 구현합니다.
우리는 CSS 모듈을 통해 클래스 이름 충돌 문제를 해결하고 컴포넌트 단위 스타일링을 구현하는 방법을 배웠습니다.
이제 5장 스타일링과 CSS의 세 번째 방법으로, CSS-in-JS의 대표 라이브러리 중 하나인 Styled-components를 소개합니다.
Styled-components는 자바스크립트 코드 안에서 CSS를 작성하고, 이를 컴포넌트에 직접 연결해 사용하는 방식입니다.
이 방식은 스타일 정의를 컴포넌트 코드 가까이에 둘 수 있게 합니다. 다만 결과는 브라우저가 처리하는 CSS와 class이므로, CSS 캐스케이드와 DOM 속성 계약까지 사라지는 것은 아닙니다.
CSS-in-JS란?
CSS-in-JS는 말 그대로 JavaScript 코드 안에 CSS를 작성하는 방식을 통칭합니다.
기존에는 CSS 파일을 별도로 분리하여 관리했지만, CSS-in-JS는 컴포넌트 로직과 스타일 로직을 하나의 JavaScript 파일 안에서 함께 작성합니다.
Styled-components의 핵심 아이디어- 컴포넌트 기반 스타일링: 스타일이 적용된 리액트 컴포넌트를 생성합니다.
- 클래스 이름 자동 생성: 라이브러리가 스타일 규칙과 연결되는 class을 생성합니다. 생성 문자열의 구체적인 모양을 애플리케이션 계약으로 사용해서는 안 됩니다.
- 동적 스타일링 용이: 보간 함수에서 props와 theme을 읽어 스타일을 유연하게 변경할 수 있습니다. state 값도 prop으로 전달해 사용할 수 있습니다.
- 컴포넌트 중심의 이름과 규칙: 스타일을 이름 있는 컴포넌트로 묶어 재사용합니다. 그러나 전역 CSS, 전달한
className, 선택자 우선순위, 소스 순서, 상속은 여전히 일반 CSS 캐스케이드에 참여합니다.
Styled-components 설치
Styled-components를 사용하기 위해서는 먼저 라이브러리를 설치해야 합니다.
터미널에서 다음 명령어를 실행하세요.
npm install styled-components
# 또는
yarn add styled-componentsStyled-components 기본 사용법
Styled-components는 태그드 템플릿 리터럴(Tagged Template Literals) 문법을 사용하여 스타일이 적용된 컴포넌트를 생성합니다.
import styled from 'styled-components'; // styled 객체 임포트
// 1. styled.{HTML 태그} 백틱(``) 안에 CSS 작성
const StyledButton = styled.button`
background-color: #61dafb;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s ease-in-out;
&:hover { /* 가상 클래스도 바로 사용 가능 */
background-color: #21a1f1;
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
`;
function MyComponent() {
return (
// 2. 스타일링된 컴포넌트 사용
<StyledButton>클릭하세요</StyledButton>
);
}예제: 기본적인 스타일링 컴포넌트 만들기
src/components/StyledComponentsBasic.js 파일을 생성하고 다음 코드를 작성하세요.
import React from 'react';
import styled from 'styled-components'; // styled-components 임포트
// 1. 스타일이 적용된 <Container> 컴포넌트 생성
const Container = styled.div`
background-color: #f0f8ff; /* 연한 하늘색 배경 */
padding: 30px;
border-radius: 10px;
border: 1px solid #dbeafe;
text-align: center;
margin-bottom: 30px;
`;
// 2. 스타일이 적용된 <Title> 컴포넌트 생성
const Title = styled.h2`
color: #1e90ff; /* 진한 파란색 */
font-size: 2.5em;
margin-bottom: 15px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
`;
// 3. 스타일이 적용된 <Paragraph> 컴포넌트 생성
const Paragraph = styled.p`
color: #4682b4; /* 중간 파란색 */
font-size: 1.2em;
line-height: 1.7;
margin-bottom: 20px;
`;
// 4. 스타일이 적용된 <StyledButton> 컴포넌트 생성
const StyledButton = styled.button`
background-color: #007bff;
color: white;
padding: 12px 25px;
border: none;
border-radius: 7px;
cursor: pointer;
font-size: 1.1em;
font-weight: bold;
transition: background-color 0.3s ease, transform 0.2s ease;
&:hover { /* 가상 클래스 */
background-color: #0056b3;
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
`;
function StyledComponentsBasic() {
return (
<Container> {/* 생성한 Styled 컴포넌트 사용 */}
<Title>Styled-components 기초 예제</Title>
<Paragraph>
이것은 styled-components로 스타일링된 단락입니다.
스타일 정의가 컴포넌트 코드 가까이에 있습니다.
</Paragraph>
<StyledButton>Styled 버튼</StyledButton>
</Container>
);
}
export default StyledComponentsBasic;App.js에서 렌더링
App.js 파일을 수정하여 StyledComponentsBasic 컴포넌트를 렌더링하세요.
import React from 'react';
import './index.css'; // 전역 스타일 유지
import StyledComponentsBasic from './components/StyledComponentsBasic';
// import CssModulesExample from './components/CssModulesExample'; // 필요 없으면 주석 처리 또는 삭제
function App() {
return (
<div className="App">
<h1>Styled-components 학습</h1>
<StyledComponentsBasic />
</div>
);
}
export default App;브라우저에서 개발자 도구(F12)를 열어 엘리먼트 탭을 확인해 보세요.
각 HTML 요소에는 styled-components가 스타일 규칙과 연결하기 위해 만든 class 토큰이 붙습니다. 그 토큰의 정확한 문자열 형식은 버전과 빌드 설정에 따라 달라질 수 있으므로 코드에서 직접 참조하지 않습니다.
Props를 이용한 동적 스타일링
Styled-components의 가장 큰 장점 중 하나는 props를 이용하여 스타일을 동적으로 변경할 수 있다는 것입니다.
태그드 템플릿 리터럴 내에서 JavaScript 코드를 삽입할 수 있는 것처럼, props를 인자로 받는 함수를 작성하여 동적인 스타일을 적용할 수 있습니다.
import React from 'react';
import styled from 'styled-components';
// $로 시작하는 transient prop은 스타일 계산에는 쓰이지만 DOM에는 전달되지 않습니다.
const DynamicButton = styled.button`
background-color: ${props => props.$primary ? '#007bff' : '#6c757d'}; /* (1) $primary에 따라 배경색 변경 */
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
margin: 5px;
transition: background-color 0.3s ease;
&:hover {
background-color: ${props => props.$primary ? '#0056b3' : '#5a6268'};
}
`;
// props에 따라 크기가 변하는 텍스트
const ResizableText = styled.p`
font-size: ${props => props.$size || '1em'}; /* (2) $size에 따라 폰트 크기 변경 */
color: ${props => props.$color || '#333'}; /* (3) $color에 따라 색상 변경 */
margin-top: 15px;
`;
function DynamicStyledComponents() {
return (
<div style={{ textAlign: 'center', margin: '20px', padding: '20px', border: '1px dashed #ccc', borderRadius: '8px' }}>
<h2>동적 스타일링 예제</h2>
<DynamicButton $primary>기본 버튼</DynamicButton>
<DynamicButton>보조 버튼</DynamicButton>
<ResizableText $size="1.5em" $color="#d9534f">크고 빨간 텍스트</ResizableText>
<ResizableText $size="0.8em" $color="#5cb85c">작고 초록 텍스트</ResizableText>
</div>
);
}
export default DynamicStyledComponents;App.js에 DynamicStyledComponents 컴포넌트를 추가하고 실행해 보세요.
$primary, $size, $color와 같은 props를 전달하여 컴포넌트의 스타일이 동적으로 변경되는 것을 확인할 수 있습니다.
이 방식은 인라인 스타일링처럼 동적 값을 다루면서도 CSS 문법을 함께 사용할 수 있다는 장점을 가집니다.
$ 접두사가 붙은 transient prop은 보간 함수에서 읽되 최종 target으로 전달하지 않는 스타일 전용 값입니다. 최신 안정판인 styled-components v6는 v5의 기본 자동 prop 필터를 제공하지 않으므로, 일반 이름의 스타일 전용 prop을 그대로 두면 target까지 전달될 수 있습니다. 이름만으로 충분하지 않은 조합에서는 withConfig({ shouldForwardProp })로 전달 여부를 명시합니다. 반면 type, aria-label처럼 실제 HTML 요소가 이해하는 속성은 DOM까지 전달해야 합니다. styled(MyComponent)처럼 사용자 컴포넌트를 감쌀 때는 그 컴포넌트가 받은 className을 실제 DOM 요소에 연결해야 생성된 스타일이 적용됩니다.
One input, two contracts
보간은 CSS 규칙을 만들고, forwarding은 target이 받을 prop을 정한다.
- 1. JSX 입력
$tone은 스타일 전용 값이고,type과aria-label은 실제 요소가 받을 속성이다.- 2. styled 평가
- 보간 함수는 props와 theme을 읽는다.
.attrs가 붙인 값도 이 컴포넌트 계약에 합쳐진다. - 3. CSS·class 출력
- 평가된 선언은 CSS 규칙과 생성 class에 연결된다. 생성 문자열 자체는 공개 API가 아니다.
- 4. prop 전달
- transient prop은 target으로 보내지 않는다. 더 복잡한 조합은
shouldForwardProp으로 필터한다. - 5. DOM에서 결합
- 최종 요소는 생성 class과 허용된 속성을 받는다. React의 DOM prop 규칙은 그대로 적용된다.
- 단순 HTML target
- 표준 HTML·ARIA·data 속성은 전달한다. 스타일 전용 값은
$접두사로 분리한다. - 사용자 컴포넌트 target
- 컴포넌트가 props를 받고, 생성 스타일을 적용할 DOM 요소에
className을 전달해야 한다. - CSS 경계
- 전역 CSS, 추가
className, 상속, 우선순위와 소스 순서는 생성 규칙에도 정상적으로 작동한다.
as prop을 이용한 태그 변경
Styled-components로 생성된 컴포넌트는 as prop을 사용하여 렌더링될 HTML 태그를 변경할 수 있습니다.
이는 동일한 스타일을 유지하면서 다른 시맨틱 태그를 사용해야 할 때 유용합니다.
import React from 'react';
import styled from 'styled-components';
const ButtonLike = styled.button`
background-color: #ffc107;
color: #333;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
margin: 5px;
transition: background-color 0.3s ease;
&:hover {
background-color: #e0a800;
}
`;
function AsPropExample() {
return (
<div style={{ textAlign: 'center', margin: '20px', padding: '20px', border: '1px dashed #ccc', borderRadius: '8px' }}>
<h2>'as' prop 예제</h2>
<ButtonLike>나는 button 태그입니다</ButtonLike>
{/* 같은 스타일로 실제 탐색 링크를 렌더링 */}
<ButtonLike as="a" href="/docs">문서로 이동</ButtonLike>
</div>
);
}
export default AsPropExample;App.js에 AsPropExample 컴포넌트를 추가하여 실행해 보세요.
두 요소는 같은 스타일을 가지지만 개발자 도구에서 서로 다른 HTML 태그로 렌더링됩니다. as는 스타일을 유지하며 target을 바꿀 뿐, 접근성 의미를 자동으로 보정하지는 않습니다. 동작에는 button, 탐색에는 유효한 href가 있는 a처럼 목적에 맞는 요소와 속성을 함께 선택해야 합니다.
다른 컴포넌트 확장하기
기존 컴포넌트를 감싸 공통 스타일 계약을 재사용하고 새로운 규칙을 추가할 수 있습니다.
import React from 'react';
import styled from 'styled-components';
const BaseButton = styled.button`
padding: 10px 20px;
font-size: 1em;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease, color 0.3s ease;
`;
// BaseButton을 확장하여 새로운 규칙 추가
const PrimaryButton = styled(BaseButton)`
background-color: #28a745;
color: white;
border: 1px solid #28a745;
&:hover {
background-color: #218838;
border-color: #218838;
}
`;
// BaseButton을 확장하여 또 다른 규칙 추가
const DangerButton = styled(BaseButton)`
background-color: #dc3545;
color: white;
border: 1px solid #dc3545;
&:hover {
background-color: #c82333;
border-color: #c82333;
}
`;
function ExtendedStyledComponent() {
return (
<div style={{ textAlign: 'center', margin: '20px', padding: '20px', border: '1px dashed #ccc', borderRadius: '8px' }}>
<h2>컴포넌트 확장 예제</h2>
<BaseButton>기본 버튼</BaseButton>
<PrimaryButton style={{ marginLeft: '10px' }}>확장된 기본 버튼</PrimaryButton>
<DangerButton style={{ marginLeft: '10px' }}>확장된 위험 버튼</DangerButton>
</div>
);
}
export default ExtendedStyledComponent;App.js에 ExtendedStyledComponent 컴포넌트를 추가하여 실행해 보세요.
BaseButton의 기본 스타일을 재사용하면서 각각 다른 색상의 버튼을 만들 수 있습니다. 이때 styled(Base)는 JavaScript 클래스 상속이 아니라 스타일 가능한 React 컴포넌트를 감싸는 조합입니다. 사용자 정의 Base라면 className을 받아 최종 DOM 요소에 전달해야 하며, 기본 규칙과 추가 규칙은 일반 CSS 캐스케이드로 함께 계산됩니다.
.attrs(...)는 모든 인스턴스에 필요한 type="button" 같은 정적·계산 속성의 기본값을 붙일 때 사용합니다. ThemeProvider의 theme이나 보간 함수가 만든 규칙도 CSS로 출력된 뒤에는 전역 규칙, 사용자 className, 상속, 우선순위, 소스 순서의 영향을 받습니다. 따라서 “컴포넌트 가까이 정의된다”와 “스타일이 완전히 격리된다”를 같은 뜻으로 이해하면 안 됩니다.
아래 다이어그램은 렌더할 target만 바꾸는 as와, 새 이름·규칙을 가진 변형을 만드는 styled(Base)의 선택 경계를 정리합니다.
Change the target or define a variant?
이번 렌더의 target을 바꾸는 일과 새 컴포넌트 변형을 정의하는 일을 구분한다.
-
이번 렌더의 HTML 의미만 다른가?
같은 시각 규칙으로 target만 바꾸면
as를 사용한다. target에 필요한 속성과 접근성 동작도 함께 제공한다. -
재사용할 Base 계약과 추가 규칙이 있는가?
반복해서 쓸 이름 있는 변형이면
styled(Base)를 사용한다. 사용자 Base는className을 전달해야 한다. -
두 조건에 모두 해당하지 않는가?
처음부터 목적에 맞는 native target으로 새 styled 요소를 정의한다.
as의 책임- 스타일은 유지하지만 의미를 자동 보정하지 않는다. 동작은
button, 탐색은 유효한href가 있는a를 고른다. styled(Base)의 책임- Base의 컴포넌트 계약을 감싸고 CSS 규칙을 추가한다. 추가 규칙은 기본 규칙과 캐스케이드한다.
.attrs와 theme- attrs는 공통 속성·기본값을, theme은 보간 입력을 제공한다. 어느 쪽도 잘못 고른 HTML 의미를 대신 고쳐 주지 않는다.
Styled-components 기초는 여기까지입니다.
이 장에서는 Styled-components의 기본적인 설치와 사용법, props를 이용한 동적 스타일링, as prop을 이용한 태그 변경, 그리고 기존 컴포넌트를 확장하는 방법까지 상세하게 다루었습니다.
Styled-components는 스타일을 컴포넌트 로직과 함께 관리하고, JavaScript 값을 활용하여 동적인 스타일링을 유연하게 구현할 수 있게 해줍니다.
이 방식의 핵심은 스타일 정의의 위치만이 아니라 보간 값, prop 전달, 실제 HTML 의미, CSS 캐스케이드의 경계를 명시하는 데 있습니다.
다음 절에서는 CSS-in-JS의 또 다른 주요 기능인 테마(Theming)와 전역 스타일(Global Styles)에 대해 알아보겠습니다.
공식 문서
- styled-components The Basics: 보간 함수, 전달되는 props,
.attrs의 기본 사용법 - styled-components API Reference:
as, transient props,shouldForwardProp,ThemeProvider - styled-components v6 마이그레이션 FAQ: 기본 자동 prop 필터 제거와
StyleSheetManager경계 - React DOM 공통 props: HTML 요소가 받는 표준·사용자 정의 속성 계약
- React Unknown Prop Warning: DOM으로 전달하면 안 되는 컴포넌트 전용 prop을 소비하는 방법