1. 核心概念
  2. 使用工具类设计样式

核心概念

使用工具类进行样式设计

从有限的基础实用工具构建复杂组件。

概述

通过将许多单用途的表现类(称为“工具类”)直接结合在您的标记中,您可以使用 Tailwind 来设计:

ChitChat

您有新的消息!

<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 dark:bg-slate-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10">
<img class="size-12 shrink-0" src="/img/logo.svg" alt="ChitChat Logo" />
<div>
<div class="text-xl font-medium text-black dark:text-white">ChitChat</div>
<p class="text-gray-500 dark:text-gray-400">您有新的消息!</p>
</div>
</div>

例如,以上 UI 中我们使用了:

以这种方式为事物设样式与许多传统最佳实践相违背,但一旦尝试后,您将迅速发现一些非常重要的好处:

  • 您能更快完成工作 — 您无需为类名而苦恼,不必花时间决定选择器或在 HTML 和 CSS 文件之间切换,因此设计能非常快速地组合在一起。
  • 进行更改时感觉更安全 — 向元素添加或删除一个工具类仅影响该元素,因此您永远无须担心意外破坏使用相同 CSS 的其他页面。
  • 维护旧项目更容易 — 更改某些内容只需在项目中找到该元素并更改类,而无须试图记住所有未接触六个月的自定义 CSS 是如何工作的。
  • 您的代码更具可移植性 — 由于结构和样式都位于同一位置,您可以轻松地复制和粘贴整个 UI 块,甚至在不同项目之间。
  • 您的 CSS 停止增长 — 由于工具类可以重用,因此您的 CSS 不再随着添加项目中的每个新功能而线性增长。

这些好处在小型项目上产生重大影响,但对于在规模较大的长期项目中工作的团队而言,它们的价值更为显著。

为什么不直接使用内联样式?

对此方法的常见反应是:“这不就是内联样式吗?”在某些方面是这样的,您确实是将样式直接应用于元素,而不是先给它们指定类名再为该类进行样式设置。

但使用工具类相较于内联样式有许多重要优势,例如:

  • 在限制下设计 — 使用内联样式时,每个值都是一个魔法数字。使用工具类时,您从预定义设计系统中选择样式,这使得构建视觉一致的 UI 更加容易。
  • 悬停、焦点及其他状态 — 内联样式无法定位像悬停或焦点这样的状态,但 Tailwind 的状态变体使得使用工具类轻松即成样式。
  • 媒体查询 — 您无法在内联样式中使用媒体查询,但可以使用 Tailwind 的响应式变体轻松构建完全响应的界面。

这个组件是完全响应式的,并且包含一个具有悬停和激活样式的按钮,并完全通过工具类构建:

女人的脸

Erin Lindford

产品工程师

<div class="flex flex-col gap-2 p-8 sm:flex-row sm:items-center sm:gap-6 sm:py-4 ...">
<img class="mx-auto block h-24 rounded-full sm:mx-0 sm:shrink-0" src="/img/erin-lindford.jpg" alt="" />
<div class="space-y-2 text-center sm:text-left">
<div class="space-y-0.5">
<p class="text-lg font-semibold text-black">Erin Lindford</p>
<p class="font-medium text-gray-500">产品工程师</p>
</div>
<button class="border-purple-200 text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700 ...">
消息
</button>
</div>
</div>

使用工具类思维

样式悬停和焦点状态

要在悬停或焦点等状态下为元素设计样式,只需在希望针对的状态前添加任何工具类前缀,例如 hover:bg-sky-700

悬停这个按钮查看背景颜色变化

<button class="bg-sky-500 hover:bg-sky-700 ...">保存更改</button>

这些前缀在 Tailwind 中称为变体,当该变体条件匹配时,仅在此时应用工具类的样式。

以下是 hover:bg-sky-700 类生成的 CSS 样式:

生成的 CSS
.hover\:bg-sky-700 {
&:hover {
background-color: var(--color-sky-700);
}
}

注意到这个类只有在元素被悬停时才会生效吗?它的“唯一”任务是提供悬停样式—没有别的。

这与您书写传统 CSS 的方式不同,通常一个类会为多个状态提供样式:

HTML
<button class="btn">保存更改</button>
<style>
.btn {
background-color: var(--color-sky-500);
&:hover {
background-color: var(--color-sky-700);
}
}
</style>

您甚至可以在 Tailwind 中叠加变体,以在多个条件匹配时应用工具类,例如将 hover:disabled: 结合起来。

<button class="bg-sky-500 disabled:hover:bg-sky-500 ...">保存更改</button>

在文档中了解更多关于在悬停、焦点和其他状态下样式化元素的信息。

媒体查询与断点

就像悬停和焦点状态一样,您可以通过在希望应用样式的断点前加上任何工具类前缀来为不同断点下的元素样式设定:

调整此示例的大小以查看布局变化

01
02
03
04
05
06
<div class="grid grid-cols-2 sm:grid-cols-3">
<!-- ... -->
</div>

在上面的示例中,sm: 前缀确保 grid-cols-3 仅在 sm 断点及以上触发,默认情况为 40rem:

生成的 CSS
.sm\:grid-cols-3 {
@media (width >= 40rem) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}

响应式设计文档中了解更多信息。

针对暗模式

在暗模式中为元素设置样式,只需在希望在暗模式激活时应用的任何工具类前加上 dark: 前缀:

明亮模式

可以上下书写

零重力笔可以以任何方向书写,包括倒着书写。它甚至能在外层空间中工作。

暗模式

可以上下书写

零重力笔可以以任何方向书写,包括倒着书写。它甚至能在外层空间中工作。

<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5">
<div>
<span class="inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg">
<svg
class="h-6 w-6 text-white"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<!-- ... -->
</svg>
</span>
</div>
<h3 class="text-gray-900 dark:text-white mt-5 text-base font-medium tracking-tight">可以上下书写</h3>
<p class="text-gray-500 dark:text-gray-400 mt-2 text-sm">
零重力笔可以以任何方向书写,包括倒着书写。它甚至能在外层空间中工作。
</p>
</div>

就像悬停状态或媒体查询一样,重要的是要理解,单个工具类从来不会同时包含明亮和暗模式样式——在暗模式中为事物设样式的方式是使用多个类,一个用于明亮模式样式,另一个用于暗模式样式。

生成的 CSS
.dark\:bg-gray-800 {
@media (prefers-color-scheme: dark) {
background-color: var(--color-gray-800);
}
}

暗模式文档中了解更多信息。

使用类组合

在 Tailwind 中,您经常需要使用多个类来为单个 CSS 属性构建值,例如为元素添加多个滤镜:

HTML
<div class="blur-sm grayscale">
<!-- ... -->
</div>

这两种效果均依赖 CSS 中的 filter 属性,因此 Tailwind 使用 CSS 变量使得这些效果能够组合在一起:

生成的 CSS
.blur-sm {
--tw-blur: blur(var(--blur-sm));
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);
}
.grayscale {
--tw-grayscale: grayscale(100%);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);
}

上面的生成 CSS 稍有简化,但窍门在于每个工具类都设置一个仅适用于其所应用效果的 CSS 变量。然后,filter 属性会查看所有这些变量,如果某个变量未设置,则会回退到空值。

Tailwind 在渐变阴影颜色变换等方面采用相同的方法。

使用任意值

Tailwind 中的许多工具类都是通过主题变量驱动的,例如 bg-blue-500text-xlshadow-md,它们映射到您的基本颜色调色板、类型比例和阴影。

当您需要使用一个在主题之外的特定值时,可以使用特殊的方括号语法来指定任意值:

HTML
<button class="bg-[#316ff6] ...">
使用 Facebook 登录
</button>

这在您需要使用不在调色板中的一次性颜色时(例如上面的 Facebook 蓝色),以及需要一个复杂的自定义值(例如非常具体的网格)时都非常有用:

HTML
<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]">
<!-- ... -->
</div>

当您需要使用像 calc() 这样的 CSS 特性时,即使您使用的是主题值,它也非常有用:

HTML
<div class="max-h-[calc(100dvh-(--spacing(6))]">
<!-- ... -->
</div>

实际上,还有一种语法可以生成完全任意的 CSS,包括任意的属性名称,这在设置 CSS 变量时非常有用:

HTML
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]">
<!-- ... -->
</div>

使用任意值文档中了解更多信息。

这到底是如何工作的?

Tailwind CSS 不是像您在其他 CSS 框架中可能习惯的那样的一个静态样式表——它根据您在编译 CSS 时实际使用的类生成所需的 CSS。

它通过扫描项目中的所有文件,寻找任何看起来可能是类名的符号来实现这一点:

Button.jsx
export default function Button({ size, children }) {
let sizeClasses = {
md: "px-4 py-2 rounded-md text-base",
lg: "px-5 py-3 rounded-lg text-lg",
}[size];
return (
<button type="button" className={`font-bold ${sizeClasses}`}>
{children}
</button>
);
}

在找到所有潜在的类之后,Tailwind 为每个类生成 CSS,并将其编译为只包含实际需要的样式的样式表。

由于 CSS 基于类名生成,Tailwind 能够识别使用任意值的类,如 bg-[#316ff6],并生成必要的 CSS,即使该值不是您主题的一部分。

检测源文件中的类中了解有关此功能的更多信息。

复杂选择器

有时您需要根据多个条件为元素设置样式,例如在暗模式下、在特定断点、悬停时和元素具有特定数据属性时。

这里是 Tailwind 中如何处理的示例:

HTML
<button class="dark:lg:data-current:hover:bg-indigo-600 ...">
<!-- ... -->
</button>
简化的 CSS
@media (prefers-color-scheme: dark) and (width >= 64rem) {
button[data-current]:hover {
background-color: var(--color-indigo-600);
}
}

Tailwind 还支持 group-hover 等功能,可以让您在特定父元素被悬停时为元素设置样式:

HTML
<a href="#" class="group rounded-lg p-8">
<!-- ... -->
<span class="group-hover:underline">阅读更多…</span>
</a>
简化的 CSS
@media (hover: hover) {
a:hover span {
text-decoration-line: underline;
}
}

group-* 语法还与其他变体一起使用,例如 group-focusgroup-active 以及更多

对于非常复杂的场景(尤其是在样式化 HTML 时您无法控制的情况),Tailwind 支持任意变体,可以让您在类名中直接写任何选择器:

HTML
<div class="[&>[data-active]+span]:text-blue-600 ...">
<span data-active><!-- ... --></span>
<span>这段文字将变成蓝色</span>
</div>
简化的 CSS
div > [data-active] + span {
color: var(--color-blue-600);
}

何时使用内联样式

内联样式在 Tailwind CSS 项目中仍然非常有用,尤其是当某个值来自于数据库或 API 等动态源时:

branded-button.jsx
export function BrandedButton({ buttonColor, textColor, children }) {
return (
<button
style={{
backgroundColor: buttonColor,
color: textColor,
}}
className="rounded-md px-3 py-1.5 font-medium"
>
{children}
</button>
);
}

您可能还会出于对非常复杂的任意值的阅读难度考虑,考虑使用内联样式,在格式化为类名时读起来较为复杂:

HTML
<div class="grid-[2fr_max(0,var(--gutter-width))_calc(var(--gutter-width)+10px)]">
<div style="grid-template-columns: 2fr max(0, var(--gutter-width)) calc(var(--gutter-width) + 10px)">
<!-- ... -->
</div>

另一个有用的模式是基于动态源设定 CSS 变量,使用内联样式,然后使用工具类引用这些变量:

branded-button.jsx
export function BrandedButton({ buttonColor, buttonColorHover, textColor, children }) {
return (
<button
style={{
"--bg-color": buttonColor,
"--bg-color-hover": buttonColorHover,
"--text-color": textColor,
}}
className="bg-(--bg-color) text-(--text-color) hover:bg-(--bg-color-hover) ..."
>
{children}
</button>
);
}

管理重复

当您仅使用工具类构建整个项目时,您不可避免地会发现自己在不同地方重复某些模式以重现相同的设计。

例如,以下每个头像图像的工具类重复了五次:

贡献者

204
<div>
<div class="flex items-center space-x-2 text-base">
<h4 class="font-semibold text-slate-900">贡献者</h4>
<span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span>
</div>
<div class="mt-3 flex -space-x-2 overflow-hidden">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" />
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" />
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt="" />
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" />
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" />
</div>
<div class="mt-3 text-sm font-medium">
<a href="#" class="text-blue-500">+ 198 其他</a>
</div>
</div>

别担心!在实际操作中,这并不是您可能担心的问题,应对它的策略是您每天都会使用的东西。

使用循环

在渲染页面中多次出现的设计元素,通常可能在实际编写时只编写了一次,因为实际标记是在循环中渲染的。

例如,前面提到的重复头像在真实项目中几乎肯定会在循环中渲染:

贡献者

204
<div>
<div class="flex items-center space-x-2 text-base">
<h4 class="font-semibold text-slate-900">贡献者</h4>
<span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span>
</div>
<div class="mt-3 flex -space-x-2 overflow-hidden">
{#each contributors as user}
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src={user.avatarUrl} alt={user.handle} />
{/each}
</div>
<div class="mt-3 text-sm font-medium">
<a href="#" class="text-blue-500">+ 198 其他</a>
</div>
</div>

当元素以这种方式在循环中渲染时,实际的类列表只会写入一次,因此并不存在重复的问题需要解决。

使用多光标编辑

当重复局限于单个文件中的一组元素时,处理它的最简单的方法是使用multi-cursor编辑快速选择并同时编辑每个元素的类列表:

您会惊讶于这种方法有多么常见。如果可以迅速在所有重复的类列表上进行编辑,则没有必要引入任何额外的抽象。

<nav class="flex justify-center space-x-4">  <a href="/dashboard" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Home  </a>  <a href="/team" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Team  </a>  <a href="/projects" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Projects  </a>  <a href="/reports" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Reports  </a></nav>

使用组件

如果您需要在多个文件中重用某些样式,最佳策略是如果您使用像 React、Svelte 或 Vue 这样的前端框架,则创建一个_组件_,如果您使用像 Blade、ERB、Twig 或 Nunjucks 这样的模板语言,则创建一个_模板局部_。

海滩
私密别墅
$299 美元每晚
export function VacationCard({ img, imgAlt, eyebrow, title, pricing, url }) {
return (
<div>
<img className="rounded-lg" src={img} alt={imgAlt} />
<div className="mt-4">
<div className="text-xs font-bold text-sky-500">{eyebrow}</div>
<div className="mt-1 font-bold text-gray-700">
<a href={url} className="hover:underline">
{title}
</a>
</div>
<div className="mt-2 text-sm text-gray-600">{pricing}</div>
</div>
</div>
);
}

现在,您可以在任意多的地方使用该组件,同时仍然拥有样式的单一来源使其能够在一个地方轻松进行更新。

使用自定义 CSS

如果您使用的是像 ERB 或 Twig 这样的模板语言,而不是像 React 或 Vue 这样的框架,对于这样的小按钮创建模板部分可能感觉有些过于繁琐,相比于简单的 CSS 类 btn

虽然我们强烈建议您为更复杂的组件创建适当的模板局部,但当模板局部感觉太繁琐时,编写一些自定义 CSS 是完全可以接受的。

以下是一个 btn-primary 类的示例,使用主题变量保持设计一致:

HTML
<button class="btn-primary">保存更改</button>
CSS
@import "tailwindcss";
@layer components {
.btn-primary {
border-radius: calc(infinity * 1px);
background-color: var(--color-violet-500);
padding-inline: --spacing(5);
padding-block: --spacing(2);
font-weight: var(--font-weight-semibold);
color: var(--color-white);
box-shadow: var(--shadow-md);
&:hover {
@media (hover: hover) {
background-color: var(--color-violet-700);
}
}
}
}

不过,除了单个 HTML 元素之外,针对比这更复杂的内容,我们强烈建议使用模板局部来封装样式和结构。

管理样式冲突

冲突的工具类

当您添加两个针对同一 CSS 属性的类时,样式表中后出现的类会胜出。因此在下面的示例中,元素将获得 display: grid,即便 flex 实际上是最后出现的类:

HTML
<div class="grid flex">
<!-- ... -->
</div>
CSS
.flex {
display: flex;
}
.grid {
display: grid;
}

一般来说,您 never 应该向同一元素添加两个冲突的类——只添加您真正希望生效的类:

example.jsx
export function Example({ gridLayout }) {
return <div className={gridLayout ? "grid" : "flex"}>{/* ... */}</div>;
}

使用像 React 或 Vue 这种基于组件的库,通常意味着要为样式定制公开特定的 props,而不是让消费者从组件外部添加额外的类,因为这些样式通常会冲突。

使用重要修饰符

当您确实需要强制特定的工具类生效,并没有其他方法来管理特异性时,可以在类名末尾添加 ! 将所有声明标记为 !important

HTML
<div class="bg-teal-500 bg-red-500!">
<!-- ... -->
</div>
生成的 CSS
.bg-red-500\! {
background-color: var(--color-red-500) !important;
}
.bg-teal-500 {
background-color: var(--color-teal-500);
}

使用重要标记

如果您将 Tailwind 添加到具有现有复杂 CSS 及高特异性规则的项目中,可以在导入 Tailwind 时使用 important 标记将_所有_工具类标记为 !important

app.css
@import "tailwindcss" important;
已编译的 CSS
@layer utilities {
.flex {
display: flex !important;
}
.gap-4 {
gap: 1rem !important;
}
.underline {
text-decoration-line: underline !important;
}
}

使用前缀选项

如果您的项目中有与 Tailwind CSS 工具类冲突的类名,您可以使用 prefix 选项为所有生成的 Tailwind 类和 CSS 变量加上前缀:

app.css
@import "tailwindcss" prefix(tw);
已编译的 CSS
@layer theme {
:root {
--tw-color-red-500: oklch(0.637 0.237 25.331);
}
}
@layer utilities {
.tw\:text-red-500 {
color: var(--tw-color-red-500);
}
}