核心概念
通过一组受限的原始工具类构建复杂组件。
在 Tailwind 中,你通过直接在标记中组合多个单一用途的展示类(工具类)来设计样式:
You have a new message!
<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 标志" /> <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 中我们使用了:
flex
、shrink-0
和 p-6
)来控制整体布局max-w-sm
和 mx-auto
)来限制卡片宽度并水平居中bg-white
、rounded-xl
和 shadow-lg
)来设计卡片外观size-12
)来设置标志图像的宽度和高度gap-x-4
)来处理标志与文本之间的间距text-xl
、text-black
、font-medium
等)来设计卡片文本这种设计方式与许多传统的最佳实践相矛盾,但一旦你尝试过,就会很快注意到一些非常重要的优势:
这些优势在小项目中已经能带来很大不同,但对于长期大规模项目的团队来说,它们甚至更有价值。
对于这种方法,一个常见的反应是疑惑:"这不就是内联样式吗?"在某种程度上确实如此——你是直接将样式应用到元素上,而不是给它们分配一个类名然后为那个类添加样式。
但是相比内联样式,使用工具类有许多重要优势,例如:
这个组件是完全响应式的,包含一个带有悬停和激活状态的按钮,完全使用工具类构建:
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">Product Engineer</p> </div> <button class="border-purple-200 text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700 ..."> Message </button> </div></div>
要为元素设置悬停或焦点等状态的样式,只需在任意工具类前添加目标状态前缀,例如 hover:bg-sky-700
:
悬停在此按钮上查看背景色变化
<button class="bg-sky-500 hover:bg-sky-700 ...">保存更改</button>
这些前缀在 Tailwind 中被称为变体,它们仅在匹配变体条件时才会应用工具类的样式。
以下是 hover:bg-sky-700
类生成的 CSS 代码:
.hover\:bg-sky-700 { &:hover { background-color: var(--color-sky-700); }}
注意这个类在元素未被悬停时不会产生任何效果?它的唯一作用就是提供悬停样式——仅此而已。
这与传统 CSS 的编写方式不同,传统 CSS 中单个类通常会为多个状态提供样式:
<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>
了解更多关于悬停、焦点和其他状态的样式设置,请查阅相关文档。
就像悬停(hover)和聚焦(focus)状态一样,你可以通过在任意工具类前添加断点前缀,来在不同断点下应用样式:
调整此示例大小以查看布局变化
<div class="grid grid-cols-2 sm:grid-cols-3"> <!-- ... --></div>
在上面的示例中,sm:
前缀确保 grid-cols-3
仅在 sm
断点及以上(默认为 40rem)时生效:
.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>
与悬停状态或媒体查询类似,关键要理解的是:单个工具类永远不会同时包含浅色和暗黑两种样式 —— 你需要使用多个类来分别定义浅色模式和暗黑模式的样式。
.dark\:bg-gray-800 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-800); }}
更多细节请参阅暗黑模式文档。
在 Tailwind 中,很多时候你会使用多个类来构建单个 CSS 属性的值,例如为一个元素添加多个滤镜效果:
<div class="blur-sm grayscale"> <!-- ... --></div>
这些效果都依赖于 CSS 中的 filter
属性,因此 Tailwind 使用 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-500
、text-xl
和 shadow-md
,它们映射到底层的调色板、字体比例和阴影配置。
当您需要使用主题之外的一次性值时,可以使用特殊的方括号语法来指定任意值:
<button class="bg-[#316ff6] ..."> 使用 Facebook 登录</button>
这对于调色板之外的一次性颜色(如上文的 Facebook 蓝色)非常有用,同时也适用于需要复杂自定义值的情况,比如非常特定的网格布局:
<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]"> <!-- ... --></div>
当您需要使用 CSS 特性如 calc()
时也很实用,即使您正在使用主题值:
<div class="max-h-[calc(100dvh-(--spacing(6)))]"> <!-- ... --></div>
甚至还有一种语法可以生成完全任意的 CSS,包括任意属性名,这对于设置 CSS 变量非常有用:
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]"> <!-- ... --></div>
了解更多请参阅使用任意值的文档。
Tailwind CSS 不像其他 CSS 框架那样是一个庞大的静态样式表 —— 它会在编译 CSS 时根据你实际使用的类名生成所需的 CSS。
它通过扫描项目中的所有文件,寻找任何可能作为类名的符号来实现这一点:
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 实现这种效果的示例:
<button class="dark:lg:data-current:hover:bg-indigo-600 ..."> <!-- ... --></button>
@media (prefers-color-scheme: dark) and (width >= 64rem) { button[data-current]:hover { background-color: var(--color-indigo-600); }}
Tailwind 还支持诸如 group-hover
这样的功能,它允许您在特定父元素被悬停时设置子元素样式:
<a href="#" class="group rounded-lg p-8"> <!-- ... --> <span class="group-hover:underline">Read more…</span></a>
@media (hover: hover) { a:hover span { text-decoration-line: underline; }}
这种 group-*
语法也适用于其他变体,如 group-focus
、group-active
以及更多变体。
对于非常复杂的场景(特别是当您需要设置不受控制的 HTML 样式时),Tailwind 支持任意变体,允许您直接在类名中编写任何选择器:
<div class="[&>[data-active]+span]:text-blue-600 ..."> <span data-active><!-- ... --></span> <span>这段文字将显示为蓝色</span></div>
div > [data-active] + span { color: var(--color-blue-600);}
在 Tailwind CSS 项目中,内联样式仍然非常有用,特别是当值来自动态源(如数据库或 API)时:
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> );}
对于非常复杂的任意值,当它们格式化为类名难以阅读时,你也可以使用内联样式:
<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 变量,然后通过工具类引用这些变量:
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> );}
当你仅使用工具类构建整个项目时,不可避免地会在不同地方重复某些模式来重现相同的设计。
例如,这里每个头像图片的工具类被重复了五次:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</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 others</a> </div></div>
别担心!实际上这并不像你担心的那样是个问题,而且处理它的策略正是你每天都在做的事情。
在渲染页面中,很多设计元素虽然会多次出现,但实际上只需要编写一次,因为实际的标记是通过循环渲染的。
例如,本指南开头的重复头像在实际项目中几乎肯定是通过循环渲染的:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</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 others</a> </div></div>
当元素像这样通过循环渲染时,实际的类列表只需编写一次,因此不存在需要解决的实际重复问题。
当重复代码集中在单个文件中的一组元素时,最简单的处理方法是使用多光标编辑来快速同时选择和编辑每个元素的类列表:
你会惊讶地发现,这往往是最佳解决方案。如果你能快速同时编辑所有重复的类列表,就没有必要引入任何额外的抽象层。
<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 等模板语言,就创建一个_模板局部文件_。
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> );}
现在你可以在任意多个地方使用这个组件,同时仍然保持样式的单一真实来源,这样就可以在一个地方轻松地统一更新它们。
如果你正在使用 ERB 或 Twig 这样的模板语言,而不是 React 或 Vue 这类框架,为像按钮这样简单的元素创建模板片段可能会显得小题大做,相比之下,使用像 btn
这样的简单 CSS 类会更合适。
虽然我们强烈建议为更复杂的组件创建适当的模板片段,但当模板片段显得过于繁琐时,编写一些自定义 CSS 是完全可行的。
以下是一个 btn-primary
类的示例,它使用了主题变量来保持设计一致性:
<button class="btn-primary">保存更改</button>
@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 属性的类时,样式表中靠后的类会生效。因此在这个例子中,即使 flex
在实际的 class
属性中排在最后,元素仍会应用 display: grid
:
<div class="grid flex"> <!-- ... --></div>
.flex { display: flex;}.grid { display: grid;}
通常来说,你不应该给同一个元素添加两个冲突的类——只添加你真正想要生效的那个:
export function Example({ gridLayout }) { return <div className={gridLayout ? "grid" : "flex"}>{/* ... */}</div>;}
在使用 React 或 Vue 这类基于组件的库时,这通常意味着通过暴露特定的 props 来进行样式定制,而不是让使用者从组件外部添加额外的类,因为这些样式经常会产生冲突。
当你确实需要强制某个工具类生效且没有其他管理特异性的方法时,可以在类名末尾添加 !
来使所有声明变为 !important
:
<div class="bg-teal-500 bg-red-500!"> <!-- ... --></div>
.bg-red-500\! { background-color: var(--color-red-500) !important;}.bg-teal-500 { background-color: var(--color-teal-500);}
如果要将 Tailwind 添加到已有复杂 CSS 和高优先级规则的项目中,可以在导入 Tailwind 时使用 important
标志,将 所有 工具类标记为 !important
:
@import "tailwindcss" important;
@layer utilities { .flex { display: flex !important; } .gap-4 { gap: 1rem !important; } .underline { text-decoration-line: underline !important; }}
如果项目中的类名与 Tailwind CSS 工具类冲突,可以使用 prefix
选项为所有 Tailwind 生成的类和 CSS 变量添加前缀:
@import "tailwindcss" prefix(tw);
@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); }}