第 8 章 自定义 Tailwind
在本书中,我多次提到 Tailwind 是可以自定义的,本章将详细说明如何以及为什么要进行自定义。 Tailwind 是一个根据你代码中的模式生成大量 CSS 类的引擎,这个引擎提供了很多钩子(hooks),让我们可以修改可用的工具类集合。
你可能想自定义 Tailwind,有几个常见原因:
修改默认值 Tailwind 为大多数工具类提供了默认的步进值,例如 margin、padding 以及其他间距类,还有响应式断点的默认屏幕尺寸。 Tailwind 也提供了一套默认颜色,但你可能希望添加自己的颜色。 在配置文件中可以修改这些默认值。 虽然你可以在需要的地方使用任意值,但如果把常用的一些值设为默认,会更方便管理,也更省事。
修改生成的类集合 Tailwind 会生成大量 CSS 类。即便它是根据你的代码生成的,你仍然可能希望显式禁止某些类生成,或者确保其他类被生成。
添加新功能 虽然你可以在普通 CSS 中自己写扩展,但你也可以通过 Tailwind 配置添加插件,这样更方便共享和与 Tailwind 其他行为整合。
与已有 CSS 集成 如果你在一个已有大量 CSS 的网站上开始使用 Tailwind,Tailwind 提供了配置选项,保证其工具类不会与现有 CSS 或 HTML 模板工具冲突。
接下来,我们来看如何根据自己的需求自定义 Tailwind,从配置文件开始。
配置文件基础
Tailwind 的配置文件是可选生成的,在安装 Tailwind 时可以创建。 你也可以在安装 Tailwind npm 包后随时用命令生成它:
npx tailwindcss init生成的最小配置文件如下:
module.exports = {
content: [],
theme: {
extend: {},
},
plugins: [],
}如果你希望得到包含完整默认配置的文件,可以用:
npx tailwindcss init --full大多数自定义内容都会放在 theme 部分。
Tailwind 将每类工具视为核心插件(core plugin), 可以在 Tailwind 文档中查看完整列表。 theme 对象会引用这些核心插件的名称,让你可以自定义核心插件,大多数核心插件都提供了可自定义的选项。
修改默认值
在配置文件中,theme 对象的 键 是每个核心插件的名称, 值 是该插件的配置选项。
有三个特殊配置选项:screens、colors 和 spacing, 它们本身不是核心插件,但作为许多其他核心插件配置的基础。
需要注意的是,Tailwind 中的 “theme” 用法与其他地方可能不同:
- 其他地方的 “theme” 通常指一套颜色方案,比如“深色主题”或“浅色主题”。
- 在 Tailwind 中,
theme指整个默认配置集合,而且只有一套。 - 如果想切换颜色方案,需要使用 CSS 变量或
dark:修饰符来指定暗模式下的行为。 - 可以在 GitHub 上查看完整的默认
theme(在 Tailwind 主分支,可能比已发布版本稍新)。
你可以通过两种方式自定义 theme:
- 覆盖整个选项(override)
- 扩展选项(extend)
要覆盖默认值,你可以在 theme 对象中为整个选项(核心插件或特殊值)提供一套新的值。 例如,下面覆盖了整个屏幕断点集合:
theme: {
screens: {
phone: '640px',
landscape: '768px',
tablet: '1024px',
laptop: '1280px',
}
}这种方式会完全替换默认值。
如果你希望保留默认值,同时在此基础上添加新的值,可以使用 theme.extend。 例如,在默认断点之外添加一个超大屏幕断点:
theme: {
extend: {
screens: {
'3xl': '2440px',
}
}
}屏幕宽度
theme.screens 对象用于生成响应式修饰符的断点。 默认配置如下:
module.exports = {
theme: {
screens: {
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
}
}
}你可以以多种方式修改这些值,但需要注意:
- 如果你修改
screens,必须提供完整的尺寸范围(覆盖所有默认断点)。 - 如果只想添加一个新断点,应使用
extend:
module.exports = {
theme: {
extend: {
screens: {
'3xl': '2440px',
}
}
}
}这些断点是合理的默认值,但你也可以调整具体数值。 此外,你还可以修改键名,例如改成 phone、landscape、tablet 和 desktop。 这些键名会成为新的修饰符名称,比如不再写 sm:m-0,而写 phone:m-0。
屏幕宽度的高级配置
字符串值作为 min-width 如果你给断点键的值是字符串,Tailwind 会将其视为该断点的最小宽度(min-width)来生成 CSS。
对象形式指定 min 和 max 你也可以传一个对象
{min, max}来指定断点范围。- 仅指定
max时,响应式行为会反转:未加修饰符的工具类应用于最大屏幕,修饰符在屏幕变小的时候生效。
- 仅指定
module.exports = {
theme: {
screens: {
'2xl': { max: '9999px' },
'xl': { max: '1535px' },
'lg': { max: '1023px' },
'md': { max: '767px' },
'sm': { max: '639px' },
}
}
}指定 min 和 max 给对象加上
min值,可以将每个断点限制在一个特定范围内,这时你需要在每个断点完整指定所有属性。基于其他条件的媒体查询 媒体查询不仅可以基于屏幕尺寸,还可以基于其他条件。 使用
raw选项可以添加自定义条件,例如打印模式:
module.exports = {
theme: {
extend: {
screens: { 'print': { raw: 'print' } },
}
}
}然后就可以像使用其他屏幕断点一样使用它:
class="print:bg-white"默认颜色
Tailwind 有一组通用的颜色,这些颜色会作为许多工具类(比如 text-、bg- 等)的后缀使用。 Tailwind 一共提供了 22 种颜色。 如果你想修改这组颜色,可以通过一个叫 colors 的对象来访问它们。
module.exports = {
theme: {
colors: {
gray: colors.warmGray,
red: colors.red,
green: colors.green,
}
}
}完整的颜色列表可以在 Tailwind 的文档中找到。
虽然你可以在 theme#colors 中完全替换整套颜色,但更常见的做法是通过 theme#extend#colors 来添加你自己的额外颜色,比如这样:
module.exports = {
theme: {
extend: {
colors: {
"company-orange": "#ff5715",
"company-dark-blue": "#323C64",
"company-gray": "#DADADA",
}
}
}
}现在你就可以使用 text-company-orange 或 bg-company-gray 了。
你也可以用同一个 colors 对象作为值,添加新的颜色系列到任意键下。
另外,你还可以通过嵌套颜色来避免重复定义:
module.exports = {
theme: {
extend: {
colors: {
"company": {
"orange": "#ff5715",
"dark-blue": "#323C64",
"gray": " #DADADA",
}
}
}
}
}生成的类名依然和未嵌套时一样,比如 text-company-orange。 如果你只想要 text-company 这样的类名,那么可以使用 default 作为键,它会代表没有后缀的那个值。
如果你在扩展颜色时使用了一个已经存在的颜色,比如定义了 red: { '100': "#WHATEVER" },那就会用你新的这一组替换掉原本的红色系列。
如果你想在已有颜色的基础上增加一个新的层级,可以使用展开运算符(spread operator):
module.exports = {
theme: {
extend: {
colors: {
"red": {
...colors.red,
"450": " #CC0000",
}
}
}
}
}但如果我真的想要“颜色主题”怎么办?
在标准的 Tailwind 中,最接近颜色主题的做法就是使用 dark: 修饰符。
要启用 dark: 修饰符,需要在 Tailwind 的配置文件中加上这一行:
module.exports = {
darkMode: "media",
}有了这个设置之后,你就可以使用 dark: 来定义当浏览器处于深色模式时的样式变化。 例如,可以写成这样:
class="bg-gray-100 dark:bg-gray-900 text-gray-700 dark:text-gray-100"这样在浅色模式下会使用灰色背景,而在深色模式下则会自动切换为深灰背景。
dark: 修饰符还可以和其他修饰符一起使用,比如 hover:,或者响应式修饰符如 sm:。
当 darkMode 设置为 "media" 时,Tailwind 会根据浏览器的 prefers-color-scheme 媒体查询自动判断深浅模式。 如果你想自己控制模式,可以把 darkMode 设置为 "class",然后在任意元素上手动添加 dark 类。 通常会把它加在 DOM 树的最顶层(比如 <body>),并用 JavaScript 来切换整棵树的模式,例如:
<body class="container mx-auto py-12 px-6 dark">
<div class="bg-gray-100 dark:bg-black">
</div>
</body>如果你想实现更复杂的自定义主题,可以配合 CSS 变量 来做。 关于这方面,dev.to 上有不错的介绍。
间距(Spacing)
用于内边距(padding)、外边距(margin)、宽度、高度等属性的间距值,也可以通过 theme#spacing 来重写,或者通过 theme#extend#spacing 来扩展。
比如,你可以直接这样替换默认的间距设置:
module.exports = {
theme: {
spacing: {
'small': '4px',
'medium': '12px',
'large': '36px'
}
}
}这些新的后缀会应用在所有使用间距的地方,因此你现在可以使用像 p-small、h-medium、gap-large 这样的类名,或者其他类似的组合。
如果你喜欢现有的间距比例,但只是想要更多选项,那就用 extend 来扩展即可。
module.exports = {
theme: {
extend: {
spacing: {
'15': '60rem',
'17': '76rem'
}
}
}
}其他核心插件
几乎每个 Tailwind 的工具类都有一套基于相同模式的后缀系列。 而且几乎所有这些工具类都可以像修改间距(spacing)和颜色(colors)那样,通过相同的方式进行重写或扩展。
这里举一个例子,因为要讲完所有插件已经超出了本书的范围。 在 Tailwind 文档中,每个核心插件的页面都会说明如何修改该插件。
比如,你可以在配置文件中通过 theme#extend 添加不同的 z-index 值:
module.exports = {
theme: {
extend: {
zIndex: {
"-1": "-1",
"-5": "-5",
"-1000": "-1000"
}
}
}
}注意,在这种情况下,Tailwind 会按照与负外边距(negative margins)相同的模式生成负值类名, 所以这里对应的负类名是 -z-1、-z-5 和 -z-1000。
如果我想要自己定义一整套完整的 z-index 选项,那就不该使用 extend:。
module.exports = {
theme: {
zIndex: {
"1": "1",
"5": "5",
"1000": "1000"
"-1": "-1",
"-5": "-5",
"-1000": "-1000"
}
}
}现在我同时拥有 z-1、z-5、z-100 以及对应的 -z-1、-z-5、-z-1000, 但原本默认生成的类名就不会再出现了。
所有带有多个可选值的核心插件,都支持类似的方式来替换或扩展这些选项。 同样,完整的说明都可以在 Tailwind 的文档中找到。
修改生成的类名
通常情况下,Tailwind 编译器只会为你应用中实际使用到的类名生成 CSS。 不过,有些情况下,你可能想要修改这个配置。
首先,看一下编译器是如何工作的。 配置文件里有一个 content 键,它应该包含项目中可能引用 Tailwind 工具类的文件模式列表。 这包括你的静态 .html 文件,也包括 React 项目的 .jsx 文件,或者 Rails 项目的 .erb 文件。 甚至还包括那些可能被模板文件调用、间接使用 Tailwind 工具类的其他文件。 (注意,不要把其他 .css 文件包括进来——你要列出使用 Tailwind 类的文件,而不是定义 Tailwind 类的文件。)
文件模式使用的是 fast-glob 库,语法如下:
*匹配除目录标记之外的任意文本**匹配任意文本,包括目录标记(可以匹配任意子目录)- 花括号和逗号
{}用来表示可选项,例如*.{html,erb}
如果你使用了前面第 12 页“重复定义”中描述的策略,在 JavaScript、Ruby 或其他语言里定义 Tailwind 类列表,那么这些文件也需要列在配置文件的 content 字段中。
例如,在一个 React 项目中,你可能会得到这样的配置:
module.exports = {
content: [
"./src/**/*.{html, jsx, tsx}",
],
}一个 Rails 项目可能看起来像这样:
module.exports = {
content: [
"./app/views/**/*.{html, erb}",
"./app/helpers/**/*.rb"
],
}这里需要稍微注意一下: 如果某些类所在的文件没有被列在 content 中,那么这些类对应的 CSS 就不会被生成,看起来就像完全不起作用一样。 (在 Rails 项目中,我就遇到过这种情况——CSS 类被定义在 config/initializers 下的某个一次性初始化文件里,却没被 content 包含。)
另一方面,虽然包含非前端文件的成本很小,但如果把整个 node_modules 目录都包含进来,成本就很高——不仅命令行工具运行时间会变长,还会生成大量不必要的 CSS。
Tailwind 使用的机制设计得很简单。它会把相关文件拆分成单个单词,然后和 Tailwind 的模式列表进行匹配,以判断哪些单词可能是需要生成 CSS 的 Tailwind 工具类。
这个过程偏向保守,可能会把一些实际上不是类名的单词也捕捉到,但这通常没问题,而且比在通用情况下去猜测哪个是真正的 CSS 类、哪个只是 JavaScript 代码或注释,要容易一百万倍。
它无法捕捉到通过字符串拼接动态生成的类名。
在第 3 章“排版”(Typography,第 17 页),我们看过这样一个用于创建悬停效果的例子:
const hoverDarker = (color) => {
return `text-${color}-300 hover:text-${color}-700`
}这个函数因为动态生成了类名,会导致 Tailwind 找不到这些类名,所以你要么需要在其他地方使用这些类名,要么就得用别的方法来实现这个功能。
一种解决办法是使用 safelist。Tailwind 提供了一个配置项叫 safelist,允许你指定希望一定生成的 Tailwind 类。 safelist 中的每一项可以是字符串,也可以是 JavaScript 正则表达式,不过有一些限制:
module.exports = {
content: [
"./app/views/**/*.{html, erb}",
"./app/helpers/**/*.rb"
],
safelist: [
"bg-red-300",
"bg-red-700",
{
pattern: /bg-(gray|slate|zinc|neutral|stone)-(300|700)/,
variants: ["hover"]
}
]
}这个配置文件单独将两个背景色加入了 safelist,然后用正则表达式匹配了所有灰度颜色。 正则表达式的模式必须以 Tailwind 工具类开头。 例如,你不能用 +.-gray-+. 来匹配所有灰色的使用情况,但你可以用 bg-red-+.\+. 来匹配所有红色背景及其透明度。 我也认为你不能用 bo+. 来匹配 box 和 border(至少,这可能是个不好的做法)。
在 safelist 的模式里,你无法匹配修饰符(modifiers),但可以通过在配置中将 html 包含在变体列表中,确保这些修饰符会被生成。
你还可以通过向配置的 corePlugins 键传入一个对象,来创建核心插件的黑名单。 这个对象的键是你想禁用的核心插件名称,值全部设置为 false。 通常只有在你确实想屏蔽某些正在使用的类时才会这么做:
module.exports = {
corePlugins: {
flex: false,
flexDirection: false,
flexGrow: false,
flexShrink: false,
flexWrap: false,
}
}这个配置会移除所有与 Flexbox 相关的工具类,不过我并不推荐这样做,因为 Flexbox 非常实用。
变体修饰符(Variant Modifiers)
我们在 Tailwind 中见过像 hover 和 focus 这样的变体修饰符,但实际上 Tailwind 定义了几十种修饰符,并且命令行工具只会为你实际使用到的修饰符组合生成 CSS。
除了屏幕尺寸修饰符 sm、md、lg、xl 和 2xl 外,下面是 Tailwind 修饰符的部分列表:
- active:元素处于激活状态时应用。
- dark:如果 Tailwind 判断处于深色模式时应用。
- first-letter:应用于文本的首字母。
- first-line:应用于文本的第一行。
- hover:用户将鼠标悬停在元素上时应用。
- landscape:设备处于横向模式时应用。
- ltr:文本从左向右排列时应用。
- marker:应用于列表标记。
- motion-reduce:用户在系统中启用了“减少动画”时应用。通常与
hover一起使用,经常同时存在motion-reduce和motion-safe两个变体。 - motion-safe:用户未启用“减少动画”时应用。通常与
hover一起使用,经常同时存在motion-reduce和motion-safe两个变体。 - portrait:设备处于纵向模式时应用。
- print:打印媒体类型下应用。
- rtl:文本从右向左排列时应用。
- selection:用户选中的文本应用。
- target:元素 ID 与当前 URL 匹配时应用。
- visited:链接已访问时应用。
有两种“组”属性是通过给父元素添加 group 类来工作的。 这些变体属性在父元素下的任意子元素被触发时生效,而不仅仅是针对当前元素:
- group-focus:当父元素下的任意子元素获得焦点时,应用到所有子元素。
- group-hover:当父元素被鼠标悬停时,应用到所有子元素。默认情况下,只要启用了
hover,这个属性就可用。
还有一些属性是根据元素在父元素中的顺序来应用的。这类变体需要加在子元素上,而不是父元素上,如果你的模板语言生成了一个完整的循环,这些属性尤其有用:
- empty:当元素没有子元素时应用。
- even:当元素是偶数位置的子元素(第二、第四、第六……)时应用。
- first:当元素是父元素的第一个(最上方)子元素时应用。也有 first-of-type。
- last:当元素是父元素的最后一个(最下方)子元素时应用。也有 last-of-type。
- odd:当元素是奇数位置的子元素(第一、第三、第五……)时应用。
- only:当元素是唯一子元素时应用,也有 only-of-type。
还有一些属性是针对表单元素的:
- autofill:当输入框被浏览器自动填充时应用。
- checked:当复选框或单选按钮被选中时应用。
- default:当元素仍保持默认值时应用。
- disabled:当元素被禁用时应用。
- file:应用于文件输入的按钮。
- focus:当元素获得焦点时应用,例如文本框。
- focus-within:当父元素内的任意子元素获得焦点时应用到父元素。
- indeterminate:当复选框或单选按钮处于不确定状态时应用。
- in-range:当元素的值在允许范围内时应用,例如数字输入框。
- invalid:当元素的值无效时应用。
- out-of-range:当元素的值超出允许范围时应用,例如数字输入框。
- placeholder:应用于占位符文本。
- placeholder-shown:当占位符文本仍显示时应用。
- read-only:当元素为只读时应用。
- required:当元素为必填时应用。
Tailwind 还支持 before 和 after 来匹配 ::before 和 ::after 伪类。
与现有 CSS 集成
如果你在项目中同时使用 Tailwind 和大量已有的 CSS,一个可能遇到的问题是 类名冲突。 你的现有 CSS 可能已经定义了 hidden、flex-grow,或者(虽然不太可能)mx-64。
Tailwind 提供了一种解决办法——你可以给所有 Tailwind 工具类加一个统一前缀:prefix: "<SOMETHING>"。 比如,你设置 prefix: "twind",那么所有 Tailwind 工具类都会变成:twind-hidden、twind-flex-grow,甚至 twind-mx-64。 如果有修饰符,依然会正常附加,例如 hover:twind-text-black。
另一个问题是,你的现有 CSS 可能设置了很高的 选择器优先级,导致覆盖了所有 Tailwind 工具类。 解决方法是配置 important: true,Tailwind 会在生成的所有工具类里加上 !important,这样它们的优先级就会高于已有 CSS。 不过,如果你同时使用了很多不同的 CSS 库,这可能会带来意想不到的副作用,所以要谨慎使用。
有些模板工具不允许在类名中使用冒号 :,这会让 Tailwind 的类名前缀失效。 你可以通过 separator: 选项自定义分隔符,例如 separator: "--",前缀类名就会变成 hover--text-black 或 lg--m0-4。(我个人觉得这种写法比冒号好看一些。)
从 JavaScript 访问 Tailwind
你可以在 JavaScript 中访问 Tailwind 的配置。这在你想在 JS 框架里创建动态行为时非常有用。 例如,你可能有自定义动画,需要遵循现有的颜色或间距,或者其他未知需求。
无论你想做什么,Tailwind 都提供了一个 resolveConfig 方法,它接受 Tailwind 配置对象作为参数,并允许你查询完整配置——不仅仅是你在文件中覆盖的部分:
import resolveConfig from 'tailwindcss/resolveConfig'
import myConfig from './tailwind.config.js'
const tailwindConfig = resolveConfig(myConfig)
tailwindConfig.theme.colorsresolveConfig 返回的对象会将你的配置覆盖与默认配置合并,提供一个可以查询的完整对象。
插件(Plugins)
Tailwind 插件本质上就是一些 JavaScript 代码片段,你可以通过它们向 Tailwind 应用中添加自定义类、模式和前缀。 它们非常简单,以至于可以直接内联写在 Tailwind 配置文件里。
首先,你需要在 tailwind.config.js 文件顶部引入插件函数:
const plugin = require("tailwindcss/plugin")然后,在配置文件内部,你可以调用这个插件函数。 传给 plugin 的参数是一个函数:
const plugin = require("taiwindcss/plugin")
module.exports = {
content: ["./html/*.html"],
theme: {
extend: {},
},
plugins: [
plugin(({}) => {
})
],
};这个匿名函数接受一个参数,前面示例中写作一个空对象 {}。 实际上传入函数的对象包含多个参数,每个参数都是一个辅助函数,你可以在匿名函数体内调用这些辅助函数来添加 Tailwind 功能。 通常,你会使用 JavaScript 的解构语法,只取出你想用的辅助函数。
例如,如果你想添加自定义的变体前缀,其中一个辅助函数是 addVariant(),可以这样使用(下面示例增加了一些额外的序数):
const plugin = require("taiwindcss/plugin")
module.exports = {
content: ["./html/*.html"],
theme: {
extend: {},
},
plugins: [
plugin(({ addVariant }) => {
addVariant("second-of-type", "&:nth-of-type(2)")
addVariant("third-of-type", "&:nth-of-type(3)")
})
],
};addVariant 方法接受两个参数:
- 你想添加的修饰符(modifier)
- 它应该转换成的 CSS 伪类或媒体类型
类似地,Tailwind 还提供了几个辅助方法:addUtilities、addComponents 和 addBase。
addUtilities:接受两个参数
- 你想添加的新 Tailwind 工具类的名称
- 一个 JavaScript 对象,定义该工具类对应的 CSS 属性,例如:
jsaddUtilities(".big-bold-text", { fontSize: "1.5rem", fontWeight: "700" })addComponents:用法类似,但将样式放在 Tailwind 的组件层(components layer)。
addBase:向 Tailwind 的基础层(base layer)添加新样式,第一个参数是 HTML 选择器(如
h4),而不是 CSS 类名。
另外,还有 matchUtilities 和 matchComponents 方法,允许你定义一组动态匹配器。 这两个方法都可以使用辅助函数 theme 来查找当前主题的值,从而确定动态匹配器的选项。例如 theme("spacing") 会返回所有间距选项。
完整文档可见:https://tailwindcss.com/docs/plugins
完结
至此,我们的 Tailwind 之旅也告一段落。 Tailwind 更新频繁,所以你可以关注 Tailwind 官方博客 https://blog.tailwindcss.com 获取最新变化。 同时,Tailwind 文档 https://tailwindcss.com 中也提供了大量精彩的教学视频、示例组件以及其他资源。
现在,去设计一些出色的作品吧!