Tailwind CSS v3.1: 미쳤다고? 좋아, 한번 미쳐보자!

Date

Tailwind CSS v3.0을 출시한 지 약 6개월이 지났고, 그동안 코드베이스에 많은 작은 개선 사항을 모아왔지만, 아직까지 _“좋아, 이제 출시할 때가 됐다”_고 말할 만한 _그-하나의-기능_이 없었습니다.

그런데 몇 주 전 어느 토요일 밤, Discord에서 Robin과 문서 내부의 클래스를 사용해 html 엘리먼트를 타겟팅하는 방법에 대해 이야기하던 중, 임의의 변형(arbitrary variants)을 지원하면 어떻게 될지 설명했습니다. 이 아이디어는 1년 넘게 고민해온 것이었죠.

Adam Wathan: 임의의 변형을 구현한다면, 문법은 정확히 '[html:has(&)]:bg-blue-500'처럼 되어야 한다고 생각해. 이렇게 하면 꽤 유연해질 거야. 실제 변형으로 할 수 있는 모든 것을 임의의 변형으로도 할 수 있으니까. '[&>*:not(:first-child)]:pl-4'처럼 말이지. Robin: 이건 내 머리를 깨뜨릴 것 같아 ㅋㅋ '[html:has(&)]:bg-blue-500'가 '&' 안에 리터럴로 사용될 거라니. 다른 변형과 조합하면... 🤯. Adam Wathan: 😅 확실히 머리를 녹일 거야. CSS는 이렇게 될 거야 ㅋㅋ 'html:has(\[html\:has\(\&\)\]\:bg-blue-500 { background: blue 500 }'. Robin: 맞아 ㅋㅋ. 좋아, 이제 시도해보고 싶어. 잠시만.

20분 후 Robin은 동작하는 개념 증명을 완성했고 (단 6줄의 코드로!), Jordan이 클래스 탐지 엔진에서 정규식 기적을 일으킨 지 약 1시간 후, 임의의 변형이 탄생했고, 출시할 만한 기능을 확보했습니다.

자, 이제 Tailwind CSS v3.1입니다! 모든 수정 사항과 개선 사항의 전체 목록은 릴리스 노트를 확인하세요. 주요 내용은 다음과 같습니다:

tailwindcss의 최신 버전을 npm에서 설치해 프로젝트를 업그레이드하세요:

npm install tailwindcss@latest

또는 Tailwind Play를 열어 브라우저에서 새로운 기능들을 직접 시험해보세요.


First-party TypeScript 타입

이제 Tailwind를 사용할 때 작업하는 모든 JS API에 대한 타입을 제공합니다. 특히 tailwind.config.js 파일에 대한 타입을 포함합니다. 이를 통해 다양한 유용한 IDE 지원을 받을 수 있으며, 문서를 참조하지 않고도 설정을 변경하기가 훨씬 쉬워집니다.

설정을 적용하려면, 설정 정의 위에 타입 주석을 추가하면 됩니다:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // ...
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

TypeScript를 좋아하는 분이라면 실제 타입 정의를 살펴보는 재미를 느낄 수 있을 겁니다. 이렇게 복잡한 객체를 지원하기 위해 많은 흥미로운 작업이 이루어지고 있습니다.

CLI에 내장된 CSS 임포트 지원

여러분이 CSS를 컴파일하기 위해 CLI 도구를 사용한다면, 이제 postcss-import가 기본적으로 내장되어 있어 추가 설정 없이도 커스텀 CSS를 여러 파일로 나눠 관리할 수 있습니다.

@import "tailwindcss/base";
@import "./select2-theme.css";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

만약 CLI 도구를 사용하지 않고 Tailwind를 PostCSS 플러그인으로 사용한다면, autoprefixer와 마찬가지로 postcss-import를 직접 설치하고 설정해야 합니다. 하지만 CLI 도구를 사용한다면 이제 별도의 설정 없이도 바로 사용할 수 있습니다.

이 기능은 특히 독립형 CLI를 사용하면서 노드 의존성을 전혀 설치하고 싶지 않은 경우에 매우 유용합니다.

theme 함수를 사용할 때 색상 불투명도 조절하기

많은 사람들이 잘 모르고 있지만, Tailwind는 CSS 파일에서 theme() 함수를 제공합니다. 이 함수를 사용하면 설정 파일에서 값을 가져와 변수처럼 재사용할 수 있습니다.

select2-theme.css
.select2-dropdown {
  border-radius: theme(borderRadius.lg);
  background-color: theme(colors.gray.100);
  color: theme(colors.gray.900);
}
/* ... */

하지만 이 방법으로 가져온 색상의 알파 채널을 조절할 수 없다는 한계가 있었습니다. 그래서 v3.1부터는 슬래시(/) 구문을 사용해 불투명도를 조절할 수 있도록 지원했습니다. 이는 모던 rgbhsl CSS 색상 함수와 유사한 방식입니다.

select2-theme.css
.select2-dropdown {
  border-radius: theme(borderRadius.lg);
  background-color: theme(colors.gray.100 / 50%);
  color: theme(colors.gray.900);
}
/* ... */

이 기능은 tailwind.config.js 파일의 theme 함수에서도 동작합니다.

tailwind.config.js
module.exports = {
  content: [
    // ...
  ],
  theme: {
    extend: {
      colors: ({ theme }) => ({
        primary: theme('colors.blue.500'),
        'primary-fade': theme('colors.blue.500 / 75%'),
      })
    },
  },
  plugins: [],
}

심지어 임의의 값에서도 이 기능을 사용할 수 있습니다. 이는 꽤 놀라운 일이며, 특히 특이한 커스텀 그라디언트 등을 만들 때 유용합니다.

<div class="bg-[image:linear-gradient(to_right,theme(colors.red.500)_75%,theme(colors.red.500/25%))]">
  <!-- ... -->
</div>

CSS 파일을 수정하지 않아도 되는 방법이라면 뭐든 좋죠, 그렇죠?

더 쉬운 CSS 변수 색상 설정

CSS 변수로 색상을 정의하고 설정하는 것을 좋아한다면, 아마도 현재 tailwind.config.js 파일에 다음과 같은 끔찍한 보일러플레이트 코드가 있을 것입니다:

tailwind.config.js
function withOpacityValue(variable) {
  return ({ opacityValue }) => {
    if (opacityValue === undefined) {
      return `rgb(var(${variable}))`
    }
    return `rgb(var(${variable}) / ${opacityValue})`
  }
}

module.exports = {
  theme: {
    colors: {
      primary: withOpacityValue('--color-primary'),
      secondary: withOpacityValue('--color-secondary'),
      // ...
    }
  }
}

v3.1에서는 이 과정을 훨씬 덜 고통스럽게 만들었습니다. 함수를 사용하는 대신 포맷 문자열로 색상을 정의할 수 있도록 지원을 추가했습니다:

tailwind.config.js
module.exports = {
  theme: {
    colors: {
      primary: 'rgb(var(--color-primary) / <alpha-value>)',
      secondary: 'rgb(var(--color-secondary) / <alpha-value>)',
      // ...
    }
  }
}

opacityValue 인자를 받는 함수를 작성하는 대신, <alpha-value> 자리 표시자가 포함된 문자열을 작성하면 Tailwind가 유틸리티에 따라 올바른 알파 값으로 해당 자리 표시자를 대체합니다.

이 내용을 처음 보는 경우, 더 자세한 정보를 위해 업데이트된 CSS 변수 사용하기 문서를 확인해 보세요.

Border spacing 유틸리티

separate borders를 사용할 때 테이블 테두리 간의 간격을 조절할 수 있도록 새로운 border-spacing 속성 유틸리티를 추가했습니다.

State City
Indiana Indianapolis
Ohio Columbus
Michigan Detroit
<table class="border-separate border-spacing-2 ...">
  <thead>
    <tr>
      <th class="border border-slate-300 ...">State</th>
      <th class="border border-slate-300 ...">City</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="border border-slate-300 ...">Indiana</td>
      <td class="border border-slate-300 ...">Indianapolis</td>
    </tr>
    <!-- ... -->
  </tbody>
</table>

여러분은 아마 이런 생각을 할지도 모릅니다. “나는 이런 식의 테이블을 만들어 본 적이 없는데…” 하지만 잠시만 들어보세요!

이 기능이 정말 유용한 경우 중 하나는 스틱키 헤더 행이 있는 테이블을 만들 때, 헤더 아래에 고정된 하단 테두리를 유지하고 싶을 때입니다.

이 테이블을 스크롤하여 스틱키 헤더 행이 동작하는 것을 확인하세요

Name Role
Courtney Henry Admin
Tom Cook Member
Whitney Francis Admin
Leonard Krasner Owner
Floyd Miles Member
Emily Selman Member
Kristin Watson Admin
Emma Dorsey Member
Alicia Bell Admin
Jenny Wilson Owner
Anna Roberts Member
Benjamin Russel Member
Jeffrey Webb Admin
Kathryn Murphy Member
<table class="border-separate border-spacing-0">
  <thead class="bg-gray-50">
    <tr>
      <th class="sticky top-0 z-10 border-b border-gray-300 ...">Name</th>
      <th class="sticky top-0 z-10 border-b border-gray-300 ...">Email</th>
      <th class="sticky top-0 z-10 border-b border-gray-300 ...">Role</th>
    </tr>
  </thead>
  <tbody class="bg-white">
    <tr>
      <td class="border-b border-gray-200 ...">Courtney Henry</td>
      <td class="border-b border-gray-200 ...">courtney.henry@example.com</td>
      <td class="border-b border-gray-200 ...">Admin</td>
    </tr>
    <!-- ... -->
  </tbody>
</table>

여러분은 아마 border-collapse를 사용하면 된다고 생각할 수 있습니다. 하지만 이 경우에는 border-separateborder-spacing-0 없이 테두리가 헤더 아래에 고정되지 않고 스크롤되어 사라질 것입니다. CSS는 정말 재미있죠?

더 자세한 내용은 border spacing 문서를 확인하세요.

활성화 및 선택적 상태 변형

:enabled:optional 의사 클래스에 대한 새로운 변형을 추가했습니다. 이 변형들은 폼 엘리먼트가 활성화되어 있거나 선택적일 때 적용됩니다.

“하지만 Adam, 왜 이런 게 필요할까요? 활성화와 선택적 상태는 기본값인데, 웹사이트를 만드는 거 맞아요?”

아야, 맞는 말이라서 더 아픕니다. 저는 이제 거의 이메일을 쓰고 GitHub에서 같은 질문에 반복해서 답변만 하고 있으니까요.

하지만 이 비활성화된 버튼 예제를 한번 보세요:

<button type="button" class="bg-indigo-500 hover:bg-indigo-400 disabled:opacity-75 ..." disabled>
  처리 중...
</button>

버튼 위에 마우스를 올렸을 때, 비활성화된 상태임에도 배경색이 바뀌는 걸 확인할 수 있습니다. 이전 버전에서는 보통 이렇게 고쳤을 겁니다:

<button type="button" class="disabled:hover:bg-indigo-500 bg-indigo-500 hover:bg-indigo-400 disabled:opacity-75  ..." disabled>
  처리 중...
</button>

하지만 새로운 enabled 수식어를 사용하면 이렇게 작성할 수 있습니다:

<button type="button" class="bg-indigo-500 hover:enabled:bg-indigo-400 disabled:opacity-75  ..." disabled>
  처리 중...
</button>

버튼이 비활성화되었을 때 호버 색상을 기본 색상으로 되돌리는 대신, hoverenabled 변형을 결합하여 버튼이 비활성화된 경우 호버 스타일을 아예 적용하지 않도록 했습니다. 이게 더 낫다고 생각합니다!

새로운 optional 수식어와 형제 상태 기능을 결합하여 필수가 아닌 필드에 대한 “필수” 알림을 숨기는 예제를 살펴보세요:

필수
필수
<form>
  <div>
    <label for="email" ...>이메일</label>
    <div>
      <input required class="peer ..." id="email" />
      <div class="peer-optional:hidden ...">
        필수
      </div>
    </div>
  </div>
  <div>
    <label for="name" ...>이름</label>
    <div>
      <input class="peer ..." id="name" />
      <div class="peer-optional:hidden ...">
        필수
      </div>
    </div>
  </div>
  <!-- ... -->
</form>

이렇게 하면 모든 폼 그룹에 동일한 마크업을 사용하고, 조건부 렌더링을 직접 처리하는 대신 CSS가 처리하도록 할 수 있습니다. 꽤 깔끔하죠!

Prefers-contrast variants

prefers-contrast 미디어 쿼리를 알고 계셨나요? 이제 Tailwind에서 기본적으로 지원합니다.

새로운 contrast-morecontrast-less 변형을 사용하여 사용자가 더 높거나 낮은 대비를 요청했을 때 디자인을 수정할 수 있습니다. 보통 macOS의 “대비 증가”와 같은 운영 체제 접근성 설정을 통해 요청됩니다.

개발자 도구에서 `prefers-contrast: more`를 에뮬레이트하여 변경 사항을 확인해 보세요

여러분의 신원을 도용하기 위해 필요합니다.

<form>
  <label class="block">
    <span class="block text-sm font-medium text-slate-700">주민등록번호</span>
    <input class="border-slate-200 placeholder-slate-400 contrast-more:border-slate-400 contrast-more:placeholder-slate-500"/>
    <p class="mt-2 opacity-10 contrast-more:opacity-100 text-slate-600 text-sm">
      여러분의 신원을 도용하기 위해 필요합니다.
    </p>
  </label>
</form>

이에 대해 문서를 작성했지만, 솔직히 여기서 더 많이 작성했습니다.

네이티브 다이얼로그 배경 스타일링

최근에 등장한 HTML <dialog> 엘리먼트는 꽤 괜찮은 브라우저 지원을 자랑하며, 최신 기술을 시도해보고 싶은 분들에게 추천할 만한 요소입니다.

다이얼로그에는 ::backdrop이라는 새로운 의사 엘리먼트가 있습니다. 이는 다이얼로그가 열려 있을 때 렌더링되며, Tailwind CSS v3.1에서는 이 배경을 스타일링할 수 있는 새로운 backdrop 수정자를 추가했습니다:

<dialog class="backdrop:bg-slate-900/50 ...">
  <form method="dialog">
    <!-- ... -->
    <button value="cancel">Cancel</button>
    <button>Submit</button>
  </form>
</dialog>

이 기능에 대해 더 깊이 알고 싶다면 MDN 다이얼로그 문서를 읽어보는 것을 추천합니다. 흥미로운 내용이 많지만, 알아야 할 것도 많습니다.

임의 값(variants용)

이 기능은 정말로 눈에 띄는 부분입니다. 여러분은 이미 addVariant API를 통해 커스텀 variants를 만들 수 있다는 걸 알고 계실 겁니다.

tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  // ...
  plugins: [
    plugin(function({ addVariant }) {
      addVariant('third', '&:nth-child(3)')
    })
  ]
}

그리고 HTML에서 직접 원하는 값을 사용할 수 있는 임의 값(arbitrary values) 기능도 알고 계실 겁니다.

<div class="top-[117px]">
  <!-- ... -->
</div>

이제 Tailwind CSS v3.1에서는 임의 variants(arbitrary variants)를 도입하여 HTML에서 직접 임시 variants를 만들 수 있습니다.

<div class="[&:nth-child(3)]:py-0">
  <!-- ... -->
</div>

이 기능은 특정 CSS 기능을 브라우저가 지원하는지 확인하는 @supports 쿼리와 같이, 매개변수화가 필요한 variants에 매우 유용합니다.

<div class="bg-white [@supports(backdrop-filter:blur(0))]:bg-white/50 [@supports(backdrop-filter:blur(0))]:backdrop-blur">
  <!-- ... -->
</div>

심지어 [&>*]와 같은 임의 variants를 사용해 자식 엘리먼트를 대상으로 스타일을 적용할 수도 있습니다.

  • Kristen Ramos

    kristen.ramos@example.com

  • Floyd Miles

    floyd.miles@example.com

  • Courtney Henry

    courtney.henry@example.com

<ul role="list" class="[&>*]:p-4 [&>*]:bg-white [&>*]:rounded-lg [&>*]:shadow space-y-4">
  <li class="flex">
    <img class="h-10 w-10 rounded-full" src="..." alt="" />
    <div class="ml-3 overflow-hidden">
      <p class="text-sm font-medium text-slate-900">Kristen Ramos</p>
      <p class="text-sm text-slate-500 truncate">kristen.ramos@example.com</p>
    </div>
  </li>
  <!-- ... -->
</ul>

심지어 두 번째 li 안에 있는 div의 첫 번째 p 엘리먼트에 hover 시 스타일을 적용할 수도 있습니다.

Try hovering over the text “Floyd Miles”

  • Kristen Ramos

    kristen.ramos@example.com

  • Floyd Miles

    floyd.miles@example.com

  • Courtney Henry

    courtney.henry@example.com

<ul role="list" class="hover:[&>li:nth-child(2)>div>p:first-child]:text-indigo-500 [&>*]:p-4 [&>*]:bg-white [&>*]:rounded-lg [&>*]:shadow space-y-4">
  <!-- ... -->
  <li class="flex">
    <img class="h-10 w-10 rounded-full" src="..." alt="" />
    <div class="ml-3 overflow-hidden">
      <p class="text-sm font-medium text-slate-900">Floyd Miles</p>
      <p class="text-sm text-slate-500 truncate">floyd.miles@example.com</p>
    </div>
  </li>
  <!-- ... -->
</ul>

이런 기능을 자주 사용해야 할까요? 그렇지는 않겠지만, 직접 변경할 수 없는 HTML을 스타일링할 때 꽤 유용한 탈출구가 될 수 있습니다. 날카로운 칼과 같지만, 최고의 요리사는 안전 가위로 음식을 준비하지 않습니다.

이 기능을 조금 사용해 보면, 상황에 따라 매우 유용한 도구라는 걸 알게 될 겁니다. 우리는 현재 작업 중인 새로운 웹사이트 템플릿에서 몇 가지 까다로운 부분에 이 기능을 사용하고 있으며, 커스텀 클래스를 만드는 것보다 훨씬 편리합니다.


이것이 바로 Tailwind CSS v3.1입니다! 마이너 버전 변경이기 때문에 호환성이 깨지는 변경 사항은 없으며, 최신 버전을 설치하기만 하면 프로젝트를 업데이트할 수 있습니다.

npm install tailwindcss@latest

버그 수정 및 여기서 언급하지 않은 몇 가지 사소한 개선 사항을 포함한 전체 변경 사항은 GitHub의 릴리스 노트에서 확인할 수 있습니다.

저는 이미 Tailwind CSS v3.2를 위한 여러 아이디어를 가지고 있습니다 (드디어 텍스트 그림자가 추가될지도?!), 하지만 현재는 새로운 웹사이트 템플릿을 완성하기 위해 열심히 작업 중입니다. 다음 주나 그 다음 주쯤에 또 다른 업데이트를 기대해 주세요!