Please enable JavaScript to view the comments powered by Disqus.

Tailwind CSS의 설치와 활용

Utility-first CSS 프레임워크

이 글은 Tailwind CSS v2를 기준으로 작성되었습니다.

Tailwind CSS의 특징

Tailwind CSS는 유틸리티 퍼스트(Utility-first)를 지향하는 CSS 프레임워크다. CSS를 작성하는 방법론 중 하나인 BEM은 마크업을 컴포넌트 단위로 구분하고 상태에 따라 스타일을 변경하는 식으로 재활용할 수 있도록 한다.

<button className="button button--success">
	<span className="button-label">Submit</span>
</button>
.button {
	display: inline-block;
	border-radius: 3px;
}
.button-label {
	font-weight: bold;
}
.button--success {
	color: #FFF;
	border-color: #4A993E;
}
.button--danger {
	color: #900;
}

하지만 유틸리티를 우선하는 CSS 프레임워크는 클래스와 스타일을 새로 작성하지 않는다. 레이아웃, 포지션, 스페이스, 컬러, 폰트 등 스타일링에 필요한 대부분의 속성이 수많은 클래스로 사전 정의되어 있고, 사용자는 그 클래스들을 조합해서 사용한다.

.inline-block {
	display: inline-block;
}
.rounded {
	border-radius: 0.25rem;
}
.p-4 {
	padding: 1rem;
}
<button className='inline-block rounded p-4'>
	Submit
</button>

CSS를 유틸리티 클래스 기반으로 사용할 때의 장점은 다음과 같다.

  • 컴포넌트를 작성할 때 CSS 클래스 이름을 고민할 필요가 없다.
  • 컴포넌트의 수가 늘어나도 CSS 파일의 사이즈는 크게 늘어나지 않는다.
  • 복잡한 구조의 클래스+태그 셀렉터를 사용하지 않기 때문에 버그 발생 가능성이 낮고 수정이 쉽다.

클래스를 여러 개 붙이는 것에서 인라인 스타일과 다를게 없다거나, className 속성이 너무 길어지지 않느냐고 말할 수도 있다. 하지만 다음과 같은 장점도 있다.

  • 사전 정의된 디자인 시스템을 기반으로 클래스를 생성하기 때문에 컬러, 사이즈, 간격에서 디자인 일관성을 유지할 수 있다.
  • 반응형 유틸리티 클래스(ex. md:p-4)를 사용해서 미디어 쿼리를 쉽게 적용할 수 있다.
  • hover, focus 등 여러가지 상태를 위한 스타일(ex. hover:font-bold)을 쉽게 추가할 수 있다. 인라인 스타일에서는 hover등의 상태 선택자를 사용할 수 없다.

그리고 Tailwind CSS는 기본 제공하는 클래스 외에도 사용자가 직접 테마를 확장하고, 유틸리티 클래스를 추가할 수 있다. 그래고 반복되는 마크업과 스타일은 새로운 클래스를 추가하는 것보다 컴포넌트 모듈로 분리하는 것을 권장한다.

클래스 자동완성을 위한 Tailwind CSS Intellisense

사용 가능한 클래스가 수백개가 넘기 때문에 처음 접하는 사람이나 조금 익숙해진 사람이나 그 많은 클래스를 모두 외운 상태로 사용하기는 어렵다. 그리고 className 속성이 길어지면 같은 속성을 설정하는 유틸리티 클래스가 중복되어(ex. display 속성을 설정하는 flex, block 클래스) 버그를 발생시킬 수도 있다. 그래서 Tailwind CSS는 Visual Studio Code의 확장 프로그램으로 Tailwind CSS IntelliSense를 제공하고 있다.

Autocomplete of Tailwind CSS Intellisense 컬러 유틸리티는 좌측에 색상까지 표시해 준다 👍

확장 프로그램을 사용하면 에디터 안에서 유틸리티 클래스 자동완성을 제공하며 충돌하는 클래스도 체크해 준다. 그리고 단순히 기본 제공하는 클래스의 자동 완성만 지원하는 것이 아니라 사용자가 직접 설정한 클래스도 확장 프로그램이 인식하여 자동완성에 제공해 준다.

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      spacing: {
        xs: defaultTheme.spacing[2],
        sm: defaultTheme.spacing[3],
			}    
    }
	}
}

위와 같이 테마 확장으로 spacing 을 추가하면 p-4 같은 기본 클래스 뿐만 아니라 p-xs, p-sm 처럼 간격에 관련된 모든 유틸리티 클래스에 suffix로 형태로 추가된다.

그리고 클래스네임이 대부분 어떤 속성을 설정하는지 유추하기 쉽게 만들어져 있어서 레퍼런스를 찾아보면서 파악하면 큰 어려움은 없다. 그리고 이런 프레임워크는 사용하면 사용할수록 작업 속도가 빨라진다는 장점이 있다.

Tailwind CSS 설치

Installation - Tailwind CSS

Tailwind CSS는 PostCSS를 기반으로 한다. 유틸리티 클래스를 생성하기 위해서 필요한 사항은 다음과 같다.

  • npm 모듈

    • TailwindCSS
    • PostCSS
    • Autoprefixer
    • postcss-cli
  • 설정 파일

    • tailwind.config.js
    • tailwind.css (postcss로 처리할 파일. 이름은 상관없음)
    • postcss.config.js

여기서는 postcss-cli를 사용해서 스타일시트를 직접 생성한 후 앱의 entry 모듈에서 import하는 방식을 사용할 것이다. 만약 webpack을 사용한다면 style-loader, css-loader와 함께 postcss-loader를 사용하면 된다.

PostCSS 설정 파일

// postcss.config.js
module.exports = {
  plugins: [require('tailwindcss'), require('autoprefixer')],
};

Babel의 .babelrc 파일처럼 프로젝트 루트에 PostCSS 설정 파일이 있으면 postcss-cli, webpack postcss-loader에서 모두 활용이 가능하다.

Tailwind 설정 파일

// tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, 
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

프로젝트 루트에 추가해 준다. 더 자세한 내용은 Configuration 문서에서 확인이 가능하다.

베이스 CSS 파일

// src/tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@tailwind 라는 키워드는 postcss.config.js 파일의 플러그인 설정에 require('tailwindcss')를 추가했기 때문에 사용 가능한 것이다. 위의 코드를 추가하면 PostCSS가 tailwindcss 플러그인과 tailwind.config.js 파일을 참조하여 유틸리티 클래스들을 만들어준다.

CSS 파일 변환 npm 스크립트 추가

// package.json
{
	"scripts": {
    "tw:watch": "TAILWIND_MODE=watch NODE_ENV=development postcss ./src/tailwind.css -o ./src/tailwind_out.css -w",
		"tw:build": "NODE_ENV=production postcss ./src/tailwind.css -o ./src/tailwind_out.css/"
	}
}

tw:watch 스크립트를 실행하면 postcss-cli가 tailwind.css 파일을 읽어서 tailwind_out.css 파일을 생성한다. 그리고 후술할 JIT 모드를 사용하기 위해 소스 코드에 변경이 있을 때 tailwind_out.css 파일이 업데이트되도록 watch 옵션을 함께 설정했다.

tw:build 스크립트는 앱 배포를 위해 빌드를 할 때 사용할 것이다. production 모드에서 CSS 속성에 autoprefixer 모듈을 사용해서 벤더 프리픽스(vendor prefix)를 붙여주기 위해서는 browserslist 설정 파일이 필요하다. 프로젝트 루트에 .browserslistrc 파일을 추가해도 되고, package.json에 추가해도 된다.

// package.json
{
	"browserslist": {
    "production": [">0.2%", "not dead", "not op_mini all"],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
}

개발 모드에서는 주요 브라우저의 최신 버전에만 맞추지만, production 모드에서는 거의 모든 브라우저를 지원하게 했으므로 필요한 대부분의 벤더 프리픽스가 추가된다.

앱에서 생성된 스타일시트 가져오기

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './tailwind_out.css'; // compiled tailwind stylesheet

const Index = () => {
  return (
    <div>index</div>
  );
};

export default Index;

React 앱의 루트 컴포넌트에서 스타일시트를 불러오도록 했다. 사용하는 번들러에 따라 CSS 파일을 import하기 위해 추가적인 설정이 필요할 수 있다.

개발 서버 실행 스크립트

postcss-cli로 watch 모드를 사용한다면 개발 서버도 동시에 실행해야 하므로 concurrently를 사용한다.

// package.json
{
	"scripts": {
		"dev": "개발 서버 실행", 
    "start": "concurrently \"yarn:tw:watch\" \"yarn:dev\"",
	}
}

Storybook 설정

Storybook에서 Tailwind CSS 로 생성한 스타일시트를 사용하려면 위에서 설정한 postcss-cli를 사용해도 된다. 하지만 Storybook 서버는 Webpack 기반이므로 실행되므로 @storybook/addon-postcss 모듈을 사용하는 편이 더 간단하다.

// .storybook/main.js
module.exports = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-postcss', 
  ],
}
// .storybook/preview.js
import '../src/tailwind.css';

Tailwind CSS 활용

JIT(Just-in-Time Mode) 모드

Tailwind CSS 2.1 이상에서 지원하는 기능으로 스타일시트를 개발 서버가 실행되기 전 생성해두는 것이 아니라 소스 파일 변경을 탐지하면서 HTML, 컴포넌트에서 사용하는 클래스만 스타일시트에 추가하도록 한다. 이 기능을 필요한 클래스만 추가되므로 스타일시트 사이즈를 크게 줄일 수 있다.

비슷한 기능으로 사용하지 않는 유틸리티를 스타일시트에서 제거하는 purge 옵션을 제공하고 있었는데 이것은 production 모드에서만 사용 가능하다. JIT 모드는 이 기능을 개발 모드에서도 사용 가능하도록 확장한 것이라 할 수 있다.

테마 확장

앞서 잠깐 살펴봤지만 Material-UI에서도 가능한 것처럼 기본 테마를 확장할 수 있다. 기존 값을 덮어쓸 수도 있고, 새로운 속성을 추가할 수도 있다.

기본 테마 객체가 어떻게 구성되었는지는 아래 파일에서 확인 가능하다.

https://unpkg.com/browse/tailwindcss@2.1.2/stubs/defaultConfig.stub.js

Variants

hover:, focus: 등의 프리픽스는 모든 CSS 속성을 지원하지 않으며 backgroundColor, opacity 등의 일부 속성에만 사용할 수 있도록 설정되어 있다. 모든 속성을 지원하면 유틸리티 클래스 수가 너무 늘어날 것이고 자연히 스타일시트 파일의 덩치도 커질 것이기 때문이다. 기본 제공하는 속성 외의 다른 속성에 variants를 조합하려면 추가 설정이 필요하다.

// tailwind.config.js
module.exports = {
  variants: {
    fill: [],
    extend: {
      borderColor: ['focus-visible'],
      opacity: ['disabled'],
    }
  },
}

다크 모드

Tailwind CSS에서 다크 모드는 테마에서 설정하는 방식이 아니라 dark: 프리픽스가 붙은 클래스로 스타일을 직접 추가하는 방식이다. Material-UI는 ThemeProvider에 테마 객체를 전달할 수 있어서 컬러 모드에 따라 테마를 통째로 교체할 수 있는데, 그에 비하면 조금 불편한 방식이라 할 수 있다. Tailwind CSS에서 다크 모드를 사용하기 위해서는 컴포넌트 모듈화가 철저하게 이뤄져야 반복 작업을 줄일 수 있을 것이다.

<div class="bg-white dark:bg-gray-800">
  <h1 class="text-gray-900 dark:text-white">Dark mode is here!</h1>
  <p class="text-gray-600 dark:text-gray-300">
    Lorem ipsum...
  </p>
</div>

Tailwind CSS 2의 Postcss 7 호환 버전 설치

Tailwind CSS 2는 PostCSS 버전 8을 기본으로 사용한다. 하지만 PostCSS 8은 배포된지 얼마 되지 않았기 때문에 아직 반영하지 않은 라이브러리들이 많다. 특히 Webpack 4에서는 PostCSS 8을 사용할 수 없기에 Webpack 5를 설치해야 한다. 그래서 PostCSS 7로 Tailwind CSS 2 를 사용할 수 있는 방법을 제공하고 있다.

https://tailwindcss.com/docs/installation#post-css-7-compatibility-build

@storybook/react의 최신 릴리즈 버전(2021.06 기준 6.2)은 Webpack 4를 사용하기 때문에 위의 링크에 있는 방법을 적용할 필요가 있다.

Twin 라이브러리로 CSS-in-JS 스타일로 사용하기

styled-components나 emotion에 익숙한 사람이라면 선호할 만한 방법이다. css 함수를 사용하면 template literal 안에 직접 CSS를 작성하면서 Tailwind CSS의 클래스도 조합할 수 있다.

import tw, { css } from 'twin.macro'

const hoverStyles = css`
  &:hover {
    border-color: black;
    ${tw`text-black`}  }
`
const Input = ({ hasHover }) => (
  <input css={[tw`border`, hasHover && hoverStyles]} />
)

Tailwind CSS는 대부분의 스타일을 유틸리티 클래스로 추가할 수 있지만, :nth-child(n) 처럼 유틸리티 클래스로 만들기 어려운 선택자는 지원하지 않는다(:first-child, :last-child는 지원함). 이처럼 클래스로 추가하기 어려운 스타일링은 스타일시트를 직접 작성해야 하는데, 이 방법을 사용하면 Tailwind의 유틸리티 클래스도 그대로 활용할 수 있다.

Tailwind CSS 유저 커뮤니티

tailwindcomponents.com에서 Tailwind CSS를 기반으로 만든 컴포넌트를 공유하고 있다. 독립된 컴포넌트 뿐만 아니라 하나의 완성된 페이지 형태의 작업물도 공유되고 있으니 참고하면 좋을 것이다.


Tailwind CSS는 유틸리티 클래스만 제공하기 때문에 다른 CSS 프레임워크처럼 사전 정의된 스타일과 기능을 가진 컴포넌트를 바로 사용할 수는 없다. 하지만 필요한 기능만 가진 가벼운 컴포넌트를 직접 구현해서 사용하는 쪽을 선호한다면 좋은 선택이 될 것이다. 특히 테스트 케이스 작성도 그렇지만, 유틸리티 클래스를 활용하는 것도 사용할 수록 작성 속도가 빨라지는 것을 실제로 사용하면서 경험할 수 있었다.

관련 자료