인라인 스타일링과 CSS 클래스 사용
React에서 className과 style prop이 DOM에 전달하는 값, 동적 스타일과 재사용 규칙의 경계, 일반 CSS의 전역 범위를 비교합니다.
React의 내장 DOM 컴포넌트는 CSS 클래스 이름을 받는 className과 스타일 선언 객체를 받는 style prop을 지원합니다.
두 방식은 서로 배타적인 스타일링 체계가 아닙니다. 미리 정의할 수 있는 규칙은 CSS 클래스에 두고, 렌더마다 계산되는 소수의 값만 style로 전달하는 것이 기본 경계입니다.
이 절에서는 일반 CSS를 기준으로 두 prop의 역할을 비교합니다. CSS Modules와 CSS-in-JS는 뒤 절에서 별도로 다룹니다.
두 prop이 받는 값
className은 DOM 요소의 CSS 클래스 이름을 나타내는 문자열입니다.style은 CSS 선언을 나타내는 JavaScript 객체입니다.- 두 prop은 같은 요소에 함께 사용할 수 있습니다.
<div
className="progress"
style={{ width: `${percent}%` }}
/>React는 CSS 파일을 프로젝트에 추가하는 방법을 규정하지 않습니다. 아래의 CSS import 예제는 이 구문을 처리하는 빌드 도구나 프레임워크를 사용하는 프로젝트를 전제로 합니다.
style prop으로 값 전달하기
style에는 문자열이 아니라 객체를 전달합니다. CSS 속성 이름은 DOM style 프로퍼티처럼 대부분 카멜 케이스로 작성합니다.
export default function InlineStylingExample({ percent }) {
const value = Number(percent);
const safePercent = Number.isFinite(value)
? Math.min(100, Math.max(0, value))
: 0;
return (
<div
className="meter"
role="progressbar"
aria-label="진행률"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={safePercent}
>
<span
className="meter__fill"
style={{ width: `${safePercent}%` }}
/>
</div>
);
}이 예제는 입력을 숫자로 변환한 뒤 유한한 값만 0부터 100 사이로 제한합니다. NaN이나 무한대처럼 진행률로 표현할 수 없는 값은 0으로 처리해 CSS 너비와 aria-valuenow가 같은 유효한 수치를 사용하도록 합니다.
background-color는 backgroundColor, font-size는 fontSize처럼 씁니다. 명시적인 단위가 필요하면 '1.25rem', '50%'처럼 문자열로 전달합니다.
숫자 값에는 React가 일반적으로 px를 붙이지만, lineHeight, opacity, flex처럼 단위가 없는 CSS 프로퍼티에는 붙이지 않습니다.
<section
style={{
marginTop: 16, // 16px
lineHeight: 1.5, // 단위 없음
opacity: 0.8, // 단위 없음
}}
>
내용
</section>style이 잘 맞는 범위
- 데이터에서 계산한 너비, 좌표, 색상처럼 값을 미리 알 수 없는 경우
- 한 요소에만 필요한 작은 동적 선언
- 클래스 규칙과 함께 전달하는 CSS 사용자 지정 속성 값
style의 경계
style 객체는 해당 요소의 인라인 선언을 표현합니다. :hover, ::before, @media, @keyframes처럼 선택자나 at-rule이 필요한 CSS 문법을 객체 안에 직접 작성할 수 없습니다. 이런 동작은 JavaScript 마우스 이벤트로 흉내 내기보다 CSS 규칙으로 표현하는 편이 키보드·포인터·미디어 조건을 함께 다루기 쉽습니다.
또한 style이 한 요소에 붙는다는 사실은 컴포넌트 스코프를 만든다는 뜻이 아닙니다. 상속되는 속성은 자손에 영향을 주고, 외부 스타일시트의 다른 규칙도 같은 요소와 자손에 계속 참여합니다. 클래스 이름 충돌은 피하지만 CSS 전체를 격리하지는 않습니다.
큰 정적 스타일 객체를 JSX마다 반복하면 규칙의 재사용과 변경 지점을 파악하기 어려워집니다. React 공식 문서도 값을 미리 알 수 없는 동적 스타일에 style을 제한하고, 그 밖의 경우에는 일반 CSS 클래스를 권장합니다.
className으로 CSS 규칙 적용하기
React에서는 HTML의 class에 해당하는 prop 이름으로 className을 사용합니다.
import './CssClassesExample.css';
export default function CssClassesExample({ isPrimary, disabled }) {
const buttonClassName = [
'button',
isPrimary && 'button--primary',
].filter(Boolean).join(' ');
return (
<button className={buttonClassName} disabled={disabled}>
저장
</button>
);
}.button {
border: 1px solid #d9d9d9;
border-radius: 0.375rem;
padding: 0.625rem 1rem;
transition: background-color 160ms ease;
}
.button:hover:not(:disabled) {
background-color: #e6e6e6;
}
.button--primary {
background-color: #087ea4;
color: white;
}
@media (width <= 40rem) {
.button {
width: 100%;
}
}조건부 스타일도 style만의 기능은 아닙니다. JavaScript로 className 문자열을 만들면 상태에 따라 미리 정의한 규칙을 선택할 수 있습니다. 클래스 조합이 복잡해지면 프로젝트가 이미 사용하는 clsx나 classnames 같은 도구를 검토할 수 있습니다.
CSS 클래스가 잘 맞는 범위
- 여러 요소와 컴포넌트에서 반복되는 시각 규칙
:hover,:focus-visible, 가상 요소, 미디어 쿼리, 키프레임처럼 선택자와 at-rule이 필요한 규칙primary,selected,compact처럼 화면 상태에 이름을 붙여 표현하는 경우
일반 CSS의 전역 범위
일반 스타일시트의 클래스 선택자는 React 컴포넌트 경계를 알지 못합니다. 같은 문서에 로드된 .button 규칙은 일치하는 모든 요소에 적용되므로, 큰 프로젝트에서는 이름 충돌과 의도하지 않은 덮어쓰기를 관리해야 합니다.
BEM 같은 네이밍 규칙은 충돌 가능성을 줄이지만 스코프를 기술적으로 강제하지는 않습니다. 클래스 이름의 지역 범위가 필요하면 CSS Modules처럼 빌드 시 고유한 클래스 이름을 만드는 방식을 사용할 수 있습니다.
선택 기준
Rule or runtime value?
알려진 규칙은 className으로 선택하고, 미리 알 수 없는 값만 style 객체로 전달한다.
| 판단 기준 | className + CSS 규칙 |
style 객체 |
|---|---|---|
| 전달하는 값 | 클래스 이름 문자열 | 카멜 케이스 CSS 선언 객체 |
| 우선 쓰는 때 | 규칙과 UI 상태를 미리 정의하고 여러 곳에서 재사용할 때 | 데이터에서 계산되어 렌더 전에는 알 수 없는 개별 값을 전달할 때 |
| 표현 범위 | 선택자, 가상 상태·요소, 미디어 쿼리, 키프레임 | 해당 요소의 인라인 선언만 표현하며 선택자와 at-rule은 포함하지 못함 |
| 조건부 변경 | JavaScript로 클래스 문자열을 조합해 이름 붙은 상태를 선택 | JavaScript 값으로 속성값을 직접 계산 |
| 범위와 재사용 | 규칙을 재사용하지만 일반 CSS 클래스는 문서 전역에서 충돌할 수 있음 | 클래스 이름 충돌은 없지만 컴포넌트 스코프나 CSS 격리를 만들지는 않음 |
className+ CSS- 값 클래스 이름 문자열
- 선택 알려진 규칙과 이름 붙은 UI 상태
- 기능 선택자, 가상 상태, 미디어 쿼리, 키프레임
- 경계 일반 CSS 클래스는 문서 전역에서 충돌할 수 있다.
style객체- 값 카멜 케이스 CSS 선언 객체
- 선택 데이터에서 계산한 개별 값
- 제약 선택자와 at-rule은 직접 표현하지 못한다.
- 경계 클래스 충돌은 피하지만 컴포넌트 스코프를 만들지는 않는다.
style로 좁히면 책임이 선명하다.실전에서는 둘을 함께 쓰는 경우가 많습니다. 구조, 상호작용 상태, 반응형 규칙은 클래스에 두고 계산된 값만 인라인으로 전달하면 CSS 기능과 데이터 기반 표현을 모두 유지할 수 있습니다.
<div
className={isSelected ? 'card card--selected' : 'card'}
style={{ '--card-progress': `${progress}%` }}
>
{children}
</div>className은 알려진 규칙과 상태 이름을 선택하고, style은 실행 중 계산된 값을 전달합니다. 어느 쪽을 쓰든 먼저 무엇이 바뀌는가와 그 규칙을 어디에서 재사용하는가를 구분해야 합니다.
예제 컴포넌트 함께 렌더링하기
import './App.css';
import InlineStylingExample from './components/InlineStylingExample.jsx';
import CssClassesExample from './components/CssClassesExample.jsx';
export default function App() {
return (
<main className="app">
<h1>React 스타일링 기초</h1>
<InlineStylingExample percent={72} />
<CssClassesExample isPrimary disabled={false} />
</main>
);
}이 예제에서 CSS 파일 import를 실제로 처리하는 방식은 사용 중인 빌드 도구나 프레임워크의 문서를 확인해야 합니다.