Lzh on GitHub

使用工具类进行样式设置

从一组受限的原始工具类构建复杂的组件。

概述

您可以通过在 html 中直接组合许多单一用途的工具类来使用 Tailwind 来设置样式:

ChitChat

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 Logo" />
  <div>
    <div class="text-xl font-medium text-black dark:text-white">ChitChat</div>
    <p class="text-gray-500 dark:text-gray-400">You have a new message!</p>
  </div>
</div>

例如,在上面的 UI 中我们使用了:

  • displaypadding 工具类( flexshrink-0p-6 )用于控制整体布局
  • max-widthmargin 工具类( max-w-smmx-auto )用于限制卡片宽度并使其水平居中
  • background-colorborder-radiusbox-shadow 工具类( bg-whiterounded-xlshadow-lg )用于设置卡片的外观
  • widthheight 工具类( size-12 )用于设置徽标图像的宽度和高度
  • gap 工具类( gap-x-4 )用于处理徽标和文本之间的间距
  • font-sizecolorfont-weight 工具类( text-xltext-blackfont-medium 等)用于设置卡片文本的样式

用这种方式来编写样式虽然违背了许多传统的最佳实践,但一旦你尝试了,就会很快注意到一些非常重要的优势:

  • 你完成工作的速度更快 —— 你无需花时间想类名、决定选择器的写法,或者在 HTML 和 CSS 文件之间来回切换,因此你的设计能非常快速地完成。
  • 修改感觉更安全 —— 给一个元素添加或删除一个工具类只会影响这个元素,所以你完全不用担心会意外破坏到另一个页面上使用相同 CSS 的地方。
  • 维护旧项目更轻松 —— 要做改动,只需要在项目中找到那个元素并修改类名,而不是试图回忆六个月前写的那堆自定义 CSS 是怎么工作的。
  • 你的代码更具可移植性 —— 因为结构和样式都在同一个地方,你可以很方便地复制粘贴整个 UI 片段,甚至可以跨项目使用。
  • 你的 CSS 不再持续膨胀 —— 因为工具类的可复用性非常高,随着你为项目添加新功能,你的 CSS 不会像以前那样线性增长。

这些优势在小项目中就能带来很大差异,而对于正在进行的、需要长期维护的大型团队项目来说,它们的价值更是不可估量。

为什么不用内联样式?

很多人对这种方法的第一反应是:“这不就是内联样式吗?”从某种意义上说确实如此 —— 你是直接将样式应用到元素上,而不是给它们分配一个类名然后再为那个类名写样式。

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

  • 在约束下设计 —— 使用内联样式时,每个值都是一个魔法数字。而使用工具类时,你是在一个 预定义的设计系统 中选择样式,这让构建视觉一致的 UI 变得更加容易。
  • 悬停、聚焦等状态 —— 内联样式无法针对 hover 或 focus 等状态进行样式设置,而 Tailwind 的状态变体(state variants)让你可以非常轻松地使用工具类来设置这些状态。
  • 媒体查询 —— 你无法在内联样式中使用媒体查询,但你可以使用 Tailwind 的响应式变体(responsive variants)轻松构建完全响应式的界面。

下面这个组件就是完全响应式的,它包含了带有 hover 和 active 样式的按钮,且完全使用工具类构建而成:

Erin Lindford

Product Engineer

<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 ...">Save changes</button>

这些前缀在 Tailwind 中称为 变体 ,并且仅当变体的条件匹配时,它们才会应用工具类的样式。

以下是针对 hover:bg-sky-700 类生成的 CSS 的样子:

Generated CSS
.hover\:bg-sky-700 {
  &:hover {
    background-color: var(--color-sky-700);
  }
}
除非元素处于悬停状态,否则这个类什么也不做。它唯一的作用就是提供悬停样式,没有其他作用。
在 Tailwind CSS 中,--color-sky-700 是一个 CSS 变量(主题变量),通常定义在 Tailwind 的 默认主题文件(theme.css)或 用户的自定义主题配置 中。

这与编写传统 CSS 的方式不同,传统 CSS 中单个类通常会提供许多状态的样式:

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

您甚至可以在 Tailwind 中堆叠变体,以便在多个条件匹配时应用工具类,例如组合 hover:disabled:

HTML
<button class="bg-sky-500 disabled:hover:bg-sky-500 ...">Save changes</button>

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

媒体查询和断点

就像悬停和焦点状态一样,您可以通过在任何工具类前加上要应用该样式的断点来在不同的断点处设置元素的样式:

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

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

在上面的例子中, sm: 前缀确保 grid-cols-3 仅在 sm 断点及以上触发,这是开箱即用的 40rem:

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

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

夜间模式

在夜间模式下设置元素的样式只需将 dark: 前缀添加到要在夜间模式激活时应用的任何工具类即可:

Light mode

Writes upside-down

The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.

Dark mode

Writes upside-down

The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.

<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 ">Writes upside-down</h3>
  <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm ">
    The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.
  </p>
</div>

就像处理悬停状态或媒体查询一样,关键是要理解:单个工具类永远不会同时包含浅色和深色样式——你需要通过使用多个类来设置深色模式下的样式,一个类用于浅色模式样式,另一个用于深色模式样式。

Generated 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 变量来实现将这些效果组合在一起:

Generated 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] ...">
  Sign in with Facebook
</button>

这对于调色板之外的一次性颜色 (如上面的 Facebook 蓝色) 很有用,但当您需要复杂的自定义值(如非常具体的网格)时也很有用:

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

当您需要使用 CSS 功能(如 calc() 时它也很有用,即使您正在使用主题值:

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>
Simplified 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">Read more…</span>
</a>
Simplified 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>This text will be blue</span>
</div>
Simplified 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>
  );
}

管理重复

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

例如,这里每个头像图像的工具类重复五次:

<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>

当元素像这样循环呈现时,实际的类列表只写入一次,因此不需要解决实际的重复问题。

使用多光标编辑

当重复局限于单个文件中的一组元素时,处理它的最简单方法是使用 多光标编辑 来快速选择和编辑每个元素的类列表:

你可能会惊讶地发现,这往往是最好的解决方案。如果你能快速地同时编辑所有重复的类列表,那么引入任何额外的抽象就没有任何好处。

(省略)

使用组件

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

Private Villa
$299 USD per night
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 之类的语言,那么与 btn 之类的简单 CSS 类相比,为按钮这样小的东西创建模板部分可能会感觉有些过度。

虽然强烈建议您为更复杂的组件创建适当的模板部分,但当模板部分感觉过于繁琐时,编写一些自定义 CSS 是完全没问题的(自定义组件)。

btn-primary 类可能如下所示,使用 主题变量 来保持设计的一致性:

<button class="btn-primary">Save changes</button>
@import "../../../../../node_modules/.pnpm/tailwindcss@4.1.11/node_modules/tailwindcss/dist/lib.d.mts";
@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 属性为目标的类时,样式表中较后出现的类将生效(注意是样式表中的出现顺序,不是 class 中的顺序)。因此,在本例中,即使 flex 在实际的 class 属性中排在最后,元素仍将获得 display: grid:

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

一般来说,你永远不应该向同一个元素添加两个冲突的类 - 只添加你真正想要生效的类:

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

在使用基于组件的库(如 ReactVue)时,这通常意味着需要暴露特定的 props 供外部进行样式定制,而不是允许使用者从组件外部添加额外的类名,因为这些样式往往会引发冲突。

使用重要修饰符

当你确实需要强制某个特定工具类生效且没有其他管理优先级的方法时,可以在类名末尾添加 ! 来使所有声明变为 !important

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

使用 important 标志

如果你正在将 Tailwind 添加到一个已有复杂 CSS 且包含高优先级规则的项目中,可以在导入 Tailwind 时使用 important 标记,使所有工具类都带有 !important 声明:

app.css
@import "tailwindcss" important;
Compiled 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);
Compiled 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);
  }
}