Tailwind CSS v3.2: 동적 브레이크포인트, 다중 설정 파일, 컨테이너 쿼리 등

Date

다시 그 시간이 왔습니다! _“새로운 Tailwind 릴리스에 추가할 게 뭐가 있을지 전혀 모르겠다”_에서 _“와, 이건 정말 엄청난 양의 새로운 기능들이네 — 상황이 완전히 통제 불능이 되기 전에 릴리스를 태그해야겠다”_로 빠르게 전환되는 그 시간 말이죠.

Tailwind CSS v3.2

Tailwind CSS v3.2이 출시되었습니다. 이번 버전에는 동적 브레이크포인트, 단일 프로젝트 내 다중 설정 파일, 중첩 그룹, 매개변수화된 변형, 컨테이너 쿼리 등 정말 엄청난 양의 새로운 기능들이 포함되어 있습니다.

항상 그렇듯이 모든 세부 수정 사항과 개선 내용은 릴리스 노트에서 확인할 수 있지만, 여기서 주요 내용을 간단히 살펴보겠습니다:

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

npm install -D tailwindcss@latest

또는 Tailwind Play에서 새로운 기능들을 직접 시험해볼 수 있습니다. 브라우저에서 바로 모든 것을 즉시 사용해볼 수 있습니다.


하나의 프로젝트에서 여러 설정 파일 사용하기 (@config 사용)

이제 CSS 파일에서 사용할 Tailwind CSS 설정 파일을 지정할 수 있는 새로운 @config 지시자를 추가했습니다:

@config "./tailwind.admin.config.js";
@tailwind base;
@tailwind components;
@tailwind utilities;

이 기능을 사용하면 하나의 프로젝트에서 서로 다른 Tailwind 설정을 가진 여러 스타일시트를 쉽게 만들 수 있습니다. 예를 들어, 고객용 사이트 부분과 관리자/백엔드 영역을 위한 별도의 설정 파일을 가질 수 있습니다.

이전에도 웹팩 설정을 통해 이 작업을 수행할 수 있었지만, 새로운 @config 지시자를 사용하면 빌드 도구 설정을 완전히 제어할 수 없는 프로젝트에서도 누구나 쉽게 사용할 수 있습니다.

브라우저 지원 기반 스타일링 with supports-*

이제 supports-[...] 변형을 사용해 사용자 브라우저에서 특정 기능이 지원되는지 여부에 따라 조건부로 스타일을 적용할 수 있습니다. 이 변형은 내부적으로 @supports 규칙을 생성합니다.

<div class="flex supports-[display:grid]:grid ...">
  <!-- ... -->
</div>

supports-[...] 변형은 대괄호 안에 @supports (...)와 함께 사용할 수 있는 모든 것을 받습니다. 예를 들어 속성/값 쌍이나 and, or를 사용한 표현식도 가능합니다.

특정 속성 자체가 지원되는지 확인하기만 하면 된다면, 속성 이름만 지정해도 Tailwind가 나머지를 자동으로 채워줍니다:

<div class="bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur ...">
  <!-- ... -->
</div>

ARIA 속성 변형

이제 새로운 aria-* 변형을 사용해 ARIA 속성에 따라 조건부 스타일을 적용할 수 있습니다.

예를 들어, aria-checked 상태가 true인지에 따라 엘리먼트의 배경색을 업데이트할 수 있습니다:

<span class="bg-gray-600 aria-checked:bg-blue-600" aria-checked="true" role="checkbox">
  <!-- ... -->
</span>

기본적으로 가장 일반적인 boolean ARIA 속성에 대한 수정자를 포함했습니다:

수정자CSS
aria-checked&[aria-checked=“true”]
aria-disabled&[aria-disabled=“true”]
aria-expanded&[aria-expanded=“true”]
aria-hidden&[aria-hidden=“true”]
aria-pressed&[aria-pressed=“true”]
aria-readonly&[aria-readonly=“true”]
aria-required&[aria-required=“true”]
aria-selected&[aria-selected=“true”]

tailwind.config.js 파일에서 theme.aria 또는 theme.extend.aria를 수정해 사용할 aria-* 수정자를 커스터마이즈할 수 있습니다:

tailwind.config.js
module.exports = {
  theme: {
    extend: {
      aria: {
        asc: 'sort="ascending"',
        desc: 'sort="descending"',
      },
    },
  },
};

테마에 포함시키기 어려운 일회성 aria 수정자를 사용하거나, 특정 값을 가진 복잡한 ARIA 속성을 사용해야 하는 경우, 대괄호를 사용해 임의의 값으로 속성을 동적으로 생성할 수 있습니다.

Invoice # Client Amount
#100 Pendant Publishing $2,000.00
#101 Kruger Industrial Smoothing $545.00
#102 J. Peterman $10,000.25
<table>
  <thead>
    <tr>
      <th
        aria-sort="ascending"
        class="aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')]"
      >
        Invoice #
      </th>
      <!-- ... -->
    </tr>
  </thead>
  <!-- ... -->
</table>

ARIA 상태 수정자는 group-aria-*peer-aria-* 수정자를 사용해 부모 및 형제 엘리먼트를 대상으로 할 수도 있습니다:

<table>
  <thead>
    <tr>
    <th aria-sort="ascending" class="group">
      Invoice #
      <svg class="group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180"><!-- ... --></svg>
    </th>
    <!-- ... -->
    </tr>
  </thead>
  <!-- ... -->
</table>

데이터 속성(data attribute) 변형

이제 새로운 data-* 변형을 사용해 데이터 속성에 따라 조건부 스타일을 적용할 수 있습니다.

정의상 표준 data-* 속성은 없기 때문에, 기본적으로 임의의 값을 지원합니다. 예를 들어:

<!-- 적용됨 -->
<div data-size="large" class="data-[size=large]:p-8">
  <!-- ... -->
</div>

<!-- 적용되지 않음 -->
<div data-size="medium" class="data-[size=large]:p-8">
  <!-- ... -->
</div>

<!-- 생성된 CSS -->
<style>
  .data-\[size\=large\]\:p-8[data-size="large"] {
    padding: 2rem;
  }
</style>

프로젝트에서 자주 사용하는 데이터 속성 선택자에 대한 단축키를 tailwind.config.js 파일의 theme 섹션 아래 data 키에서 설정할 수 있습니다:

// tailwind.config.js
module.exports = {
  theme: {
    data: {
      checked: 'ui~="checked"',
    },
  },
  // ...
};
<div data-ui="checked active" class="data-checked:underline">
  <!-- ... -->
</div>

이 변형은 프레임워크의 다른 많은 변형과 마찬가지로 group-*peer-* 변형으로도 작동합니다:

<div data-size="large" class="group">
  <div class="group-data-[size=large]:p-8">
    <!-- `p-8`이 적용됨 -->
  </div>
</div>

<div data-size="medium" class="group">
  <div class="group-data-[size=large]:p-8">
    <!-- `p-8`이 적용되지 않음 -->
  </div>
</div>

Max-width와 동적 브레이크포인트

이제 설정된 브레이크포인트를 기반으로 최대 너비 미디어 쿼리를 적용할 수 있는 새로운 max-* 변형이 추가되었습니다:

<div class="max-lg:p-8">
  <!-- `lg` 브레이크포인트가 적용될 때까지 `p-8`을 적용합니다 -->
</div>

개인적으로는 여전히 최소 너비 브레이크포인트를 사용하는 것을 권장하지만, 이 기능은 다른 브레이크포인트에서 스타일을 되돌릴 필요가 없다는 유용한 워크플로우 이점을 제공합니다.

예를 들어, 이 기능이 없으면 다음과 같은 작업을 해야 할 때가 많습니다:

<div class="md:sr-only xl:not-sr-only">
  <!-- ... -->
</div>

이 기능을 사용하면 원래 선언에 max-* 변형을 추가하여 스타일을 되돌릴 필요가 없습니다:

<div class="md:max-xl:sr-only">
  <!-- ... -->
</div>

이와 함께 임의의 값을 지원하는 기능과 임의의 값만 허용하는 새로운 min-* 변형이 추가되어 다음과 같은 작업을 할 수 있습니다:

<div class="min-[712px]:max-[877px]:right-16 ...">
  <!-- ... -->
</div>

이 기능들은 프로젝트가 단순한 screens 구성을 사용할 때만 사용 가능하다는 점을 주의해야 합니다.

이 기능들은 브라우저에서 예상한 동작을 제공하기 위해 모든 미디어 쿼리가 최종 CSS에서 정렬되어야 하기 때문에 보이는 것보다 훨씬 복잡합니다. 따라서 현재는 screens 구성이 기본 구성과 같이 문자열 값만 있는 단순한 객체일 때만 작동합니다:

// tailwind.config.js
module.exports = {
  theme: {
    screens: {
      sm: "640px",
      md: "768px",
      lg: "1024px",
      xl: "1280px",
      "2xl": "1536px",
    },
  },
};

이미 max-width 브레이크포인트가 정의되어 있거나 범위 기반 미디어 쿼리, 또는 문자열 이외의 다른 것이 포함된 복잡한 구성이 있다면 이 기능을 사용할 수 없습니다. 앞으로 이를 해결할 수 있을지 모르겠지만, CSS가 어떻게 정렬되어야 하는지에 대한 많은 질문이 남아 있어 아직 답을 찾지 못했습니다.

따라서 현재(그리고 아마도 영원히) 이 기능을 사용하려면 screens 구성이 단순해야 합니다. 이 기능들이 복잡한 screens 구성을 불필요하게 만들기를 바랍니다.


동적 group-* 및 peer-* 변형

이제 대괄호 안에 “그룹화” 또는 “피어화”할 커스텀 선택자를 전달하여 실시간으로 group-*peer-* 변형을 생성할 수 있습니다:

<div class="group is-published">
  <div class="hidden group-[.is-published]:block">
    Published
  </div>
</div>

더 많은 제어를 위해 & 문자를 사용하여 .group 또는 .peer가 전달한 선택자와 상대적으로 최종 선택자에서 어디에 위치해야 하는지 표시할 수 있습니다:

<div>
  <input type="text" class="peer" />
  <div class="hidden peer-[:nth-of-type(3)_&]:block">
    <!-- ... -->
  </div>
</div>

솔직히 말해서, 이 기능을 평생에 세 번 정도밖에 사용하지 않을 가능성이 높지만, 그래도 꽤 멋진 기능입니다. 앞으로 이 기능을 기반으로 하여 grouppeer가 서드파티 플러그인에 의해 등록된 변형과 더 자동으로 작동할 수 있기를 바랍니다.


Dynamic variants with matchVariant

여러분은 아마도 새로운 기능들에서 variant-[...]라는 새로운 구문을 많이 보셨을 겁니다. 이 모든 것은 새로운 matchVariant 플러그인 API 덕분에 가능한데, 이 API를 통해 우리가 “동적 변형(dynamic variants)“이라고 부르는 것을 만들 수 있습니다.

다음은 가상의 툴팁 라이브러리를 위한 placement-* 변형을 만드는 예제입니다. 이 라이브러리는 data-placement 속성을 사용해 툴팁의 현재 위치를 알려줍니다:

let plugin = require("tailwindcss/plugin");

module.exports = {
  // ...
  plugins: [
    plugin(function ({ matchVariant }) {
      matchVariant(
        "placement",
        (value) => {
          return `&[data-placement=${value}]`;
        },
        {
          values: {
            t: "top",
            r: "right",
            b: "bottom",
            l: "left",
          },
        }
      );
    }),
  ],
};

위에서 정의한 변형은 placement-tplacement-b와 같은 변형을 제공하지만, 대괄호 안에 임의의 값을 넣는 것도 지원합니다. 따라서 이 가상의 툴팁 라이브러리가 내장 값으로 만들 필요가 없는 다른 잠재적인 값을 가지고 있다면, 다음과 같은 작업도 가능합니다:

<div class="placement-[top-start]:mb-2 ...">
  <!-- ... -->
</div>

이 API를 사용해 커스텀 변형을 정의할 때, 각 클래스가 동일한 변형에서 나온 다른 값들과 올바른 우선순위를 가지도록 CSS가 생성되는 순서를 어느 정도 제어할 수 있는 것이 중요합니다. 이를 지원하기 위해, 변형을 정의할 때 sort 함수를 제공할 수 있습니다:

matchVariant("min", (value) => `@media (min-width: ${value})`, {
  sort(a, z) {
    return parseInt(a) - parseInt(z);
  },
});

중첩된 그룹과 다중 피어 지원을 위한 변형 수식어 사용

여러 개의 group이 서로 중첩되어 있을 때, Tailwind는 이를 명확히 구분할 방법이 없어 문제가 발생할 수 있습니다.

이를 해결하기 위해 **변형 수식어(variant modifiers)**를 도입했습니다. 이는 선택적 투명도 수식어 구문에서 영감을 받은 새로운 동적 청크로, 각 그룹/피어에 고유한 식별자를 부여할 수 있습니다.

다음은 그 예시입니다:

<div class="group/sidebar ...">
  <!-- ... -->
  <div class="group/navitem ...">
    <a
      href="#"
      class="opacity-50 group-hover/sidebar:opacity-75 group-hover/navitem:bg-black/75"
    >
      <!-- ... -->
    </a>
  </div>
  <!-- ... -->
</div>

이를 통해 각 그룹에 상황에 맞는 명확한 이름을 즉시 부여할 수 있으며, Tailwind는 이를 작동시키기 위해 필요한 CSS를 생성합니다.

이 문제를 해결하기 위해 몇 년 동안 좋은 접근 방식을 찾고 있었는데, 이번에 마침내 그 힘과 유연성을 제공하는 해결책을 찾게 되어 매우 기쁩니다.

컨테이너 쿼리

믿기 어렵지만 컨테이너 쿼리가 드디어 실현되었고, 브라우저 지원이 프로덕션 준비에 거의 다다랐습니다. 사실 Electron 앱을 개발 중이라면 지금 바로 사용할 수 있습니다.

오늘 우리는 @tailwindcss/container-queries를 출시했습니다. 이는 Tailwind CSS 프레임워크에 컨테이너 쿼리 지원을 추가하는 새로운 퍼스트파티 플러그인입니다. 일반 미디어 쿼리와 구분하기 위해 새로운 @ 구문을 사용합니다:

<div class="@container">
  <div class="block @lg:flex">
    <!-- ... -->
  </div>
</div>

기본적으로 우리는 기본 max-width 스케일에 맞는 컨테이너 크기 세트를 제공합니다:

이름
xs20rem
sm24rem
md28rem
lg32rem
xl36rem
2xl42rem
3xl48rem
4xl56rem
5xl64rem
6xl72rem
7xl80rem

tailwind.config.js 파일의 containers 키를 사용하여 사용 가능한 값을 구성할 수 있습니다:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      containers: {
        2xs: '16rem',
        // 등등...
      },
    },
  },
}

또한 @[...] 구문을 사용하여 임의의 값을 지원합니다:

<div class="@container">
  <div class="block @[618px]:flex">
    <!-- ... -->
  </div>
</div>

그리고 group-*peer-* 변형에 사용하는 것과 동일한 변형 수정자 구문을 사용하여 명명된 컨테이너를 지원합니다:

<div class="@container/main">
  <!-- ... -->
  <div>
    <div class="block @lg/main:flex">
      <!-- ... -->
    </div>
  </div>
</div>

현재는 간단한 min-width 기반 컨테이너 쿼리로 시작했지만, 시간이 지남에 따라 범위를 확장할 계획입니다. API를 완벽하게 정리했다고 느껴지면 이를 코어로 통합할 예정입니다.

완전한 문서는 GitHub에서 확인할 수 있습니다.


이렇게 해서 Tailwind CSS v3.2를 소개합니다! 주요 개선 사항이 있지만 단순한 마이너 버전 변경이므로, 기존 프로젝트를 업데이트하려면 의존성을 업데이트하기만 하면 됩니다:

npm install -D tailwindcss@latest

네, 뒤에서 말씀하시는 거 들립니다. 아직 텍스트 그림자는 없지만, 적어도 HTML을 벗어나지 않고도 체크박스의 부모가 리스트에서 세 번째 자식일 때 체크박스의 형제 요소를 스타일링할 수 있습니다. 우선순위가 중요하죠.