리액트에서 컴포넌트를 스타일링하는 방법은 여러 가지가 있다.
그 중에서 요구하는 스펙이나 자신에게 맞는 방법을 사용하면 된다.
가장 흔하고 일반적인 방식이다.
css 파일을 따로 만들고 클래스나 아이디, 태그에 따라 스타일링하는 방법이다.
프로젝트에 따라 이름 짓는 규칙은 다를 수도 있지만
가장 많이 쓰이는 방법은 BEM 네이밍이라는 것이 있다.
BlockElementModifier의 약자이다.
작명규칙
-로 연결하여 작명한다.서로 중첩될 수 있으며, 몇 겹으로 중첩되는 것도 허용된다.
상태가 아닌 용도를 나타내야 한다.
<!-- 용도: 에러인 것을 나타내고 있다. -->
<div class="error"></div>
<!-- 잘못된 사용: 상태를 나타내고 있다. -->
<div class="violet"></div>
외부의 환경에 의존적이지 않기 위해서
block 자체에 외부 여백(margin)이나, 위치(position)를 설정하지 않아야 한다.
Block은 독립적이지만 Element는 블록에 의존적이다.
자식이 속한 불럭 내에서만 사용하며, 다른 곳에는 사용해서는 안 된다.
<!-- tab은 블록이고, __item은 엘리먼트이다. -->
<ul class="tab">
<li class="tab__item">1</li>
<li class="tab__item">2</li>
<li class="tab__item">3</li>
</ul>
엘리먼트는 상태가 아닌 용도를 나타낸다.
여떤 용도인가? [O]
item, text, button
어떻게 생겼는가? [X]
big, small
블럭이나, 엘리먼트의 속성이다.
상태 또는 동작을 정의한다.
<ul class="tab">
<li class="tab__item tab__item--active">1</li>
<li class="tab__item">2</li>
<li class="tab__item">3</li>
</ul>
모양, 상태, 동작을 나타낸다.
Modifier는 블록, 엘리먼트의 모양, 상태, 동작을 변경하는 것이기 때문에
블록, 엘리먼트에 추가하여 사용한다.

이것은 css의 기본적인 구조인데, h1 부분을 Selector(선택자)라고 한다.
<!-- css reset -->
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
*은 HTML 문서 내의 모든 요소를 선택한다.
한꺼번에 적용해야 하는 스타일할 때 적용할 때 주로 사용한다.
p {
font-size: 1.6rem;
}
지정한 태그에만 스타일을 적용시킬 때 사용한다.
rem과em은 상대적인 단위를 나타낸다. 둘의 차이는 참조 기준에 따라 결정된다.rem (Root em)
HTML의 루트 요소의 글꼴 크기를 기준으로 한다.
html { font-size: 16px; } p { font-size: 1rem; /* 16px */ } h1 { font-size: 2rem; /* 32px */ }em (Element em)
현재 요소의 글꼴 크기의 기준으로 한다.
html { font-size: 16px; } div { font-size: 1.5em; /* 24px */ } div p { font-size: 1em; /* 24px */ }ID 셀렉터
#nav { position: absolute; }선택한 id에 스타일을 적용시킬 때 사용한다.
.tab__item tab__item--active {
color: blueviolet;
}
선택한 class에 스타일을 적용시킬 때 사용한다.
태그#id 태그.class id.class
요소를 구체적으로 지정하여 스타일을 적용시킬 때 사용한다.
/* p태그이면서 selected라는 클래스를 가진 요소 */
p.selected {
color: blueviolet;
}
지정된 속성 값을 가지는 모든 요소를 선택하여 스타일을 적용시킬 때 사용한다.
a[href] {
color: blueviolet;
}
상위 요소의 하위에 속하는 모든 요소를 지정하여 스타일을 적용시킬 때 사용한다.
/* div 요소에 속해 있는 모든 p */
div p {
font-color: white;
상위 요소에 속해 있는 요소 중 바로 밑에 있는 요소를 지정하여 스타일을 적용시킬 때 사용한다.
div > p {
color: blueviolet;
}
셀렉터 A의 형제 요소 중 셀렉터 A 바로 뒤에 위치하는 요소를 선택하여 스타일을 적용시킬 때 사용한다.
사이에 다른 요소가 존재하면 선택되지 않는다.
<style>
p + ul {
color: blueviolet;
}
</style>
<p>Text</p>
<ul>
<li>Text 01</li>
</ul>
셀렉터 A의 형제 요소 중, 셀렉터 A 뒤에 위치하는 셀렉터 B 요소를 모두 선택하여 스타일을 적용시킬 때 사용한다.
<style>
p ~ ul {
color: blueviolet;
}
</style>
<div>
<!-- 적용되지 않는다 -->
<ul>
<li>00</li>
</ul>
<p>Text</p>
<!-- 적용된다 -->
<ul>
<li>01</li>
</ul>
<p>Text</p>
<ul>
<li>01</li>
</ul>
</div>
마우스가 들어왔을 때(hover)의 스타일을 적용시킬 때 사용한다.
div:hover {
color: blueviolet;
}
:first-child: 셀렉터에 해당하는 모든 요소 중 첫번째 자식인 요소를 선택
:last-child: 셀렉터에 해당하는 모든 요소 중 마지막 자식인 요소를 선택
:nth-child(n): 셀렉터에 해당하는 모든 요소 중 앞에서 n번째 자식인 요소를 선택
:nth-last-child(n): 셀렉터에 해당하는 모든 요소 중 뒤에서 n번째 자식인 요소를 선택
:nth-child(odd): 셀렉터에 해당하는 모든 요소 중 홀수번째 자식인 요소 선택
nth-child(even): 셀렉터에 해당하는 모든 요소 중 짝수번째 자식인 요소 선택
:not: 셀렉터에 해다되지 않는 모든 요소를 선택.
input:not([type=password]) {
color: blueviolet;
}
::first-letter: 콘텐츠의 첫글자를 선택
::first-line: 콘텐츠의 첫 줄을 선택. 블록 요소에만 적용할 수 있다
::after: 콘텐츠의 뒤에 위치하는 공간을 선택. 일반적으로 content 어트리뷰트와 함께 사용된다
::before: 콘텐츠의 앞에 위치하는 공간을 선택. 일반적으로 content 어트리뷰트와 함께 사용된다
::selection: 드래그한 콘텐츠를 선택. iOS Safari 등 일부 브라우저에서 동작하지 않을 수도 있다.
:root {
--primary-color: #6200EE;
}
div {
color: var(--primary-color);
}
Syntactically Awesome Style Sheets의 약자로 CSS 전처리기이다.
CSS를 더 효율적으로 작성할 수 있게 도와준다.
Sass는 Scss와 Sass 두 가지 구문을 지원하며
Scss는 css와 비슷한 문법을 사용하지만 Sass는 중괄호와 세미콜론을 사용하지 않아도 된다.
SASS
$primary-color: #0f0
$secondary-color: #f00
=button-styles
background-color: $primary-color
color: #fff
padding: 10px 20px
button
+button-styles
a
color: $secondary-color
SCSS
$primary-color: #0f0;
$secondary-color: #f00;
@mixin button-styles {
background-color: $primary-color;
color: #fff;
padding: 10px 20px;
}
button {
@include button-styles;
}
a {
color: $secondary-color;
}
Sass는 css 전처리기이기 때문에 브라우저가 이해하지 못하므로
Sass로 작성된 파일을 css로 컴파일 할 필요가 있다.
그래서 이를 변환해 주는 라이브러리를 설치해야 된다.
npm install node-sass
node-sass는 더 이상 지원하지 않는다고 해서 아래의 라이브러리를 설치했다.
(vite 환경)
npm install -D sass-embedded
$ 기호를 사용해서 변수를 정의할 수 있다.
/* 변수 정의 */
$primary-color: #6200EE
$secondary-color: #9654F4
/* 변수 사용 */
h1 {
color: $primary-color;
}
선택자를 중첩하여 작성할 수 있다. 이를 통해 가독성을 높일 수 있다.
nav {
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
display: inline-block;
margin-right: 10px;
a {
color: #fff;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
@mixin을 사용해서 스타일 블록을 정의하고
@include를 사용해서 해당 스타일 블록을 적용할 수 있다.
스타일을 재사용하고 더욱 간결하게 작성할 수 있다.
/* 믹스인 정의 */
@mixin text-style($font-size, $line-hiehgt, $text-color) {
font-size: $font-size;
line-hiehgt: $line-height;
color: $text-color;
}
/* 믹스인 사용 */
h1 {
@include text-style(24px, 1.2, #eee);
}
p {
@include text-style(16px, 1.5, #666);
}
@extend를 사용해서 스타일을 상속할 수 있다.
중복되는 스타일을 줄일 수 있다.
/* 스타일 정의 */
.btn {
display: iline-block;
padding: 8px 16px;
font-size: 14px;
color: #fff;
text-align: center;
background-color: #EFE6FD;
border-radius: 4px;
}
/* 스타일 상속 */
.btn-primary {
@extend .btn;
background-color: #5cb85c;
}
sass는 @mixin이나 변수를 다른 파일에 분리시켜서 가져올 수 있는데
예전에는 @import로 가져와서 썼지만 더 이상 사용할 수 없고
대신에 @use를 사용해서 가져와서 사용할 수 있다.
@use '가져올 파일명'로 가져온 다음
사용할 때는 가져온 파일명.변수명으로 사용하면 된다.
@use 'utils'
.SassComponent {
display: flex;
.box {
background: red;
cursor: pointer;
transition: all 0.3s ease-in;
&.red {
background: utils.$red;
@include utils.square(1)
}
&.orange {
background: utils.$orange;
@include utils.square(2)
}
&.yellow {
background: utils.$yellow;
@include utils.square(3)
}
&.green {
background: utils.$green;
@include utils.square(4)
}
&.blue {
background: utils.$blue;
@include utils.square(5)
}
&.indigo {
background: utils.$indigo;
@include utils.square(6)
}
&.violet {
background: utils.$violet;
@include utils.square(7)
}
&:hover {
background: black;
}
}
}
scss 라이브러리를 설치하고 가져와서 사용하고 싶다면 아래와 같이 작성하면 된다.
/* as를 사용해서 대신할 이름을 지정할 수 있다 */
@use 'include-media' as im;
@use 'utils';
.SassComponent {
display: flex;
/* 가로 크기가 768px 이하면 적용된다 */
@include im.media('<768px') {
background: blueviolet;
}
/* ... */
}
프로젝트를 진행하다보면 여러 파일을 import 하는 경우가 많다.
그럴 때마다 상대 경로로 가져오게 될 경우
../../../../style.css와 같이 알아보기 힘들고 길어지는 경우가 있다.
그럴 때 path alias를 사용해서 가독성을 높여줄 수 있다.
지금 vite를 사용하고 있기 때문에 vite.config.ts에서
이것을 설정해 줄 수 있다.
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: [
{ find: "@/*", replacement: path.resolve(__dirname, "src") },
{
find: "@styles",
replacement: path.resolve(__dirname, "src/styles"),
}
]
}
})
// tsconfig.app.json
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@styles/*": ["src/styles/*"],
},
// ...
위와 같이 파일을 변경한 다음, 실제 사용하는 곳에서는
import '@styles/SassComponent.scss'
const SassComponent = () => {
return (
<div className="SassComponent">
<div className="box red" />
<div className="box orange" />
<div className="box yellow" />
<div className="box green" />
<div className="box blue" />
<div className="box indigo" />
<div className="box violet" />
</div>
);
}
이런 식으로 사용해서 깔끔하게 작성할 수 있다.
CSS를 불러와서 사용할 때, 클래스 이름을 고유한 값으로 자동으로 만들어서
클래스 이름이 중첩되는 현상을 방지해주는 기술이다.
파일명을 이름.module.css로 작성해 주면 된다.
/* 클래스 이름을 자동으로 바꿔주므로 겹쳐도 된다 */
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
/* 전역으로 사용하고 싶다면 :global을 추가하자 */
:global .something {
font-weight: 800;
color: aqua;
}
import styles from '@styles/CSSModule.module.css';
const CSSModule = () => {
return (
<div className={styles.wrapper}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
}
클래스 이름이 _클래스이름_랜덤문자열으로 자동으로 생성됐다.
CSS in JS 방식으로 CSS를 작성할 수 있게 해주는 라이브러리이다.
가장 대표적인 것이 styled-component이다.
CSS in JS
자바스크립트 파일 안에서 css를 작성하는 방식이다.
컴포넌트 단위로 스타일링을 할 수 있으며
자바스크립트의 변수와 로직을 사용해서 스타일을 동적으로 적용시킬 수 있다.
먼저 라이브러리를 설치해야 된다.
npm install styled-component -D @types/styled-component
import styled, { css } from 'styled-components';
const Box = styled.div`
/* props로 넣어 준 값을 직접 전달해 줄 수 있다 */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
`;
const Button = styled.button<{inverted?: boolean}>`
background: white;
color: black;
border-radius: 4px;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 1rem;
font-weight: 600;
&:hover {
background: rgba(255, 255, 255, 0.9);
}
${props =>
props.inverted && css`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
& + button {
margin-left: 1rem;
}
`;
const StyledComponent = () => {
return (
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
);
}
VS code의 익스텐션에서 vscode-styled-components를 설치하면
글자에 색상과 자동 완성이 돼서 작성이 수월해진다.
자바스크립트의 문법인 템플릿 리터럴를 사용해서 작동한다.
자바스크립트에서 문자열을 입력하는 방식 중 하나.
표현식, 문자열 삽입, 여러 줄 문자열, 물자열 형식화, 문자열 태깅 등의 기능을 제공한다.
`string text`
`string text line1
string text line2
`
let expression;
`string text ${expression} string text`
function tag() { };
tag`string text ${expression} string text`
ES6 이전에는 아래와 같은 방법으로 문자열을 삽입했었다.
let a = 10;
let b = 7;
let c = "자바스크립트";
let text = "입력한 값은 " + (a + b) + "이며, " + c + "를 사용 중입니다.";
템플릿 리터럴에서는 아래와 같이 $와 {}를 사용해서 삽입할 수 있다.
let a = 10;
let b = 7;
let c = "자바스크립트";
let text = `입력한 값은 ${a+b}이며, ${c}를 사용 중입니다.`;
템플릿 리터럴의 발전된 형태이다.
태그를 사용하여 템플랫 리터럴을 함수로 파싱할 수 있다.
let person = 'Lee'
let age = 28;
let tag = function(strings, personExp, ageExp) {
console.log(strings); // 첫 번째에는 배열이 들어온다
console.log(personExp); // 두 번째에는 person 값이 들어온다
console.log(ageExp); // 세 번째에는 age 값이 들어온다
}
let output = tag`that ${person} is a ${age}`;
function fn(strings, brand, items) {
if(undefined === items) {
return brand + "의 라면은 재고가 없습니다!";
} else {
return strings[0] + brand + strings[1] + items;
}
}
console.log(fn`구매가능한 ${ramenList[0].brand}의 라면 : ${ramenList[0].items}`);
//구매가능한 농심의 라면 : 신라면,짜파게티,참치마요,둥지냉면
console.log(fn`구매가능한 ${ramenList[1].brand}의 라면 : ${ramenList[1].items}`);
//구매가능한 삼양의 라면 : 삼양라면,불닭볶음면
console.log(fn`구매가능한 ${ramenList[2].brand}의 라면 : ${ramenList[2].items}`);
//오뚜기의 라면은 재고가 없습니다!
styled.태그명을 사용해서 만들면 된다.
const MyComponent = styled.div`
font-size: 1.6rem;
`;
// ...
<MyComponent>
나의 컴포넌트
</MyComponent>
const MyComponent2 = styled('div')`
font-size: 3rem;
color: blueviolet;
`;
// ...
<MyComponent2>
나의 컴포넌트2
</MyComponent2>
const Box = styled.div`
/* props로 넣어 준 값을 직접 전달해 줄 수 있다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
`;
//...
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
color라는 props를 줘서 배경 색상을 지정할 수 있다.
만약 color를 주지 않았다면 기본 색상으로 파란색이 나오도록 했다.
기존에는 클래스에 조건부로 스타일링을 했었다면
styled-component에서는 props를 받아서 조건부로 스타일링 해줄 수 있다.
const Button = styled.button<{inverted?: boolean}>`
/* ... */
${props =>
props.inverted && css`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
`
// ...
<Button inverted={true}>테두리만</Button>
스타일 코드 여러 줄을 props에 따라 넣어야 할 때는
styled-component에서 제공하는 css를 사용해야한다.
css를 사용하지 않아도 작동은 하지만
문자열로 취급되어서 익스텐션의 신택스 하이라이팅이 되지 않고
태그 템플릿이 아니기 때문에 해당 부분에서는 props 값을 사용할 수 없게된다.
조건부 스타일링 부분에 props를 참조하지 않는다면 사용하지 않아도 상관없지만,
참조할 경우에는 반드시 css를 활용해서 태그 템플릿 기능을 사용하는 것이 좋다.
반응형 디자인도 가능한데, 일반 CSS에서 사용할 때처럼 media 쿼리를 사용하면 된다.
/* ... */
/* 768px 미만이되면 화면을 꽉 채운다 */
@media (max-width: 1024px) {
width: 768px;
}
@media (max-width: 768px) {
width: 100%;
}
/* ... */
반응형 CSS를 함수화해서 여러 곳에서 재사용하는 방법도 있다.
const sizes = {
desktop: 1024,
tablet: 768,
}
type Sizes = typeof sizes;
type Media = {
[Key in keyof Sizes]: (...args: Parameters<typeof css>) => CSSProp;
};
const media: Media = Object.keys(sizes).reduce((acc, label) => {
const key = label as keyof Sizes;
acc[key] = (...args) => css`
@media (max-width: ${sizes[key] / 16}em) {
${css(...args)};
}
`;
return acc;
}, {} as Media);
// ...
const Button = styled.button<{inverted?: boolean}>`
/* ... */
${media.desktop`width: 768px;`}
${media.tablet`width: 100%;`}
/* ... */
`
위 방법 외에도 Bootstrap Tailwind CSS 등이 있다.