Tailwind 入門介紹:環境設定、核心概念、運作方式

fin
16 min readSep 10, 2021

--

最近在玩 tailwind,紀錄一下整個使用的過程,以及與團員討論過程中的一些資訊。

開始使用 tailwind — 環境安裝

這裡以 next.js 來當作範例,或是參考官網的安裝過程 https://tailwindcss.tw/docs/guides

先安裝相關套件

$ yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest

產生 tailwind.config.js / postcss.config.js 。如果已經有 postcss config 的話,就不用加 -p。Next.js 內建使用 postcss 所以不需要另外對 postcss 做設定

$ npx tailwindcss init -p

這會產生一個基本的 postcss.config.js

// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

如果你的 postcss.config 設定比較複雜,可以參考這裡

然後我們就可以到 CSS 檔案內把 tailwind 的東西弄進來,先到你的 globals.css (或任何你用的基本 css 檔)加入以下幾行。這幾行的前後仍然可以塞其他 CSS,最後產生出的 CSS 順序不會被更改。

/* ./styles/globals.css *//* 其他在 tailwind css 前的 css 內容 */@tailwind base;
@tailwind components;
@tailwind utilities;
/* 其他在 tailwind css 前的 css 內容 */

並確認 pages/_app.js 有把這個 CSS import 進來

/* ./pages/_app.js */
import '../styles/globals.css'

然後 yarn dev 就可以開始開發了,試著在頁面上加入一些 tailwind class 玩玩看吧。

開發環境

為了開發方便,我們還需要做一些編輯器上的調整,這裡以 VSCode 為主,其他請見官網

  • stylelint 調整為支援 tailwind
  • tailwind 相關的語法高光
  • 自動完成 tailwind utility class

先安裝 stylelint 相關套件

$ yarn add -D stylelint stylelint-config-recommended

然後修改 .stylelinrc 檔案,讓 stylelint 忽略 tailwind 的特殊語法 @tailwind @apply @layer

{
"extends": "stylelint-config-recommended",
"rules": {
"at-rule-no-unknown": [ true, {
"ignoreAtRules": [
"extends",
"tailwind",
"apply",
"layer",
"variants",
"responsive",
"screen"
]
}],
"block-no-empty": null
}
}

最後到編輯器內,安裝三個延伸模組

Tailwind CSS IntelliSense

再來是進到 VSCode 的使用者設定或工作區設定,修改以下三個設定。因為 VSCode 內建 css/scss validator 會將 @tailwind @layer 視為錯誤,因此需要關閉,改用 stylelint。

"css.validate": false, // Disable css built-in lint
"scss.validate": false, // Disable scss lint (optional if using scss)
"stylelint.enable": true, // Enable sytlelint

支援 tailwind 的開發環境到這裡就設定完成。

核心概念

使用 Tailwind 的時候必須要理解的核心概念,這裡講的大部分官網都有,如有不清楚的請以官網為準

Utility-First

用各式各樣 tailwind 提供的 utility class 來組出複雜的元件樣式,這些 utility class 就如同樂高積木一樣,提供基本的零組件,讓你建構出複雜的架構,這其實也很類似 functional programming 的概念。

那麼,這跟 inline style 有什麼不一樣?

  • inline style 無法做到 responsive / hover / click 等狀態,這一定要寫在 css 檔內
  • 透過 utility class 限制了使用數值,可以更輕易的與 design system 做搭配,後續要調整也會比較快。比如下圖是 border radius 的可用 class,後面的屬性可以透過設定檔做修改,一次套用到所有使用相對應 class 的地方

另外一個比較常討論的是可維護性,因為在元件上原本可能只需要幾個 class 一下子變成了十幾二十個,看起來應該不容易維護。這可以透過兩種方式解決

元件化

元件化的重點在於透過模板減少重複的程式碼,因此只要能夠好好的規劃,同樣重複出現的區塊都使用同一個模板,那麼要改樣式時不管需要調整幾個 class 也都只需要調整相對應的元件即可

@apply

一個好維護的專案必定會有足夠層度的元件化,當元件一多複雜度增加時,不可避免有一些 utitilty 的組合可能會在不同元件中需要共用。比如我們希望可點擊區域行為一致,顏色、背景色、hover 狀態都相同,可點擊區域可能是 button ,也有可能是 tab 或其他元件。如果不用 @apply 的話勢必得在每個元件上重複寫同樣的幾個 class,也導致未來可維護性降低因為修改時有可能會遺漏,因而偏離原本希望的行為一致的目標。

使用 @apply 其實就像是在寫一般我們常寫的,屬性比較複雜的 class,只是這個 class 的內容是由其他的 utility class 組起來。這裡要記得,寫這樣的 class 是為了方便共用,如果只有一個地方要用這樣的組合的話,我會選擇不使用 @apply 而是直接把相關的 utility class 寫在元件上,多一層 @apply 反而會造成閱讀上的負擔

透過 @apply 來產生複雜 class 還有個好處,因為底層都是 utility class,如果相關的設定調整了(比如以下範例的 font-semibold 的 weight),那麼上層是不需要做任何變動也會跟著調整。

<!-- 都是點擊區域 -->
<Button class="py-2 px-4 rounded-lg shadow-md font-semibold text-white bg-green-500 hover:bg-green-700">
按我一下
</Button>
<Dropdown class="py-1 px-3 font-semibold text-white bg-green-500 hover:bg-green-700" items={items} /><!-- 用 @apply 提取 Class -->
<Button class="py-2 px-4 rounded-lg shadow-md clickable">
按鈕
</Button>
<Dropdown class="py-1 px-3 clickable" items={items} /><style>
.clickable {
@apply font-semibold text-white bg-green-500 hover:bg-green-700;
}
</style>

RWD (responsive)

預設有五個斷點,可以由設定檔去調整這些斷點或者新增刪除斷點:

使用方式: class 加上斷點的前綴,再加上 : (這也可以透過設定檔修改)以及 utility class 名,就可以讓該 utility class 在某個斷點以上的解析度生效,如下圖預設 w-16 ,但在 md (768px 以上) 時會被 md:w-32 蓋掉,在 lg (1024px 以上) 會使用 lg:w-48 。使用時要注意,斷點順序預設就是上圖的順序,越下面的會蓋過前面的。

<!-- 寬度預設值設為 16, 中等螢幕設為 32, 大螢幕設為 48 -->
<img class="w-16 md:w-32 lg:w-48" src="...">

那如果只想要在某個範圍有特殊的樣式呢?比如上述例子做個修改,我只想要 md ~ lg 之間是 w-48 而其他時候 w-16 。只需要在更大的斷點上再把相對應的 class 套上去,藉此來蓋過前一個 class 的效果。

<!-- 寬度預設值設為 16, 中等螢幕設為 48, 大螢幕以上回到 16 -->
<img class="w-16 md:w-48 lg:w-16" src="...">

@screen

@screen 指令讓你依照斷點名稱建立相關的媒體查詢。

舉例來說,你想要有一個 640pxsm 斷點,並且你還需要撰寫一些自定義的 CSS 給這一個斷點。斷點名稱與上述圖表一樣,日後如果要調整可以透過設定定義 sm 的尺寸,或是直接搜尋 sm 一次替換成別的斷點名稱。

/* 原來的寫法 */
@media (min-width: 640px) {
/* ... */
}
/* 新的寫法 */
@screen sm {
/* ... */
}

元素狀態 (variants)

如果需要讓元素在不同狀態下有不同的樣式,就需要了解 variants,前述的 responsive 也是一種特殊的 variant。先看看 variant 的列表:

大部分都與 CSS 的 psuedo-class 有對應關係,比如 hover 就對應到 CSS 的 :hover

下列表單使用了 focus hover 兩種 variant,input 元素被 focus 時會產生 ring-2 的外圈,而 button hover 時則會變色 hover:bg-cyan-700到這裡試玩

<form class="...">
<input class="border border-transparent bg-white text-gray-700 placeholder-gray-400 shadow-md text-base focus:outline-none focus:ring-2 focus:ring-cyan-600 focus:border-transparent ..." ...>
<button class="hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2 focus:ring-offset-cyan-200 ..." type="button">
Sign up
</button>
</form>

用這些 variant 可以組合出許多的變化,但組合時必須要注意,跟 responsive 一樣,這些 variant 是有優先順序的,比如說,元素的狀態同時 focus 又 hover,且兩邊都定義了不同的背景顏色,那麼會呈現哪個?這時可以參考 預設的變化模式順序 ,排後面的代表優先權比較高會蓋過前面的,或者是透過設定檔自己改變順序,當然也可以自己新增 variant 。

variant 可以組合,其中 responsive 一定是第一個,dark 第二個(下一章節會介紹),後面接其他 variant,結構:

${responsive}:${dark}:${variants}:${utility-name}

一般 variant 只能選一個,所以才會有前面 focus 又 hover 需要處理排序的問題,但如果用的是 JIT mode 則可以使用多層 variant 像 sm:hover:active:disabled:opacity-75 這樣的 class。關於 JIT mode 後面會再介紹,在這裡先記得非 JIT 只能 responsive + 1 variant,而 JIT 沒有這個限制。

深色模式 Dark Mode

針對深色模式的支援,預設情況下, 只有 backgroundColor, borderColor, gradientColorStops, placeholderColor, and textColor 這幾個 utility class 有支援,如有其他需要要自己在設定新增。使用上:

<button class="lg:hover:bg-gray-800 lg:dark:hover:bg-white ...">
<!-- ... -->
</button>

深色模式的切換可以依據頁面 html 是否有 dark 這個 class 或者是瀏覽器的 prefers-color-scheme 設定,這可以在設定檔中修改:

//tailwind.config.js
module.exports = {
darkMode: 'class' | 'media',
// ...
}

class 這個選項會比較彈性些,就可以搭配下面這個簡單的 script,做到手動切換 淺色模式深色模式,和 根據作業系統的偏好設定

// 最好在頁面讀取或更換主題的時候增加一行到 `head`來避免 FOUC
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
// 當使用者選擇 淺色模式 時
localStorage.theme = 'light'
// 當使用者選擇 深色模式 時
localStorage.theme = 'dark'
// 當使用者選擇 根據作業系統偏好設定 時
localStorage.removeItem('theme')

補充:其實 dark 可以看成是另一種主題 (theme) 因此我們可以透過類似的做法來延伸做出多個主題,詳情可以看這裡

基底樣式

現有專案導入 tailwind 的可能特別需要注意這塊,基底樣式是 tailwind 預設會引入的,影響全域的樣式。Utility class 因為有使用才會有效果,所以對現有專案的影響不大。這裡可以看到預設基底樣式 preflight 的設定,不喜歡的話也可以從設定中移除。

當然,要自訂基底樣式也是可以的

@tailwind base;@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
h3 {
@apply text-lg;
}
a {
@apply text-blue-600 underline;
}
}
@tailwind components;
@tailwind utilities;

運作機制

核心概念都了解後,先來講 tailwind 的 css 運作機制,關於運作機制可以分成兩塊來看,引入生成

引入

以 next.js 為例:

  • css 的引入點在 pages/_app.js ,從這檔案引入了 globals.css
  • 引入的 globals.css 會被 PostCSS 處理
  • PostCSS 內的 tailwind plugin 看到關鍵字 @tailwind 即會把 base component utility 三層各自的 css 產生出來,或是看到其他關鍵字如 @layer @screen 等進行相對應的 CSS 生成動作,生成的過程參考下一段落。如想更深入的了解底層的轉換機制,可以看看這篇 ,或是找 tailwind 的原始碼來參考。
  • 頁面上即有了相對應的 CSS

生成

生成即是把原始的 CSS 變成給瀏覽器用的 CSS ,這部分分成一般開發模式、生產模式、以及 JIT 模式

  • 一般開發模式:會引入已經預先建置好的 tailwind.css ,裡面包含了各種 utility class 的組合,所以很大一包,但在開發模式時並無影響,就由開發者自由取用。
  • 生產模式:開發模式用的 tailwind.css 的未壓縮大小是 3.5 MB ,明顯不適合生產環境使用,因此上到生產模式時會經過一層 purge 的關卡,把沒有使用到的 class 全部清理乾淨,這裡的清理標準是透過正則表達式,去掃描對應的模板檔案 (jsx, html 等)。因此不建議動態生成 utility class 否則在清理過程時會無法辨認有使用到哪些 utility class 造成要用到的 class 不會出現在生產模式。另外針對 html tag 的樣式不會被清除,如有特別需求比如白名單之類,則可以在設定中設定。
  • JIT 模式:動態依據頁面上有的 class 去產生出相對應的 class,相較原有模式多了許多彈性,生產模式的建置速度也比較快(其實 JIT 開發模式就等於生產模式),如前述的可以多個 variant 相疊。不過目前尚處於實驗階段,可以玩玩但需要隨時注意規格上的改動。

RAILS?

Rails 6 之後可以使用 DHH (Rails 作者) 做的 https://github.com/rails/tailwindcss-rails ,5 之前就要自己想辦法,https://github.com/rails/tailwindcss-rails/issues/50

附上一個 migration guide https://www.honeybadger.io/blog/tailwind-css-rails/

參考資料

--

--