Tailwind 雜記

來點不一樣的 CSS。

簡述

Tailwind 從以前給我的印象就是個麻煩的東西,但並不是因為 HTML 很難看的關係,而是當習慣了 CSS Selector 的概念後,就會覺得 Tailwind 這種全部綁在 class 中的寫法用起來各種障礙。

但自從學了 React 後,逐漸體會到 Component 跟 Tailwind 結合起來用的效益,慢慢的改變了以前的觀點。

總之這篇會把我想記的一些觀念給寫下來,如果想知道更多的話推薦參考這份 官方文件

前置作業

初始化

簡單來說就是建立 tailwind.config.js,可以用底下的指令來產生:

1
npx tailwind init

初始化的設定會長這樣:

1
2
3
4
5
6
7
8
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [],
theme: {
extend: {}
},
plugins: []
}

編譯

如果剛剛初始化以後沒有設定 content 就直接執行編譯的話,可能會看到這段訊息:

config-warning

簡單來說這邊是要你設定「所有會使用到 tailwind class 名稱」的檔案路徑,例如說我在 ./public/index.html 中有用到這些 class,那我就會這樣設定:

1
2
3
4
5
6
7
8
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'], // src 底下所有的 .html 或 .js 檔案
theme: {
extend: {}
},
plugins: []
}

附註:glob pattern 可以參考這份 cheat-sheet

設定完再跑一次編譯時,Tailwind 就會自動根據我設定的路徑來產出所有用到的 class,就可以正常使用囉!更多細節請參考 官方文件

新增自己的 utils

  1. 先引入 plugin
  2. 執行 plugin 時會拿到 addUtilities 這個參數,用它來加就對了

附註:記得要加上 .(有被雷到過)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** @type {import('tailwindcss').Config} */
const plugin = require('tailwindcss/plugin')

module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {}
},
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
'.rotateY-90': {
transform: 'rotateY(90deg)'
},
'.rotateY-0': {
transform: 'rotateY(0deg)'
}
})
})
]
}

新增顏色

可以在 extend 中新增項目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
primary: '#F97C7C',
secondary: {
100: '#3a97d4',
200: '#1a2037'
}
}
}
},
plugins: []
}

補充一下這背後的運作方式,如果你用 tailwind init --full 來做初始化的話可以看到 config 預設值。接著你滑到 colors 段落應該能看到這樣的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** @type {import('tailwindcss').Config} */
module.exports = {
// 略...
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px'
},
// colors 初始值
colors: ({ colors }) => ({
inherit: colors.inherit,
current: colors.current,
transparent: colors.transparent,
black: colors.black,
white: colors.white,
slate: colors.slate,
gray: colors.gray
// 略...
})
}
}

簡單來說 colors 就只是一個物件(function 的回傳值)而已,所以當你添增一些新的屬性到 colors 身上以後,tailwind 就可以依據這個物件來產生對應的 class 與 css 內容,其實就只是這樣而已。

根據父層的狀態來改變 style

舉例來說,如果我希望父層 hover 的時候改變子層的樣式,那可以用 group 這個方式來做:

1
2
3
4
5
6
<div class="group">
<!-- 父層 hover 時套用 bg-rose-400 -->
<div class="group-hover:bg-rose-400 w-1/2 mx-auto my-10 p-4 bg-blue-300 rounded duration-300">
<h1 class="text-white text-center text-2xl">Hello</h1>
</div>
</div>

first-child / odd /even 之類的類選取器

要用的話可以用 odd:*even:*first:* 之類的 class 來做:

1
2
3
4
5
6
7
8
<div class="w-1/2 mx-auto py-10">
<div class="mb-2 p-4 rounded odd:bg-sky-300 even:bg-rose-300">
<h1 class="text-white text-center text-2xl">Hello</h1>
</div>
<div class="mb-2 p-4 rounded odd:bg-sky-300 even:bg-rose-300">
<h1 class="text-white text-center text-2xl">Hello</h1>
</div>
</div>

我當時還蠻疑惑的問題是,如果我有 10 列的話不就得寫 10 次?而且這跟我直接用人工把奇數偶數的樣式套用不同的 class 有什麼差?

但後來仔細想想,當我們真的在顯示資料時不太可能這樣 hard code,以 React 來說的話,我們可能會這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const arr = ['Hello', 'Hello', 'Hello', 'Hello', 'Hello', 'Hello']

const List = ({ text }) => (
<div class='mb-2 p-4 rounded odd:bg-sky-300 even:bg-rose-300'>
<h1 class='text-white text-center text-2xl'>{text}</h1>
</div>
)

export default function Com1() {
return (
<div class='w-1/2 mx-auto py-10'>
{arr.map((item) => (
<List text={item} />
))}
</div>
)
}

總之這是拿來讓你搭配渲染用的,跟原本 CSS 寫在父層的概念有點不同,要習慣一下。

動態 class

簡單來說,如果你要動態產生 class 的話:

  • 請務必用全名,不要用字串拼接
  • 請務必用全名,不要用字串拼接
  • 請務必用全名,不要用字串拼接

詳細的原因可以參考官方文件,但簡單來說就是透過字串拼接的 class 沒辦法被編譯出來,下面來舉個例子。

當時我想做一個按鈕元件,並根據 props 的內容來決定背景顏色,所以就寫了這樣的東西:

1
2
3
4
5
6
7
8
9
10
11
12
13
const Button = ({ color, onClick }) => {
const palette = {
purple: 'violet',
green: 'green',
red: 'red'
}
return (
<button
className={`w-5 h-5 rounded-full border-0 bg-${palette[color]}-700`}
onClick={onClick}
></button>
)
}

這裡的想法就是透過字串拼接 bg-{props}-700 來產出想要的 class,但是出來的結果並不如預期。

儘管 class 會輸出正確的字串,例如 props: red -> bg-red-700,可是這時後並不會顯示正確的背景顏色,這個就是用字串拼接時會產生的問題。

如果想動態產生 class 的話,必須用全名來套用,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Button = ({ color, onClick }) => {
// 全名
const palette = {
purple: 'bg-violet-700',
green: 'bg-green-700',
red: 'bg-red-700'
}
return (
<button
className={`w-5 h-5 rounded-full border-0 ${palette[color]}`}
onClick={onClick}
></button>
)
}

多行文字換行

雖然用我之前有寫一篇 CSS 多行文字顯示 … 的效果,但要在 tailwind 上自己實作的話有點麻煩,所以建議改用下面的方式。

詳細可以參考 官方文件 說明,這邊只列方法。

安裝套件:

1
npm install @tailwindcss/line-clamp

設定 config:

1
2
3
4
5
6
7
module.exports = {
// ...
plugins: [
// ...
require('@tailwindcss/line-clamp')
]
}

加上 class:

1
2
3
4
5
6
7
8
9
<p class="line-clamp-3">
Here's a block of text from a blog post that isn't conveniently three lines long like you designed
for originally. It's probably like 6 lines on mobile or even on desktop depending on how you have
things laid out. Truly a big pain in the derriere, and not the sort of thing you expected to be
wasting your time trying to deal with at 4:45pm on a Friday am I right? You've got tickets to
SmackDown and you heard there's gonna be a dark match with that local guy from two towns over that
your cousin went to high school with before the show starts, and you're gonna miss it if you're
not there early.
</p>

如果你把 devtool 打開來看的話就會發現其實跟 CSS 多行文字顯示 … 的效果 寫的內容一樣,只是包裝成 utility 而已。

回到熟悉的 class(@apply)

在 tailwind 裡面如果想要幫每個元素套用同樣的樣式,你可能得這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<button
class="py-2 px-4 bg-blue-500 text-white
font-semibold rounded-lg shadow-md hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-blue-400
focus:ring-opacity-75"
>
click1
</button>

<button
class="py-2 px-4 bg-blue-500 text-white
font-semibold rounded-lg shadow-md hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-blue-400
focus:ring-opacity-75"
>
click2
</button>
... ...

只是套用的話這樣寫是沒差,重點是如果要改呢?我們來看看 官方文件 怎麼做的:

solution

官網還順便吐槽了一下:

If you can quickly edit all of the duplicated class lists simultaneously, there’s no benefit to introducing any additional abstraction.

總之如果你能接受的話就這樣子做就好,但如果真的不行就繼續看下去吧。

tailwind 提供了一個叫做 @apply 的東西,可以讓你把一坨 utilty 包裝成一個 class,用法是長這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
.label {
@apply text-slate-500 mb-2;
}
.label-dark {
@apply text-white mb-2;
}
.input-field {
@apply w-full border-0 text-slate-700 focus:outline-slate-700 p-2 rounded;
}
}

順道一提,你甚至能直接在裡面加上原生的 CSS:

1
2
3
4
5
6
@layer components {
.label {
@apply text-slate-500 mb-2;
background-color: dodgerblue;
}
}

雖然不確定這是不是合法的寫法,不過我覺得還蠻方便的就是了 XD

一次性的屬性值

雖然透過 tailwind.config.js 可以添加一些客製化的項目,但是有時會碰到那種只會出現在「特定一兩個地方」的樣式,這時候你通常就不會想把它加進 theme 的設定中,畢竟用到的場合真的不多?

這時候就可以考慮用 xxx-[value] 的方式來填入「一次性」的值,像這樣:

1
<div class="text- font-body bg-[#bada55] text-[24px] p-[20px]">...</div>

只要這樣子就完成囉,是不是省事很多啊!

除此之外,如果想要帶入「變數」也是 OK 的,不過使用時要加上「屬性」來表明這個變數是用來代表什麼的?例如 color(顏色) 或 length(尺寸)之類的,詳細可以參考 官方文件 的說明,這邊做個簡單的示範:

1
2
3
4
5
6
7
8
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--primary: #f00099;
--large: 24px;
}
1
2
3
4
5
6
7
8
9
10
<div>
<!-- 把變數放在 [] 裡面並指定為 color 這個屬性 -->
<button class="bg-[color:var(--primary)] text-white p-2 rounded">Click Me</button>
<!-- 把變數放在 [] 裡面並指定為 length 這個屬性 -->
<p class="font-bold text-[length:var(--large)]">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Modi alias, hic voluptatem quo unde
reiciendis magni accusamus quos illum corrupti veniam voluptatum voluptas voluptate harum
necessitatibus dolorem cumque nulla saepe.
</p>
</div>

主要就是要記得加上「屬性」就好,不然其實就跟一般的用法差不多。

BreakPoint(斷點)

在寫 CSS 時要做 RWD 的話都會用 @media 來對不同的斷點設定不同樣式,這個在 Tailwind 中也有提供對應的 utility:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [],
presets: [],
darkMode: 'media', // or 'class'
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px'
}
}
}

sm 會對應到 @media (min-width: 640px)md 會對應到 @media (min-width: 768px) 以此類推。

要套用的話只要加上「前綴」就行了:

1
<div class="bg-red-500 sm:bg-green-500 md:bg-sky-500 lg:bg-pink-500 xl:bg-teal-500 "></div>

也是很方便的功能。

Firebase 基礎 CRA-解決 source map 載入問題的 Warning
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×