menulogo

스타일링 라이브러리 비교하기

@corinthioniaAugust 7, 2024

저는 스타일 라이브러리 유목민입니다 🐪🐎
그동안 프로젝트를 진행하며 다양한 기술들을 사용해 봤는데요. 매번 프로젝트를 진행할 때마다 어떤 기술을 선택할지에 대해 상당히 오랜 시간을 들여 고민하게 되는 것 같습니다. 그래서 여러 라이브러리들의 장단점을 비교해 보고 추후 기술스택 선택 시에 도움이 되움이 되고자 이렇게 정리해 보았습니다 🙂


CSS (Cascading Style Sheets)

웹의 스타일링을 위한 기본 표준으로, 모든 웹 브라우저에서 지원하는 스타일 시트 언어입니다. 선택자를 이용하여 HTML 문서에 있는 요소들에 선택적으로 스타일을 적용할 수 있습니다.

p {
  color: red;
  width: 500px;
  border: 1px solid black;
}

장점

표준 CSS를 사용하여 개발한다면 전역 스타일링을 편리하게 적용할 수 있습니다.
예를 들어 모든 <p> 태그에 특정 스타일을 적용하려 한다면, 아래와 같이 작성하고 해당 css 파일을 import 하는 html의 모든 <p> 태그에는 아래와 같은 속성을 지정할 수 있습니다.

p {
  color: blue;
  font-size: 16px;
  margin: 20px;
}

또한, HTML과 CSS 파일이 분리되어 있다 보니 관심사 분리의 측면에서 유용하고, 또 유지보수가 용이합니다.

단점

그럼 CSS 사용 시 발생할 수 있는 문제점은 무엇이 있을까요? 아이러니 하게도 위에 언급한 장점이 상황에 따라서는 단점이 될 수도 있습니다. 먼저, 스타일이 전역적으로 적용되므로 스타일 충돌이 발생할 수 있습니다.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CSS Example</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <div class="navbar">
      <p class="text">Hello World!</p>
    </div>
    <div class="content">
      <p class="text">Hello CSS!</p>
    </div>
  </body>
</html>
p {
  margin: 20px;
  color: blue;
  font-size: 16px;
}

.navbar .text {
  padding: 10px;
  color: white;
  background-color: black;
}

위와 같이 html 파일과 css 파일을 만들었다고 가정해 봅시다. html 파일 내에 존재하는 모든 <p>에 대해 color: blue를 적용했습니다. 하지만 이를 실제로 확인해 보면, 특정 스타일이 우선 순위를 가져 color: white로 덮어씌워진 것을 확인할 수 있습니다.

이와 같은 상황을 의도하여 개발할 수도 있지만, 프로젝트가 복잡해질수록 관리가 어려워질 수 있다는 문제가 존재합니다.

또한 동적 스타일을 적용하기 어렵다는 점도 단점 중 하나입니다. 예를들어, <button>을 클릭했을 때와 클릭하지 않았을 때의 스타일을 다르게 지정하고 싶다면 별도의 클래스를 생성하고 이를 동적으로 부여해야 합니다. 이에 대해서는 CSS-in-JS를 소개할 때 자세히 말씀드리겠습니다.

CSS Preprocessor

CSS 전처리기는 앞서 말한 표준 CSS에 추가적인 기능을 제공하여 코드 작성과 유지보수를 더 쉽게 하고, 스타일시트의 재사용성을 높이는 도구입니다. 따라서 CSS 문법과 굉장히 유사하지만, 변수, 네스팅(중첩), 믹스인(mixin)과 같은 보다 확장된 기능을 사용할 수 있습니다.

가장 많이 사용되는 CSS 전처리기에는 Sass(SCSS), LESS, Stylus가 있습니다. 이제부터는 Sass(SCSS)를 예시로 설명하겠습니다.

Sass & SCSS

이 둘은 CSS 구문과 완전히 호환되도록 새로운 구문을 도입한 CSS의 Superset입니다. SCSS는 CSS와 유사한 구문을 사용하고, Sass는 조금 더 간결한 구문을 사용합니다.
먼저, 아래는 Sass로 작성한 코드입니다. 기존 CSS를 사용했을 때와 다르게 중괄호({})와 세미콜론(;)을 사용하지 않는 것을 확인할 수 있습니다.

/* Sass */
body
  font: 100% $font-stack
  color: $primary-color

반면 SCSS를 사용할 때에는 중괄호({})와 세미콜론(;)을 명시합니다.

/* SCSS */
body {
  font: 100% $font-stack;
  color: $primary-color;
}

이제부터는 SCSS를 기준으로 변수, 중첩, 믹스인, 연산을 사용하는 방법에 대해 간단하게 살펴보겠습니다.

/* 변수 사용 */
$primary-color: #333;

body {
  color: $primary-color;
}
/* 선택자 중첩 (Nesting) */
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }
}
/* 믹스인 (Mixin) */
@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
  -moz-border-radius: $radius;
  border-radius: $radius;
}

.box {
  @include border-radius(10px);
}

/* 연산 */
.container {
  width: 100% / 3;
}

정말 간단한 예시로만 설명 드렸는데, 자세한 사용법은 이 글 참고하시면 좋을 것 같습니다!

장점

우선 변수, 믹스인, 함수 등의 추가 기능을 제공하기 때문에, 유지보수가 쉽고 코드의 재사용 측면에서 활용하기도 좋습니다. 또한 연산, 조건문, 반복문 등 다양한 프로그래밍 기능을 제공하여 개발자의 입장에서 보다 편리하게 사용할 수 있습니다.

단점

CSS 전처리기는 표준 CSS에 몇 가지 기능이 추가된 것이라고 설명했습니다. 웹에서는 표준 CSS만 동작하기 때문에, 전처리기를 표준 CSS로 컴파일 하는 과정이 필요합니다. 실제 프로젝트를 진행할 때 유의미한 성능 저하가 있는지는 잘 모르겠지만, 표준 CSS와 비교했을 때의 상대적인 단점인 것 같습니다.

CSS-in-JS

CSS-in-JS는 말 그대로 JavaScript 안에 CSS가 들어 있다는 뜻입니다. 다시 말해 JavaScript 코드로 스타일을 지정할 수 있어 별도의 CSS 파일이 필요하지 않습니다.

styled-components & emotion

CSS-in-JS의 대표적인 라이브러리로는 styled-componentsemotion이 있습니다. 이 두 라이브러리 모두 스타일을 컴포넌트 수준에서 관리할 수 있게 해주며, 스타일이 적용된 컴포넌트를 생성합니다. 두 라이브러리의 사용법이 굉장히 유사하여 앞으로는 styled-components를 기준으로 설명하겠습니다.

아래는 styled-components를 사용한 React 코드입니다. 자바스크립트 파일 내부에서 백틱(`)으로 스타일 프로퍼티를 지정하고, 이를 컴포넌트로 할당하여 사용합니다.

// App.js

import styled from 'styled-components';

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

function App() {
  return (
    <div>
      <Button>버튼 클릭</Button>
    </div>
  );
}

export default App;

JavaScript 파일 내부에서 스타일을 지정하는 것이다 보니, JavaScript 변수를 이용하여 스타일을 동적으로 편리하게 지정할 수 있습니다. 만약 버튼을 클릭했을 때 배경색상을 빨간색으로 변경하고 싶다면 styled-components로 작성한 컴포넌트에서 props를 이용하여 JavaScript 변수를 사용할 수 있는 것입니다.

const Home = () => {
  const [isClicked, setIsClicked] = useState(false);
  return (
    <Button isClicked={isClicked} onClick={() => setIsClicked(true)}>
      Hello World!
    </Button>
  );
};

const Button = styled.button`
  width: 100%;
  height: 100%;
  background: ${props => (props.isClicked ? 'red' : 'powderblue')};
`;

장점

CSS-in-JS 라이브러리의 큰 장점은 JavaScript 변수를 이용하여 동적인 스타일이 가능하고, 또 매우 편리하게 사용할 수 있다는 점이라고 생각합니다. 또한 스타일을 정의하여 개별적인 컴포넌트로 만들다 보니, 표준 CSS의 단점이었던 전역 스타일 충돌 문제도 발생하지 않습니다.

단점

저는 그동안 CSS-in-JS 라이브러리를 굉장히 유용하게 사용해 왔는데요. 최근에 CSS-in-JS 라이브러리를 사용할 때의 단점에 대한 글을 꽤 많이 접했습니다.


먼저 단점을 살펴보기 전에 CSS-in-JS 라이브러리가 어떻게 동작하는지에 대한 이해가 필요합니다.

  1. JavaScript 파싱 및 실행 컴포넌트가 렌더링될 때 JavaScript 코드가 파싱되고 실행됩니다. 이 과정에서 CSS-in-JS 라이브러리는 스타일 객체를 생성합니다.
  2. 스타일 생성 및 DOM에 삽입 생성된 스타일 객체는 <style> 태그 형태로 DOM에 삽입됩니다. 이 과정은 브라우저의 CSSOM(CSS Object Model)과 DOM을 조작하는 작업을 포함합니다.
  3. 스타일 적용 브라우저는 삽입된 스타일을 해석하고 적용하여 화면에 렌더링합니다.

이 과정에서의 핵심은 JavaScript 코드로 스타일을 작성하다 보니 이를 파싱하고 CSS 구문으로 컴파일 하는 과정이 필요하고, 런타임에 스타일이 적용된다는 것입니다.

이렇다 보니 별도의 컴파일 시간이 필요하여 성능상 문제가 발생할 수 있습니다. 또한 런타임에 스타일이 적용되다 보니, 페이지를 서버에서 구성하는 SSR, SSG, 서버 컴포넌트에서 사용이 어렵습니다. (별도의 추가적인 설정이 필요합니다.) 실제로 관련 내용이 Next.js 공식문서에도 명시되어 있습니다.

Warning: CSS-in-JS libraries which require runtime JavaScript are not currently supported in Server Components. Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React, including concurrent rendering.

추가적인 문제점이 궁금하신 분들은 (번역) 우리가 CSS-in-JS와 헤어지는 이유 한번 참고해 보세요!


쓰다 보니 단점이 굉장히 길어졌는데, 저는 CSS-in-JS 좋아합니다... 개인적으로 동적 스타일링이 가능하다는 점이 정말 무시 못할 편리함이라 생각합니다 ㅎㅎ 실제로 아직까지도 토스, 당근 등의 기업에서도 emotion을 많이 사용하는 것으로 알고 있습니다.

CSS Modules

CSS Modules는 스타일 시트를 모듈화하여 각 클래스와 스타일을 로컬 범위로 스코핑합니다. 표준 CSS를 사용하면 전역 스타일 적용으로 인해 스타일 충돌이 발생할 수 있다는 우려가 있었는데, CSS Modules는 전역 네임스페이스 문제를 해결하여 스타일이 다른 컴포넌트와 충돌하지 않도록 도와줍니다.

아래와 같이 Button.module.css.button 클래스는 .button__hash123와 같이 해싱된 고유 이름으로 컴파일되어 전역 클래스 이름 충돌을 방지합니다.

/* Button.module.css */
.button {
  background-color: #3498db;
  color: white;
}

.button:hover {
  background-color: #2980b9;
}

지금 보고 계시는 이 블로그도 SCSS Modules로 개발되어 아래와 같이 해싱된 클래스명을 확인해 보실 수 있습니다!

장점

CSS Modules는 기존 CSS 문법을 그대로 사용할 수 있어 편리합니다. 또한, 클래스 네임이 자동으로 고유화되어 로컬 스코프를 가지므로 스타일 충돌이 발생하지 않습니다. 또한 런타임이 아닌 컴파일 타임에 처리되어 성능에 유리합니다.

단점

CSS Modules도 선택자를 이용하여 스타일링 하기 때문에 기본적으로 정적 스타일링을 제공하며, 동적 스타일링에는 추가적인 설정이 필요합니다.

CSS-in-JS vs CSS Modules 비교

  • CSS-in-JS 런타임에 스타일을 동적으로 생성하는 경우, JavaScript 코드가 실행되면서 스타일이 생성되고, DOM에 삽입되기 때문에 페이지 로드가 지연될 수 있습니다.
  • CSS Modules 컴파일 타임에 CSS 클래스 이름이 해싱되어 스타일이 최적화된 형태로 포함됩니다. 페이지 로드 시 브라우저는 최적화된 CSS 파일을 즉시 사용하여 스타일을 적용할 수 있습니다.

Tailwind CSS

Tailwind CSS는 유틸리티-퍼스트 CSS 프레임워크로, 웹 개발자들이 빠르게 사용자 인터페이스를 만들 수 있도록 도와줍니다. 유틸리티-퍼스트 프레임워크란, 미리 정의된 유틸리티 타입을 사용하는 프레임워크를 뜻합니다.
아래 예시 코드에서 보듯이 text-base라는 클래스에 관한 스타일이 미리 정의되어 있어, 단순히 클래스 이름을 부여하는 것만으로도 곧바로 스타일 적용이 가능합니다.

<blockquote class="text-base md:text-md 3xl:text-lg">Hello world! Hello world! Hello world! Hello world! Hello world!</blockquote>

장점

미리 정의된 유틸리티 클래스를 사용하여 빠르게 스타일을 적용할 수 있습니다. 특히 디자이너 없이 프로젝트를 진행하는 경우 타이포그라피, 색상 등에 대한 정의가 잘되어 있어 쉽고 빠르게 개발을 진행할 수 있습니다. (공식문서도 참 잘되어 있답니다) 또한 유틸리티 타입을 미리 정의하다 보니 디자인 시스템 적용에 유리합니다.

제가 느꼈던 장점으로는 더 이상 클래스명 또는 컴포넌트명 등을 고민하지 않아도 된다는 것 🥹 그리고 CSS 파일과 HTML 코드 사이의 컨텍스트 전환이 훨씬 적다는 점이었습니다.

단점

프로젝트에 Tailwind를 설정하고 학습하는 과정이 다소 번거로울 수 있습니다. 저도 처음 사용했을 때에는 공식문서를 왔다갔다 하며 유틸리티 클래스를 찾아보느라 번거로웠던 기억이 있습니다 😅
또한 HTML에 많은 클래스를 추가해야 하므로 가독성이 떨어질 수 있다는 단점도 존재합니다. 아래는 공식문서에 있던 코드인데, 코드를 보면 무슨 말인지 바로 와닿으실 것 같습니다.

<form>
  <label class="block">
    <span class="block text-sm font-medium text-slate-700">Username</span>
    <!-- Using form state modifiers, the classes can be identical for every input -->
    <input
      type="text"
      value="tbone"
      disabled
      class="mt-1 block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm placeholder-slate-400
      focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500
      disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none
      invalid:border-pink-500 invalid:text-pink-600
      focus:invalid:border-pink-500 focus:invalid:ring-pink-500
    "
    />
  </label>
  <!-- ... -->
</form>

Reference

← 이전 글Next.js 프로젝트를 정적 페이지로 배포하기 (Github pages)
다음 글 →axios 커스텀 에러 핸들링