Tailwind CSS 最佳实践

1,789 阅读3分钟

Tailwind CSS 最佳实践

我总结了使用 Tailwind CSS 编写类名的一些规范,目的是为了更好维护类名、更高性能以及解决 Tailwind CSS 特定的问题。

尽量少用 @apply 来自定义类名

因为使用 @apply 自定义的类名,CSS Ouput 会输出重复的声明

不推荐

.custom-cls {
  @apply text-black py-2 text-sm
}
<div class="custom-cls"></div>
<div class="py-2">
    <div class="text-black text-sm"></div>
</div>

CSS Output

.py-2 {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem
}

.text-sm {
  font-size: 0.875rem;
  line-height: 1.25rem
}

.text-black {
  --tw-text-opacity: 1;
  color: rgb(0 0 0 / var(--tw-text-opacity))
}

.custom-cls {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  --tw-text-opacity: 1;
  color: rgb(0 0 0 / var(--tw-text-opacity))
}

推荐

<div class="py-2 text-sm text-black"></div>
<div class="py-2">
    <div class="text-black text-sm"></div>
</div>

CSS Output

.py-2 {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem
}

.text-sm {
  font-size: 0.875rem;
  line-height: 1.25rem
}

.text-black {
  --tw-text-opacity: 1;
  color: rgb(0 0 0 / var(--tw-text-opacity))
}

复用样式

尽管 Tailwind CSS 官方提供复用样式方案, 但是更推荐使用 Class Variance Authority 实现复用。

  • 减少样式冲突:CVA 提供了一种结构化的方式来管理样式变体,减少了全局样式冲突的可能性。
  • 方便扩展: 当需要添加新的样式变体时,CVA 使得扩展变得更加方便和高效。只需在现有的样式中添加新的变体,而不需要大规模地修改现有的样式代码。
  • 提高代码可维护性: CVA 通过明确地管理样式的变体,使得代码更加可读和可维护。

更多了解 Large Tailwind Components — What to do About All Those ...,这个视频详细具体地介绍了使用 CVA 的优点。

(推荐) 使用 Class Variance Authority

import { cva } from "class-variance-authority";
 
const button = cva(["font-semibold", "border", "rounded"], {
  variants: {
    intent: {
      primary: [
        "bg-blue-500",
        "text-white",
        "border-transparent",
        "hover:bg-blue-600",
      ],
      // **or**
      // primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
      secondary: [
        "bg-white",
        "text-gray-800",
        "border-gray-400",
        "hover:bg-gray-100",
      ],
    },
    size: {
      small: ["text-sm", "py-1", "px-2"],
      medium: ["text-base", "py-2", "px-4"],
    },
  },
  compoundVariants: [
    {
      intent: "primary",
      size: "medium",
      class: "uppercase",
      // **or** if you're a React.js user, `className` may feel more consistent:
      // className: "uppercase"
    },
  ],
  defaultVariants: {
    intent: "primary",
    size: "medium",
  },
});

button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase"
 
button({ intent: "secondary", size: "small" });
// => "font-semibold border rounded bg-white text-gray-800 border-gray-400 hover:bg-gray-100 text-sm py-1 px-2"

尽量少用 Arbitrary values

在开发之前,最好确定设计规范。然后通过 tailwind.config.js 修改默认值或者新增类名。尽量避免使用 Arbitrary values, 可能会导致 CSS Output 存在重复的样式。

<!-- bad -->
<div class="p-4">
  <div class="p-[0.5rem] m-2"></div>
  <div class="p-[16px] m-2"></div>
</div>

CSS Output

/* 编译后 */
.m-2 {
  margin: 0.5rem
}

.p-4 {
  padding: 1rem
}

.p-\[0\.5rem\] {
  padding: 0.5rem
}

.p-\[16px\] {
  padding: 16px
}
``

<!-- good -->
<div class="p-4">
  <div class="p-4 m-2"></div>
  <div class="p-4 m-2"></div>
</div>

CSS Output

/* 编译后 */
.m-2 {
  margin: 0.5rem
}

.p-4 {
  padding: 1rem
}

类名冲突方案

尽管 CVA 的 API 设计旨在帮助避免样式冲突,但仍存在很小的错误余地。请使用 tailwind-merge 包。

import { cva, type VariantProps } from "class-variance-authority";
import { twMerge } from "tailwind-merge";
 
const buttonVariants = cva(["your", "base", "classes"], {
  variants: {
    intent: {
      primary: ["your", "primary", "classes"],
    },
  },
  defaultVariants: {
    intent: "primary",
  },
});
 
export interface ButtonVariants extends VariantProps<typeof buttonVariants> {}
 
export const button = (variants: ButtonVariants) =>
  twMerge(buttonVariants(variants));

tailwind-merge

tailwind-merge 的 twMerge 不是银弹。把 twMerge 当做最后的手段,实在没办法处理冲突类名的情况下使用。因为 twMerge 运行时逻辑计算,比单纯的类名合并更耗时。

详细了解

尽量少写自定义样式

使用 Tailwind CSS 提供的类名就能完成大部分的样式。不需要在项目里自定义样式,

不推荐

<div class="chat-notification">
  <div class="chat-notification-logo-wrapper">
    <img class="chat-notification-logo" src="/img/logo.svg" alt="ChitChat Logo">
  </div>
  <div class="chat-notification-content">
    <h4 class="chat-notification-title">ChitChat</h4>
    <p class="chat-notification-message">You have a new message!</p>
  </div>
</div>

<style>
  .chat-notification { 
      margin: 0 auto;
      padding: 24px;
      /* ... */
  }
  .chat-notification-logo-wrapper { /* ... */ }
  .chat-notification-logo { /* ... */ }
  .chat-notification-content { /* ... */ }
  .chat-notification-title { /* ... */ }
  .chat-notification-message { /* ... */ }
</style>

推荐

<div class="max-w-sm mx-auto p-6 flex items-center bg-white rounded-xl shadow-md space-x-4">
  <div class="shrink-0">
    <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
  </div>
  <div class="chat-notification-content">
    <h4 class="text-base sm:text-xl font-medium text-black">ChitChat</h4>
    <p class="text-sm sm:text-base text-slate-500">You have a new message!</p>
  </div>
</div>

使用 PostCSS 作为预编译器

Using with Preprocessors

使用 cx

使用 cx (CVA 的 API)用来拼接类名以及逻辑运算 (也可以使用 'clx'、'classnames'、tailwind-merge 的 'tw-join')

const button = cx(full && 'w-full', 'font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase')