인라인 스타일
인라인 스타일 : JSX 요소의 스타일 속성을 사용해, 스타일을 객체 값으로 직접 지정하는 방식
// App.tsx
export default function App() {
return (
<>
<h1 style={{ color: "red", fontSize: "50px" }}>Inline Style</h1>
</>
);
}
* 속성은 카멜케이스로 입력
변수에 스타일을 할당하고, 해당 변수를 바인딩하는 것 가능
// App.tsx
export default function App() {
const h1Style = { color: "red", fontSize: "50px" };
return (
<>
<h1 style={h1Style}>Inline Style</h1>
</>
);
}
컴포넌트에 props로 전달 가능
// App.tsx
import Inline from "./components/Inline";
export default function App() {
const h1Style = { color: "red", fontSize: "50px" };
return (
<>
<Inline h1Style={h1Style} />
</>
);
}
// Inline.tsx
export default function Inline({
h1Style,
}: {
h1Style: { color: string; fontSize: string };
}) {
return (
<>
<h1 style={h1Style}>Inline Component</h1>
</>
);
}
삼항 연산자 사용 가능
// App.tsx
import Inline from "./components/Inline";
export default function App() {
const isLoggedIn = false;
const h1Style = { color: isLoggedIn ? "blue" : "red", fontSize: "50px" };
return (
<>
<Inline h1Style={h1Style} />
</>
);
}
* && 연산자는 사용 불가능
글로벌 스타일
글로벌 스타일 : 별도의 css 파일을 만들어 스타일을 작성하는 방식
ㄴ 우리가 일반적으로 알고 있는 외부 스타일링 방식과 유사
// App.tsx
import "./App.css";
export default function App() {
return (
<>
<h1>App Component</h1>
</>
);
}
/* App.css */
h1 {
color: red;
text-decoration: underline;
font-size: 50px;
}
* React에서 import로 불러온 CSS 파일은 모든 컴포넌트 트리에 영향을 줌
/* App.css */
h1 {
color: red;
text-decoration: underline;
font-size: 50px;
}
// App.tsx
import "./App.css";
import Global from "./components/Global";
export default function App() {
return (
<>
<h1>App Component</h1>
<Global />
</>
);
}
// Global.tsx
export default function Global () {
return (
<>
<h1>Global Component</h1>
</>
);
}

^ 실제로 Global 컴포넌트에서 App.css를 import 하지 않았음에도 DOM 트리 내에서 동일한 스타일을 공유하게 되어 h1 요소에 스타일이 적용됨
* 글로벌 스타일을 적용할 때에는 import 경로를 main.tsx 파일에 몰아서 작성하는 것이 좋음
ㄴ 개별적으로 컴포넌트에 import 하는 것보다 관리하기 편함
CSS 모듈
CSS 모듈 : 확장자가 'module.css'로 끝나는 파일에 CSS를 작성하는 방식
ㄴ 특정 컴포넌트에서만 스타일이 공유되도록 하기 위해 사용
ex) App.module.css
/* App.module.css */
.title {
color: red;
text-decoration: underline;
font-size: 50px;
}
// App.tsx
import styles from "./App.module.css";
import Global from "./components/Global";
export default function App() {
return (
<>
<h1 className={styles.title}>CSS Module</h1>
<Global />
</>
);
}

* CSS 파일에는 클래스 선택자만을 사용해서 스타일을 지정해야 함
ㄴ 태그 선택자, 아이디 선택자와 같은 것들을 사용하는 것은 원칙에 위배
--> 글로벌 스타일처럼 동작
classnames
CSS 모듈 방식은 CSS 모듈 객체를 매번 활용해야 하기 때문에 코드가 길어질 수 있다는 단점 O
--> 이러한 단점을 해결하기 위해서 외부 패키지 중 classnames 패키지를 많이 사용
classnames : CSS 클래스 이름을 동적으로 조합하고 관리할 수 있도록 도와주는 유틸리티
ㄴ npm install classnames 명령어를 사용하여 다운로드
/* App.module.css */
.btn {
width: 100px;
height: 50px;
border: none;
background-color: gray;
color: white;
font-size: 14px;
border-radius: 10px;
margin: 0 10px;
}
.is-active {
background-color: blue;
}
// App.tsx
import styles from "./App.module.css";
export default function App() {
return (
<>
<button className={`${styles.btn} ${styles["is-active"]}`}>버튼</button>
</>
);
}
^ 하나의 태그에 2개의 스타일을 적용하기 위한 방법
--> 코드가 길어지고 가독성이 떨어짐
classnames 패키지 사용
/* App.module.css */
.btn {
width: 100px;
height: 50px;
border: none;
background-color: gray;
color: white;
font-size: 14px;
border-radius: 10px;
margin: 0 10px;
}
.is-active {
background-color: blue;
}
// App.tsx
import classNames from "classnames/bind";
import styles from "./App.module.css";
export default function App() {
const cx = classNames.bind(styles);
return (
<>
<button className={`${styles.btn} ${styles["is-active"]}`}>버튼</button>
<button className={cx("btn", "is-active")}>버튼</button>
</>
);
}
^ cx가 styles 객체 안에서 값을 매핑, 바인딩함
// App.tsx
import classNames from "classnames/bind";
import styles from "./App.module.css";
export default function App() {
const cx = classNames.bind(styles);
const isLoggedIn = false;
return (
<>
<button className={`${styles.btn} ${isLoggedIn ? styles["is-active"] : ""}`}>버튼</button>
<button className={cx("btn", { "is-active": isLoggedIn })}>버튼</button>
</>
);
}
^ 상태 값의 변화에 따라 클래스명을 동적으로 제어할 때, classnames/bind를 활용하면 복잡한 조건부 로직을 객체 구조로 간결하게 표현 가능
CSS-IN-JS 소개
전통적인 웹 개발 방식에서는 HTML, CSS, JavaScript를 각각 분리하여 개발하는 것을 추구
= 관심사 분리라는 소프트웨어 설계 원칙에 따른 것으로, 각 기술의 역할을 명확히 구분하여 유지보수성을 높이고자 함
그러나 웹이 발전하면서 컴포넌트 기반 개발이 보편화되었고, 전통적인 방식이 몇 가지 문제를 일으킴
- 전역 스코프 문제
- 기존의 CSS는 전역 스코프로 동작하기 때문에 클래스명 충돌이 빈번하게 발생
- 상태 관리의 어려움
- UI 상태는 JS로 작성하는데, 스타일은 CSS 파일에서 작성하기 때문에 동적인 상태를 추가하는 게 번거롭고 어려움
- 유지보수의 복잡성
- 프로젝트의 규모가 커질수록, 어떤 CSS가 어떤 컴포넌트에 영향을 주는지 찾기 어려움
- 사용하지 않는 CSS도 완전히 제거하기 어려움
위의 문제를 해결하고자 새롭게 등장한 스타일링 방식이 CSS-IN-JS 방식
CSS-IN-JS : 자바스크립트 파일 안에서 CSS 스타일을 정의하고 적용하는 접근 방식
ㄴ 자바스크립트로 개발하는 컴포넌트 기반 방식에 매우 적합, 동적 스타일링을 쉽게 구현 가능
CSS-IN-JS 라이브러리 중 React에서 자주 사용하는 세 가지 라이브러리
- Styled-Component
- Emotion
- Vanilla Extract
styled-components (기본)
Styled-Components : 스타일 속성을 적용해서 하나의 완성된 컴포넌트 태그를 만들어 사용하는 방식
ㄴ 외부 라이브러리이기 때문에 npm install styled-components 명령어로 패키지를 설치해야만 사용 가능
기본 문법
const 컴포넌트이름 = styled.[태그명]`
스타일 속성;
`;
// App.tsx
import styled from "styled-components";
const Title = styled.h1`
color: red;
text-decoration: underline;
`;
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
export default function App() {
return (
<>
<Wrapper>
<Title>Hello, ReactJS!</Title>
</Wrapper>
</>
);
}

styled-components (props)
// App.tsx
import styled from "styled-components";
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
`;
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
export default function App() {
return (
<>
<Wrapper>
<Title $color="green" $decoration="line-through">Hello, ReactJS!</Title>
</Wrapper>
</>
);
}

styled-components (확장)
기존에 생성한 스타일 컴포넌트에서 일부를 바꾼 새 컴포넌트가 필요한 경우에는 확장을 사용
ㄴ 새로 만들어진 컴포넌트는 기존 컴포넌트의 스타일을 모두 상속받음
* 기존 컴포넌트가 props를 받고 있을 때에는 기존 컴포넌트도 props를 넘겨받아야 함
// App.tsx
import styled from "styled-components";
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
`;
const BigTitle = styled(Title)`
font-size: 50px;
`
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)`
border-color: blue;
`
export default function App() {
return (
<>
<BlueBorderWrapper>
<Title $color="green" $decoration="line-through">Hello, ReactJS!</Title>
<BigTitle $color="blue" $decoration="underline">Hello, Big Title!</BigTitle>
</BlueBorderWrapper>
</>
);
}

styled-components (as, keyframes)
styled-components는 as 속성을 제공
as : 스타일이 입혀진 컴포넌트의 디자인(CSS)은 그대로 유지하면서, 렌더링되는 HTML 태그만 다른 것으로 교체할 때 사용하는 속성
// App.tsx
import styled from "styled-components";
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
`;
const BigTitle = styled(Title)`
font-size: 50px;
`
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)`
border-color: blue;
`
export default function App() {
return (
<>
<BlueBorderWrapper>
<Title $color="green" $decoration="line-through" as="h4">Hello, ReactJS!</Title>
<BigTitle $color="blue" $decoration="underline">Hello, Big Title!</BigTitle>
</BlueBorderWrapper>
</>
);
}
^ as 속성을 활용해 기존 Title 컴포넌트의 children으로 h4 태그로 출력

styled-components는 keyframes 속성을 제공
keyframes : 애니메이션을 하나의 변수(객체)로 저장해두고, 여러 스타일 컴포넌트에서 가져다 쓸 수 있도록 해주는 속성
// App.tsx
import styled, { keyframes } from "styled-components";
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
animation: ${fadeIn} 2s ease-in;
`;
const BigTitle = styled(Title)`
font-size: 50px;
`
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)`
border-color: blue;
`
export default function App() {
return (
<>
<BlueBorderWrapper>
<Title $color="green" $decoration="line-through" as="h4">Hello, ReactJS!</Title>
<BigTitle $color="blue" $decoration="underline">Hello, Big Title!</BigTitle>
</BlueBorderWrapper>
</>
);
}
^ keyframes 속성을 활용해 컴포넌트에 애니메이션 적용


styled-components (CSS 헬퍼, 믹스인)
CSS 헬퍼 : 자주 사용하는 코드 블럭을 하나의 변수(코드 블록)로 묶어서 관리할 수 있게 해주는 함수
ㄴ 조건부로 스타일을 적용하는 용도로 활용 가능
// App.tsx
import styled, { keyframes, css } from "styled-components";
const boxShadowMixin = css`
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.5);
`;
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
animation: ${fadeIn} 2s ease-in;
`;
const BigTitle = styled(Title)`
font-size: 50px;
`
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)<{ $shadow: boolean }>`
border-color: blue;
${(props) => props.$shadow && boxShadowMixin}; // 조건부 스타일 지정
`
export default function App() {
return (
<>
<BlueBorderWrapper $shadow> {/* shadow를 쓰면 shadow가 true로 들어감 */}
<Title $color="green" $decoration="line-through" as="h4">Hello, ReactJS!</Title>
<BigTitle $color="blue" $decoration="underline">Hello, Big Title!</BigTitle>
</BlueBorderWrapper>
</>
);
}

styled-components (전역)
styled-components는 일관된 디자인 시스템을 적용하기 위해 테마 기능을 제공
테마 기능 : 특정 스타일이 정의된 변수를 컴포넌트 트리 전체에 공급하여 이를 스타일 컴포넌트에서 참조할 수 있게 하는 방식
// main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { ThemeProvider } from "styled-components";
const theme = {
colors: {
primary: "red",
secondary: "gray",
},
fontSize: {
normal: "16px",
large: "20px",
},
};
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</StrictMode>
);
// App.tsx
import styled, { keyframes, css } from "styled-components";
const boxShadowMixin = css`
margin: 20px;
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.5);
`;
const fadeIn = keyframes`
from{
opacity: 0;
}
to{
opacity: 1;
}
`;
const Title = styled.h1<{ $color: string; $decoration: string }>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
animation: ${fadeIn} 2s ease-in;
`;
const BigTitle = styled(Title)`
font-size: ${(props) => props.theme.fontSize.large};
`;
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)<{ $shadow: boolean }>`
border-color: ${(props) => props.theme.colors.primary};
${(props) => props.$shadow && boxShadowMixin}
`;
export default function App() {
return (
<>
<BlueBorderWrapper $shadow>
<Title $color="#0000ff" $decoration="underline" as="p">
Hello, ReactJS!
</Title>
<BigTitle $color="#0000ff" $decoration="underline">
Hello, Big Title!
</BigTitle>
</BlueBorderWrapper>
</>
);
}
styled-components (팁)
앰퍼센트(&)를 활용한 선택자 지정 방식으로 여러 스타일 적용 가능
ㄴ 앰퍼센트 : 현재 컴포넌트를 가리키는 지시자
// App.tsx
import styled, { keyframes, css } from "styled-components";
const boxShadowMixin = css`
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.5);
`;
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
animation: ${fadeIn} 2s ease-in;
&:hover {
color: pink;
}
`;
const BigTitle = styled(Title)`
font-size: 50px;
`
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)<{ $shadow: boolean }>`
border-color: blue;
${(props) => props.$shadow && boxShadowMixin};
`
export default function App() {
return (
<>
<BlueBorderWrapper $shadow> {/* shadow를 쓰면 shadow가 true로 들어감 */}
<Title $color="green" $decoration="line-through" as="h4">Hello, ReactJS!</Title>
<BigTitle $color="blue" $decoration="underline">Hello, Big Title!</BigTitle>
</BlueBorderWrapper>
</>
);
}

- $ 기호가 붙은 속성 ($props)
- 오직 스타일 정의를 위한 용도로만 사용
- CSS 내부 처리용
- 실제 HTML 태그로는 전달되지 않고 스타일 컴포넌트 내부에서만 소비된 후 사라짐
- $ 기호가 붙지 않은 속성
- HTML 표준 속성(ex. id, href, title, onClick)이나, 하위 컴포넌트까지 전달되어야 하는 속성들
// App.tsx
import styled, { keyframes, css } from "styled-components";
const boxShadowMixin = css`
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.5);
`;
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const Title = styled.h1<{ $color: string; $decoration: string}>`
color: ${(props) => props.$color};
text-decoration: ${(props) => props.$decoration};
animation: ${fadeIn} 2s ease-in;
&:hover {
color: pink;
}
`;
const BigTitle = styled(Title)`
font-size: 50px;
`
const Wrapper = styled.section`
padding: 2rem;
border: 1px solid red;
`;
const BlueBorderWrapper = styled(Wrapper)<{ $shadow: boolean }>`
border-color: blue;
${(props) => props.$shadow && boxShadowMixin};
`
export default function App() {
return (
<>
<BlueBorderWrapper $shadow> {/* shadow를 쓰면 shadow가 true로 들어감 */}
<Title $color="green" $decoration="line-through" as="h4" onClick={() => alert('hello!')}>Hello, ReactJS!</Title>
<BigTitle $color="blue" $decoration="underline">Hello, Big Title!</BigTitle>
</BlueBorderWrapper>
</>
);
}
^ Title 컴포넌트의 속성 중 $ 기호가 붙은 것과 붙지 않은 것이 있음

'React > 타입스크립트로 배우는 리액트(React.js) : 기초부터 최신 기술까지' 카테고리의 다른 글
| 섹션 9. (프로젝트) 계산기 만들기 (0) | 2026.05.02 |
|---|---|
| 섹션 8. 컴포넌트 스타일링 (2) (0) | 2026.04.21 |
| 섹션 7. 반복 렌더링과 조건부 렌더링 (0) | 2026.04.09 |
| 섹션 6. 컴포넌트 상태 (0) | 2026.04.03 |
| 섹션 5. 컴포넌트와 이벤트 (0) | 2026.03.27 |