Post

TailwindCSS (7) - Tailwind 4.0 업데이트 정리

📌시작하며

tailwind CSS가 4.0으로 업데이트 되었다! tailwind CSS가 3.0으로 업데이트 된게 2021년 12월이니, 3년 조금 넘어 1버전이 업데이트 된 것!😎 나는 tailwind를 기본으로 작업을 하기 때문에 이번에 어떤 내용들이 업데이트 되었는지 무척 기대가 된다. 그럼 블로그 글을 바탕으로 업데이트 내역을 살펴보고, 실제 학습용 repo에 사용해보면서 공부한 내용을 함께 정리하고자 한다.

✅tailwind CSS 4.0

테일윈드 CSS v4.0은 성능과 유연성에 최적화된 완전히 새로운 버전의 프레임워크로, 새로운 구성과 사용자 정의 환경을 제공하며, 웹 플랫폼이 제공하는 최신 기능을 최대한 활용한다고 설명한다.

🌷설치 / 업데이트

현재 내 repo는 next.js latest버전을 기반으로 tailwind 3버전이 설치되어있었는데, 공식문서의 upgrade guide를 따라 업그레이드 해주었다.

1
npx @tailwindcss/upgrade@next

설치후 확인하니 아래와 같이 4버전으로 업그레이드 되었다.

1
"@tailwindcss/postcss": "^4.0.0",

postcss.config.js 파일에 아래와 같이 적어주자.

1
2
3
4
5
6
7
8
9
//postcss.config.js
module.exports = {
  plugins: {
    "postcss-import": {},
    tailwindcss: {},
    autoprefixer: {},
    "@tailwindcss/postcss": {},
  },
}

그리고 global.css에 tailwind css를 import 해준다. 기존과 다르게 @import "tailwindcss" 한문장만 작성하는 것으로 변경되었다.

1
2
3
4
5
6
7
8
/* globals.css */
/* 
@tailwind base;
@tailwind components;
@tailwind utilities; 
*/

@import "tailwindcss";

마지막으로 dev모드에서 tailwind가 올바르게 적용되는지 확인해준다.

✅이름이 변경된 유틸리티

v4에서 몇가지 유틸리티의 이름이 변경되었다.

v3v4
shadow-smshadow-xs
shadowshadow-sm
drop-shadow-smdrop-shadow-xs
drop-shadowdrop-shadow-sm
blur-smblur-xs
blurblur-sm
backdrop-blur-smbackdrop-blur-xs
backdrop-blurbackdrop-blur-sm
rounded-smrounded-xs
roundedrounded-sm
outline-noneoutline-hidden
ringring-3

특징을 보자면, shadow, blur 처럼 단위가 없는 것들에 단위가 생기면서 기존의 sm이 xs로 변경되었음을 알 수 있다. 개인적으로는 관련된 모든 유틸리티의 이름이 통일성을 가지게 되었다고 생각해 만족하는 변경점이다. 😊

🌷 ring

  • v3: ring을 쓰면 3px 효과가 나타났다.
  • v4: ring-3 이란 클래스가 생겨났고, ring1px이다.

참고로, ringring-1 모두 동일하게 1px의 효과가 나타나는 것을 확인했다. 개발도구로 확인했을 때 동일한 class가 사용된다.

1
2
3
4
5
6
7
8
9
10
11
export default function TailwindFour() {
  return (
    <div className="h-dvh w-full p-6">
      <div className="flex flex-col gap-10">
        <button className="px-4 py-2 bg-red-400 ring">버튼입니다</button>
        <button className="px-4 py-2 bg-blue-400 ring-1">버튼입니다</button>
        <button className="px-4 py-2 bg-green-400 ring-3">버튼입니다</button>
      </div>
    </div>
  )
}

🌷기본 border 컬러 변경

  • v3: 기본적으로 border 의 색상이 gray-200 이었다.
  • v4: currentColor와 동일한 색상으로 설정된다. (브라우저의 기본 동작과 일치함)

🌷기본 placeholder 컬러 변경

  • v3: 기본적으로 placeholder 의 색상이 gray-400 이었다.
  • v4: currentColor의 50% opacity로 설정된다.

✅새로운 고성능 엔진

전체 빌드는 최대 5배, 증분 빌드는 100배이상 빨라졌다.

증분 빌드(incremental builds) 소프트웨어 빌시 변경된 부분만 빌드하는 방법

:star:CSS 중심의 통합적인 커스텀 방식

큰 변화 중 하나인데, 프로젝트를 구성할 때 tailwind.config.js 의 JavaScript를 사용하는 대신, CSS로 구성하도록 변경되었다.

🌷3.0 버전의 tailwind.config.js

3.0버전의 경우 아래와 같이 파일을 작성해서 tailwind를 설정해주었다. 아래는 lime이란 color를 정의해준 예시이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import type { Config } from "tailwindcss";

export default {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        'line': "#B2F300",
      },
    },
  },
  plugins: [],
} satisfies Config;

🌷4.0 버전의 CSS

globals.css에 다음과 같이 작성하자.

1
2
3
4
5
@import "tailwindcss";

@theme {
  --color-lime: #b2f300;
}

해당 구문을 해석하면 color에 lime이란 이름을 가진 색을 추가하겠다 가 되고, 실제 사용방식은 동일하다.

1
2
<button className="px-4 py-2 bg-lime ring-3">버튼입니다</button>
<button className="px-4 py-2 text-lime ring-3">버튼입니다</button>

그러면 위에서 정의한 lime 색이 정확히 적용된 모습을 보인다! 추가로, 4.0에서의 ring 컬러는 currentColor가 적용되어, text 색상인 lime 색과 동일한 색을 띄게 된다.

:star:keyframe animation 정의하기

이번엔 animation을 정의해보자. 위와 동일하게 CSS에서 작성한다.

keyframe을 정의하는 방식은 기존 CSS 작성방식과 동일하며, @theme에 사용 등록만 해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@import "tailwindcss";

@theme {
  --color-lime: #b2f300;

  --animate-scale-up: scale-up 2s ease-out;
}

@keyframes scale-up {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

실제 컴포넌트에 적용은 다음과 같다.

1
<div className="size-20 bg-lime animate-scale-up "></div>

✅@container queries

CSS의 최신 기능인 container queries의 기능이 추가 되었다.

🌷container queries

컨테이너 쿼리는 뷰포트를 기준으로 하는 @media 쿼리와 다르게, 요소의 컨테이너 크기에 따라 스타일을 지정할 수 있다. 아래 예제를 살펴보자.

먼저 globals.css에 다음과 같이 컨테이너 크기를 지정해주었다.

1
2
3
4
5
6
@import "tailwindcss";

@theme {
  --container-sm: 100px;
  --container-md: 200px;
}

컴포넌트를 이어서 살펴보자. 해당 예제는 부모 div를 클릭하면 컨테이너 크기가 변경되며, @sm@md로 컨테이너 사이즈에 따라 조건부 css를 적용했다.

즉, container 사이즈가 @sm일때 text-red-500text-xs를 적용하며 container가 @md 인 경우엔 text-blue-500text-2xl이 적용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const ContainerQueries = () => {
  const [isSmall, setIsSmall] = useState<boolean>(false);

  return (
    {/* 클릭 시 컨테이너 크기 변경*/}
				<div
					className={cn('@container bg-lime cursor-pointer ', {
						'w-[100px]': isSmall,
						'w-[200px]': !isSmall,
					})}
					onClick={() => {
						setIsSmall(!isSmall);
					}}>
					{/* 컨테이너 크기에 따라 글씨 색과 크기가 변경됨 */}
					<div
						className={`text-center @sm:text-xs @sm:text-red-500 @md:text-blue-500 @md:text-2xl text-black`}>
						{isSmall
							? '컨테이너가 작아져, 글씨가 작아졌습니다.'
							: '컨테이너가 커져 글씨가 커졌습니다.'}
					</div>
				</div>
  )
}

min max값을 지정해줄 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
<div
  className={cn("@container bg-gray-200 ", {
    "w-[50px]": size === "xs",
    "w-[100px]": size === "sm",
    "w-[200px]": size === "md",
    "w-[300px]": size === "lg",
  })}
>
  <div className="@sm:text-red-500 @md:text-green-500 @lg:text-blue-500 @xs:@max-md:text-2xl">
    현재 {size} 사이즈에 맞는 CSS가 적용되었습니다.
  </div>
</div>

예제와 같이 @xs:@max-md:text-2xl을 이용해 xs~md사이즈까지만 특정 css를 적용해 줄 수 있다.

✅@starting-style

CSS의 @starting-style 의 기능이 들어왔다. 처음보는 기능이라 MDN 사이트에 들어가봤는데, 아직 Firefox에서는 사용이 불가능했지만, 대부분의 브라우저에서 사용 가능했다.

@starting-style은 요소가 처음 DOM에 렌더링되거나, display: none에서 visible로 전환될 때 초기 상태를 설정하는 데 사용된다. 즉 요소가 처음 표시될 때 스타일을 지정하거나 부드러운 애니메이션을 넣어줄 수 있다.

이 설명에 바로 떠오르는 것은 바로 popup이나 modal창이다.

자세한 사항은 다음 포스팅을 참고한다.

:star:gradient 확장

그라디언트를 더욱 다양하게 활용할 수 있는 유틸리티가 추가되었다.

gradient에 linear만 지원하는 것이 아닌 다른 형태도 함께 지원하면서 기존의 linear형태의 gradient의 경우 bg-gradient-*에서 bg-linear-*로 이름이 변경되었다!

🌷선형 그라디언트와 각도

선형 그라디언트에 각도를 쉽게 넣을 수 있다. 사용 방식은 linear-각도와 같다.

사용자가 원하는 각도의 경우 bg-linear-[190deg]와 같은 방식으로 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react"

const Gradient = () => {
  return (
    <div className=" w-full p-6">
      <h1 className="text-2xl font-semibold">그라디언트</h1>
      <h2 className="text-xl font-semibold mt-5">각도</h2>
      <div className="**:size-20 **:rounded-sm **:text-white **:via-purple-500 **:to-pink-500 **:from-indigo-500 space-y-2">
        <div className="bg-linear-45">45</div>
        <div className="bg-linear-90">90</div>
        <div className="bg-linear-180">180</div>
        <div className="bg-linear-[190deg]">190</div>
        <div className="bg-linear-270">270</div>
      </div>
    </div>
  )
}

export default Gradient

🌷색상 제어

색상 보간(interpolation) 방식을 제어할 수 있는 모디파이어(modifier)가 추가되었다.

색상 보간 보간이란, 알려진 값을 기반으로 값을 계산하는 프로세스를 의미한다. 그라디언트에서는 제공된 색상 목록을 기반으로 색상의 중간 값을 정의하는 데 사용된다.

작성할 때는 다음과 같이 bg-linear-to-r/srgb, bg-linear-to-r/oklch와 같이 작성한다. tailwind 4.0에서는 oklch 방식이 기본 적용되기 떄문에, oklch 방식을 사용할 땐 굳이 작성하지 않아도 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react"

const Gradient = () => {
  return (
    <div className=" w-full p-6">
      <h2 className="text-xl font-semibold mt-5">색상</h2>
      <div className="**:size-20 **:rounded-sm **via-purple-500 **:to-pink-500 **:from-indigo-500 space-y-2  **:text-white">
        <div className="bg-linear-to-r/srgb">srgb</div>
        <div className="bg-linear-to-r/oklch">oklch</div>
      </div>
    </div>
  )
}

export default Gradient

🌷원뿔형과 방사형

원뿔형, 방사형 그래디언트를 생성하기 위한 유틸리티가 추가되었다. bg-conic-_, bg-radial-_ 로 작성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react"

const Gradient = () => {
  return (
    <div className=" w-full p-6">
      <h2 className="text-xl font-semibold mt-5">원뿔형</h2>
      <div className="size-20 rounded-sm bg-conic/[in_hsl_longer_hue] from-red-600 to-red-600">
        원뿔
      </div>
      <h2 className="text-xl font-semibold mt-5">방사형</h2>
      <div className="size-20 rounded-full bg-radial-[at_25%_25%] from-white to-zinc-900 to-75%">
        방사
      </div>
    </div>
  )
}

export default Gradient

:star:자식요소 CSS

자식요소에 한 번에 CSS를 줄 수 있는 방법이 생겨났다!

🌷직계 요소

직계 요소는 *:~를 사용해 작성한다.

아래 예제에서는 이 규칙을 이용해 <li> 태그에 스타일을 주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function Children() {
  return (
    <Container>
      <Container.Title>자식요소</Container.Title>
      <Container.SubTitle>직계</Container.SubTitle>
      <div className="mt-3">
        <ul className="flex gap-2 *:rounded-full *:border *:border-sky-100 *:bg-sky-50 *:px-2 *:py-0.5 *:text-sky-500">
          {hutaba.map((item, index) => {
            return <li key={item.name}>{item.name}</li>
          })}
        </ul>
      </div>
    </Container>
  )
}

🌷자손 요소

아래 자손 요소 모두에 스타일을 주고 싶다면 **:~ 규칙을 사용한다.

이때, data-* 규칙을 이용해 특정 tag들만 CSS를 줄 수 있다! 아래는 <p>태그에 data-birth 라고 이름을 지어 주고, ul 태그에 **:data-birth:text-gray-500 와 같은 방식으로 한 번에 스타일을 주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export default function Children() {
  return (
    <Container>
      <Container.SubTitle>모든 자손</Container.SubTitle>
      <ul className="**:data-avatar:size-12 **:data-avatar:rounded-full **:data-birth:text-gray-500 **:data-birth:text-xs **:text-sky-500 space-y-2 mt-3">
        {hutaba.map((item, index) => {
          return (
            <li key={item.name} className="flex items-center gap-4">
              <div data-avatar className="border overflow-hidden">
                <Image
                  src={item.src}
                  alt={item.name}
                  width={100}
                  height={100}
                />
              </div>
              <div>
                <p>{item.name}</p>
                <p data-birth className="">
                  {item.birth}
                </p>
              </div>
            </li>
          )
        })}
      </ul>
    </Container>
  )
}

✅not

not을 이용해 특정 상황이 아닐 때만 CSS를 줄 수 있다! 특히 checked가 되지 않았을 때(not) 와 같은 상황에 활용도가 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default function Not() {
  return (
    <Container>
      <Container.Title>not</Container.Title>
      <div className="space-y-5">
        <div className="not-hover:opacity-75 bg-blue-500 p-4 text-white cursor-pointer">
          마우스를 올리지 않으면 투명도 75%
        </div>
        <label className="flex items-center space-x-2">
          <input type="checkbox" id="apple" className="hidden peer" />
          <div className="peer-not-checked:bg-red-500 peer-checked:bg-green-500 text-white px-4 py-2 cursor-pointer">
            체크하면 초록색, 해제하면 빨간색
          </div>
        </label>
      </div>
    </Container>
  )
}

✅Dynamic Utility

4.0버전에서는 더 다양한 임의의 값을 쓸 수 있게 변경되었다.

예를 들어, grid 컬럼을 15개를 쓰고 싶을 때, 기존 3버전에서는 해당 값이 tailwind에 미리 등록되지 않았으므로, grid-cols-[15]와 같이 작성해야 했으나, 이제는 그냥 grid-cols-15로 써도 알아서 처리된다.

동일하게, mt-11이나 mt-13과 같이 기존에는 사용할 수 없던 class도 알아서 처리된다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Container from "@/components/Container"

export default function Dynamic() {
  return (
    <Container>
      <Container.Title>Dynamic Utility</Container.Title>
      <div className="grid grid-cols-15 gap-2 **:size-4 **:text-xs **:text-white **:bg-blue-500">
        {Array.from({ length: 30 }).map((_, index) => (
          <div key={index}>{index + 1}</div>
        ))}
      </div>
      <div className="mt-11 grid grid-cols-17 gap-2 **:size-4 **:text-xs **:text-white **:bg-red-500">
        {Array.from({ length: 30 }).map((_, index) => (
          <div key={index}>{index + 1}</div>
        ))}
      </div>
      <div className="mt-13 grid grid-cols-11 gap-2 **:size-4 **:text-xs **:text-white **:bg-green-500">
        {Array.from({ length: 30 }).map((_, index) => (
          <div key={index}>{index + 1}</div>
        ))}
      </div>
    </Container>
  )
}

이게 가능한 이유는, Tailwind의 기본 간격 단위인 –spacing을 기반으로 자동 계산되기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@layer theme {
  :root {
    --spacing: 0.25rem;
  }
}
@layer utilities {
  .mt-8 {
    margin-top: calc(var(--spacing) * 8);
  }
  .w-17 {
    width: calc(var(--spacing) * 17);
  }
  .pr-29 {
    padding-right: calc(var(--spacing) * 29);
  }
}

👏마무리 하며

결론부터 말하자면, 정말 마음에 드는 업데이트다! 자손 요소에 한 번에 CSS를 지정해주는 게 너무 편리해서 얼른 도입하고 싶을정도….

그런데, 이 포스팅 연습용으로 사용한 repo는 vercel로 github push때마다 배포를 시도하게 해두었는데, tailwind 4.0 업데이트 하자 배포가 안되었다(!) 기존에 설치해둔 tailwind 관련 eslint 라이브러리와 tailwind 4.0이 호환되지 않았기 때문이다…🤔

언제쯤 업데이트가 될지 궁금해서 해당 라이브러리의 깃허브를 찾아가 보니 이미 많은 사람들이 업데이트에 대해 문의해두었으나, 시간이 다소 소요될 것이라는 답이 달려있었다.😅 나의 여가 생활에 하는 프로젝트이기 때문이다 라는 말에 (그건 그렇네요…)하게 되는 것은 덤 (ㅎㅎ)

코드 품질을 위한 tailwind의 eslint를 적용하는 것에 관련된 라이브러리기 때문에 아예 없는 상태에서 대형 프로젝트를 시작하는 것은 피하고 싶어서 아무래도, 실제 프로젝트 도입은 조금 더 미뤄지게 되겠지만, 개인적으로 진행할 때는 적극적으로 사용할 계획이다!

이번 업데이트는 공식 릴리즈 이틀 후에 공식 docs를 보면서 예제를 만들어 보고, 기록해보았는데 훨씬 이해하기도 쉽고, 몰랐던 기존 tailwind 기능을 더 많이 알 수 있어서 너무 만족스러웠다.😊

회사에서, 이 기능을 공유할 수 있길 바라며, 이번 포스팅은 여기서 끝!

🗂️참고 사이트

This post is licensed under CC BY 4.0 by the author.