在本书中,我一直暗指 Tailwind 是可定制的,这里我将解释如何以及为什么。Tailwind 是一个引擎,它根据你代码中的模式生成大量的 CSS 类,这个引擎有很多钩子,允许我们改变可用的工具集。
你可能想定制 Tailwind,有几个原因:
- 改变默认值:Tailwind 为其大多数工具提供了默认的步骤值,例如,边距、填充和其他间距项目的常见步骤。而且它为响应式断点提供了默认的屏幕尺寸。Tailwind 还提供了一套默认的颜色,但是你可能想添加自己的颜色。在配置文件中,你可以改变这些。即使你可以在可以插入数值的地方使用任意的数值,但如果你把它们变成默认值,常用的一次性数值更容易管理,而且输入时间更短。
- 改变类的集合:Tailwind 生成了大量的 CSS 类。而且,即使它根据你自己的代码生成类,你可能仍然想明确地阻止某些类的生成,或者确保其他类的生成。
- 添加新的行为:尽管你可以在常规的 CSS 中编写你自己的扩展,但你也可以将新的项目作为插件添加到 Tailwind 配置的一部分,这可以使它们更容易分享,并与其他 Tailwind 行为整合。
- 与传统的 CSS 整合:你可能想在一个已经有很多 CSS 的网站上开始使用 Tailwind。Tailwind 提供了配置选项,使您能够确保 Tailwind 工具不会与现有的 CSS 或您的 HTML 模板工具的限制相冲突。
让我们来看看如何按照你的喜好定制 Tailwind,从配置文件开始。
配置文件基础
该配置文件是作为你的 Tailwind 安装的一部分而选择性地生成的。你也可以在你用 npx tailwindcss init
命令安装 Tailwind npm 包后的任何时候创建它。最小的配置文件看起来像这样:
module.exports = {
content: [],
theme: {
extend: {},
},
plugins: [],
}
如果出于某种原因,你想要一个明确列出整个默认配置的配置文件,你可以用 npx tailwindcss init --full
得到它。我们要看的大部分内容都将放在主题部分。
Tailwind 认为每一个实用程序系列都是一个核心插件;你可以在 Tailwind 文档中看到一个完整的列表。主题对象引用这些核心插件的名称,以允许你定制核心插件 —— 大多数核心插件都有定制选项。
改变默认值
在配置文件中,主题对象的键是每个核心插件的名称,值是该插件的配置选项。三个特殊的配置选项,屏幕、颜色和间距,本身并不是核心插件,但却是许多其他核心插件的配置基础。
这里值得一提的是,Tailwind 对 “主题” 一词的使用与你可能在其他地方看到的使用方式不同,“主题” 指的是一组颜色,如 “深色主题” 与 “浅色主题”。对于 Tailwind 来说,主题是整套的默认值,而且只有一个。如果你想改变颜色方案,你需要使用 CSS 变量或者使用 dark:
修改器来指定在黑暗模式下的行为。(你可以在 GitHub 上看到整个默认主题。这是 Tailwind 主分支中的主题,所以它可能略微领先于发布的版本)。
你可以通过两种方式定制主题:(1)覆盖整个选项或(2)扩展选项。
要覆盖,你要为整个对象提供一组新的值 —— 要么是核心插件,要么是主题对象中的一个特殊值。这个例子改变了整个屏幕断点的集合。通过这种方式覆盖主题对象,完全取代了默认值:
theme: {
screens: {
'phone': '640px',
'landscape': '768px',
'tablet': '1024px',
'laptop': '1280px',
}
}
如果你想保留现有的默认值,但在上面添加新的值,你可以使用 theme#extend
。这种配置会增加一个额外的、超宽的屏幕断点:
theme: {
extend: {
screens: {
'3xl': '2440px',
}
}
}
屏幕宽度
键内的屏幕对象产生了用于响应式修改器的断点。默认情况下是这样的:
module.exports = {
theme: {
screens: {
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
}
}
}
你可以用很多方法来修改,但要注意,如果你修改这里的数值,你需要提供整个尺寸范围。如果你只想增加一个新的尺寸,你需要去扩展主题的主题:
module.exports = {
theme: {
extend: {
screens: {
'3xl': '2440px',
}
}
}
}
这些断点是一套合理的默认值,但如果你只想把这些值移动一下,你可以在这里做。
你也可以把这些键的名字改成手机、横屏、平板和桌面之类的。这些键就会成为你的修改器的名称,所以你不再写 sm:m-0
;你会写 phone:m-0
。
如果你为屏幕宽度键提供的值是一个字符串,那么在生成 CSS 时,它就被认为是断点的最小宽度。如果你想以不同的方式指定断点,你也可以传递一个带有最小和最大键的对象。如果你只指定最大值,那么响应的行为是相反的。未修改的实用程序适用于最大尺寸,而修改器在屏幕变小时生效:
module.exports = {
theme: {
screens: {
'2xl': {'max': '9999px'},
'xl': {'max': '1535px'},
'lg': {'max': '1023px'},
'md': {'max': '767px'},
'sm': {'max': '639px'},
}
}
}
你也可以为这些对象提供一个最小值,这就把每个断点限制在一个特定的范围内,意味着你需要在每个断点完全指定所有的属性。
媒体查询并不仅仅是基于尺寸的。如果你想把修改器建立在其他东西上,你可以用一个原始选项来做。下面是一个增加打印模式的配置:
module.exports = {
theme: {
extend: {
screens: { pinrt: { raw: print } }
}
}
}
然后你可以像其他屏幕一样使用这个配置,class="print:bg-white"
。
默认颜色
Tailwind 有一套通用的颜色,作为许多实用程序系列的后缀,包括 text-
,bg-
和其他。Tailwind 提供了 22 种颜色。如果你想改变这组颜色,你可以通过一个颜色对象达到它们:
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
。你还可以通过使用相同的颜色对象作为任何键的值来添加颜色族。
你还可以对颜色进行嵌套以消除重复:
module.exports = {
theme: {
extend: {
colors: {
"company": {
"orange": "#ff5715",
"dark-blue": "#323C64",
"gray": " #DADADA",
}
}
}
}
}
产生的类仍然和未嵌套的颜色一样,比如说 text-company-orange
。如果你只想要 text-company
,那么使用 default
就可以代替未嵌套的值。
如果你用现有的颜色来扩展颜色,例如,如果你提供红色。{ '100': "#WHATEVER" }
,你将用你的新集合替换现有的红色系列。
如果你想用一个新的级别来扩展一个颜色,你可以使用传播操作符:
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
,然后 Tailwind 添加一个实用类 dark
,将该类中的任何元素改变为黑暗模式。通常,你会把它放在你的 DOM 树的顶端,并使用 JavaScript,这样你就可以一次改变整个树:
<body class="container mx-auto py-12 px-6 dark">
<div class="bg-gray-100 dark:bg-black">
</div>
</body>
你可以用自定义主题和 CSS 变量做更精细的事情。你可以在 dev.to 找到一个很好的概述。
间距
间距,如用于填充、边距、宽度、高度和其他属性,也可以通过使用 theme#spacing
或用 theme#extend#spacing
扩展来重写。所以你可以简单地像这样替换间距:
module.exports = {
theme: {
spacing: {
'small': '4px',
'medium': '12px',
'large': '36px'
}
}
}
这些新的后缀将适用于间距的任何地方,所以你现在会有 p-small
、h-medium
、gap-large
或其他可能性。
如果你喜欢现有的尺度,但想有更多的选择,那就使用扩展选项:
module.exports = {
theme: {
spacing: {
'15': '60rem',
'17': '76rem',
}
}
}
其它核心插件
几乎每一个 Tailwind 工具都有一系列的后缀,这些后缀来自于一个基本模式。而且几乎所有的工具都允许你覆盖或扩展它们,就像你对间距和颜色所做的那样。
我将选取一个例子,因为做所有的例子不在本书的范围之内。Tailwind 文档中每个核心插件的页面都解释了如何修改该插件。你可以在配置文件中用 theme#extend
添加不同的 z-index
值:
module.exports = {
theme: {
extend: {
zIndex: {
"-1": "-1",
"-5": "-5",
"-1000": "-1000"
}
}
}
}
注意,在这种情况下,Tailwind 生成的负数类的模式与负数边际一致,所以这里的负数类是 -z-1
,-z-5
,和 -z-1000
。
如果我想要我自己的一套完整的 z-index
选项,那么我就不会使用扩展:
module.exports = {
theme: {
zIndex: {
"1": "1",
"5": "5",
"1000": "1000"
"-1": "-1",
"-5": "-5",
"-1000": "-1000"
}
}
}
现在我们有了 -z-1
、-z-5
和 -z-1000
旁边还有 z-1
、z-5
和 z-100
,但原来的类已经不再生成了。
每个有多个选项的核心插件都允许类似的替换或扩展选项。同样,Tailwind 文档有一个完整的列表。
改变生成的类
在正常情况下,Tailwind 编译器只为你应用程序中使用的类名生成 CSS。然而,在某些情况下,你可能想修改这一配置。
首先,这里是编译器的工作。配置文件有一个内容键,它应该包含你项目中任何可能引用 Tailwind 工具类的文件的文件模式列表。这包括你的静态 .html
文件,但也包括 React 项目的 .jsx
文件或 Rails 项目的 .erb
文件。它甚至包括其他可能引用模板文件所调用的 Tailwind 工具类的文件。(但不要包括其他 .css
文件 —— 你要列出使用 Tailwind 类的文件,而不是定义 Tailwind 类的文件)
文件模式 使用 fast-glob
库,其中包括 *
匹配除目录标记外的任何文本,**
匹配包括目录标记在内的任何测试(允许你匹配任意的子目录),以及大括号和逗号以允许选项,如 *.{html, erb}
。
如果你使用重复中描述的策略来定义 JavaScript 或 Ruby 或其他什么的 Tailwind 类的列表,这些文件也需要在配置文件的内容栏中列出。对于一个 React 项目,你可能会得到类似这样的结果:
module.exports = {
content: [
"./src/**/*.{html, jsx, tsx}",
],
}
在这里你要稍微小心一点。在不在内容列表中的文件中使用的类将不会生成 CSS,而且看起来就是不工作。
另一方面,虽然包括非前端文件的成本很小,但包括你的整个节点-模块目录的成本很高,无论是运行命令行工具所需的时间,还是它将产生的不需要的 CSS 数量。
Tailwind 使用的机制在设计上很简单。它试图将相关文件分割成单独的单词,然后与 Tailwind 模式列表相匹配,以确定哪些单词是潜在的 Tailwind 实用程序,需要为它们生成 CSS。
这个过程偏向于谨慎,因为它可能会捕捉到没有被用作类名的类名,这通常是好的,比在一般情况下试图猜测什么是真正的 CSS 类,什么只是一行 JavaScript 或注释要容易一百万倍。但它不会捕捉到通过字符串连接动态创建的类名。
在 第 3 章 “排版” 中,我们看了以下创建悬停效果的例子:
const hoverDarker = (color) => {
return `text-${color}-300 hover:text-${color}-700`
}
这个函数,因为它动态地创建了一个类名,会阻止这些类被 Tailwind 找到,所以你要么需要在其他地方使用这些类,要么找到另一种方法来创建这个功能。
一个选择是安全列表。Tailwind 提供了一个名为 safelist
的配置键,允许你指定你想保证会生成的 Tailwind 类。安全列表中的单个列表可以是字符串或 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"]
}
]
}
这个配置文件单独列出两种背景颜色,然后用正则表达式匹配所有灰度的颜色。正则表达式的模式必须以 Tailwind 工具开始。例如,你不能用 +.-gray-+.
匹配所有使用灰色的地方,但是你可以用 bg-red-+.\+
. 匹配所有红色背景颜色和不透明度。我也不认为你可以用 bo+.
来匹配,比如说,盒子和边框(至少,这可能是个坏主意)。 你不能在 safelist
模式中匹配修饰符,但你可以通过在变体列表中加入 html
作为配置的一部分来确保修饰符的产生。
你也可以通过向配置的 corePlugins
键传递一个对象来创建一个核心插件的块状列表。这个对象的键是你想消除的核心插件的名称,而值都是假的。你只有在想屏蔽你因某种原因而实际使用的类时才会这样做:
module.exports = {
corePlugins: {
flex: false,
flexDirection: false,
flexGrow: false,
flexShrink: false,
flexWrap: false,
}
}
这种配置可以摆脱所有与 Flexbox 相关的工具,尽管我不建议这么做。Flexbox 是相当有用的。
变体修饰器
我们已经在 Tailwind 中看到了像 hover
和 focus
这样的变体修改器,但是 Tailwind 定义了几十个修改器,而且命令行工具只为你实际使用的修改器组合生成 CSS。
除了屏幕尺寸修改器 sm
、md
、lg
、xl
和 2xl
之外,下面是 Tailwind 修饰器的部分内容:
active
:当元素处于活动状态时适用dark
:如果 Tailwind 认为它处于黑暗模式,则适用first-letter
:适用于文本的第一个字母first-line
:适用于文本的第一行hover
:当用户将指针悬停在该元素上时适用landscape
:适用于设备处于横向的情况ltr
:适用于从左到右的文本marker
: 适用于列表中的标记motion-reduce
:如果用户在系统上启用了减少运动,则适用,它通常与悬停一起应用,你通常会有一个motion-reduce
和一个motion-safe
的变体motion-safe
:如果用户没有在系统上启用减少动作,则适用。它通常与悬停一起应用,你通常会有一个motion-reduce
和 一个motion-safe
的变体portrait
:适用于设备处于纵向的情况print
:适用于打印媒体类型rtl
:适用于从右到左的文本selection
:适用于用户选择的文本target
:如果元素 ID 与当前的 URL 匹配,则适用visited
:如果一个链接已经被访问,则适用
两个组属性通过声明一个具有组类的父元素来工作。当父元素中的任何元素成为目标时,这些变体属性都适用,而不仅仅是有关元素:
group-focus
:当父元素下的任何子元素获得焦点时,适用于任何子元素。group-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
:如果占位符文本仍在显示,则适用readonly
:如果该元素是只读的,则适用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 可能被设置成这样一种方式,即所有现有的 CSS 选择器都有很高的特殊性,从而覆盖了所有的 Tailwind 工具类。你可以通过配置 important: true
来解决这个问题,它为所有的 Tailwind 工具添加了 CSS 标记 !!important
,这应该使它们优先于现有的 CSS。如果你使用了很多不同的 CSS 库,这可能会产生不必要的副作用,所以要小心使用。
一些模板工具类不允许你在类名中使用冒号(:
)字符,使 Tailwind 的前缀成为非法。你可以指定一个分离器:选项来选择你自己的分离器,所以 separator: "--"
意味着前缀会看起来像 hover-text-black
或 lg-m0-4
。(我想我更喜欢这样的外观,而不是冒号)
用 JavaScript 访问 Tailwind
你可以从 JavaScript 访问 Tailwind 配置。如果你想使用这些值在你的 JavaScript 框架中创建动态行为,这很有用。你可能有某种定制的动画,需要尊重现有的颜色或间距,或者谁知道是什么。
无论你想做什么,Tailwind 都提供了一个 resolveConfig
方法,该方法将 Tailwind 配置对象作为参数,并允许你查询配置 —— 完整的配置,而不仅仅是你在文件中的覆盖:
import resolveConfig from 'tailwindcss/resolveConfig'
import myConfig from './tailwind.config.js'
const tailwindConfig = resolveConfig(myConfig)
tailwindConfig.theme.colors
来自 resolveConfig
的结果对象将你的配置覆盖与默认值合并,并提供一个你可以查询的对象。
插件
Tailwind 插件只是你可以定义的 JavaScript 片段,允许你在你的 Tailwind 应用中插入你自己的额外类、模式和前缀。它们实际上非常简单,你可以在你的 Tailwind 配置文件中插入它们。
首先,你需要在你的 tailwind.config.js
文件的顶部放入这一行,以要求该插件功能:
const plugin = require("taiwindcss/plugin")
然后,在配置本身里面,你可以调用那个 plugin
函数。plugin
函数的参数是一个函数。
const plugin = require("taiwindcss/plugin")
module.exports = {
content: ["./html/*.html"],
theme: {
extend: {},
},
plugins: [
plugin(({}) => {
})
],
};
匿名函数需要一个参数,前面的片段中的参数是一个空的 JavaScript 对象,{}
。实际上传递给该函数的对象有许多参数,每个参数都是一个辅助函数,你可以在匿名函数的主体中调用,以增加 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
方法需要两个参数。(1) 你想添加的修改器,以及 (2) 它应该转换的 CSS 伪类或媒体类型。
同样地,Tailwind 为 addUtilities
、addComponents
和 addBase
提供了辅助方法。addUtilities
方法需要两个参数。(1) 你想添加的一个新的 Tailwind 工具类的名称,以及 (2) 一个带有你想让该工具类解析为 CSS 属性的 JavaScript 对象,如 addUtilities(".big-bold-text", {font-size: "1.5rem", font-weight: "700"})
。addComponents
助手做了同样的事情,但把样式放在 Tailwind 组件层中。addBase
助手将新的样式添加到 Tailwind 基础层,这意味着第一个参数是一个HTML选择器,如 h4
,而不是一个 CSS 类。
还有 matchUtilities
和 matchComponents
方法,允许你定义一组动态匹配器。这两个方法都可以使用一个辅助方法 theme
来查找当前主题中的值,以此来确定动态匹配器集合中的内容。所以,theme("spacing")
给你所有的间距选项。完整的文档见 https://tailwindcss.com/docs/plugins。