本文约 5,982 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:写得快,上线就翻车
用 Tailwind 写 WordPress 模板的体验确实好:不用在 SCSS 文件之间跳来跳去,直接在 class 里堆 flex items-center gap-4 text-sm text-gray-600 就能出效果。问题往往出现在打包后——本地开发好好的,构建产物里某些类不见了;或者更隐蔽的:后台编辑器里按钮没有边框、列表没有圆点,因为 Tailwind 的 preflight 把浏览器默认样式(连带区块编辑器依赖的一部分)重置了。
这两个坑的本质是同一件事:Tailwind 是一个「按需生成」的构建工具,它只认静态出现在源码里的类名;而 WordPress 的类名大量来自 PHP 运行时拼接与核心样式表。把这两者的边界理清楚,Tailwind 才能真正用在生产项目上。
原理分析:三个必须解决的冲突
冲突一:purging 与动态类名。 Tailwind 扫描 content 里指定的文件,用正则提取可能的类名字符串,只生成命中的那部分 CSS。如果类名是运行时拼出来的(col-span-{{ $n }}、<?php echo $align === 'left' ? 'text-left' : 'text-right'; ?>),扫描器看不到最终字符串,产物里就没有对应规则。解决办法是 safelist(白名单)或用完整的静态类名分支。
冲突二:preflight 与编辑器/内容区。 preflight 重置了 ul { list-style: none }、h1 { font-size: inherit }、button { background: transparent } 等。这在你自己的组件里是好事,但区块编辑器输出的内容(.wp-block-list、.wp-block-quote)依赖核心样式与主题补充样式,一重置就「散架」。处理方式有三:关闭 preflight、只在自己写的结构里用 Tailwind、或给内容区单独补一套排版样式(@tailwindcss/typography 的 prose 是常见选择)。
冲突三:与 theme.json / 区块默认样式的优先级。 区块主题会输出全局样式(CSS 变量与类名),Tailwind 的工具类优先级(单层类)常常打不过 .wp-block-button__link 这种带层级的规则。需要时用 @layer 或提高选择器权重,别靠 !important 硬来(AMP 环境下 !important 还会导致验证失败,见 AMP 与移动适配)。
保姆级实操
第一步:初始化构建链
cd wp-content/themes/mimangfei
npm init -y
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
tailwind.config.js(Tailwind v3):
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./**/*.php',
'./template-parts/**/*.php',
'./inc/**/*.php',
'./js/**/*.js',
],
theme: {
extend: {
colors: {
brand: {
DEFAULT: '#2563eb',
dark: '#1d4ed8',
},
ink: '#1a1a1a',
},
maxWidth: {
content: '720px',
container: '1200px',
},
fontFamily: {
sans: [ 'system-ui', '-apple-system', 'PingFang SC', 'Microsoft YaHei', 'sans-serif' ],
},
},
},
plugins: [
require( '@tailwindcss/typography' ),
],
};
content 一定要覆盖所有可能写类名的 PHP 文件,包括 template-parts/ 与 inc/。漏掉某个目录的表现是「只有那个目录的模板样式失效」。
Tailwind v4 改为 CSS 优先配置,写法不同:
@import "tailwindcss";
@theme {
--color-brand: #2563eb;
--color-ink: #1a1a1a;
--font-sans: system-ui, -apple-system, "PingFang SC", sans-serif;
}
第二步:入口样式文件
/* src/input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply text-ink antialiased;
}
/* preflight 重置后,给内容区补回列表与引用样式 */
.entry-content ul { @apply list-disc pl-6 my-4; }
.entry-content ol { @apply list-decimal pl-6 my-4; }
.entry-content blockquote { @apply border-l-4 border-brand pl-4 italic my-6; }
}
第三步:PostCSS 与构建脚本
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
{
"scripts": {
"dev": "tailwindcss -i ./src/input.css -o ./assets/css/app.css --watch",
"build": "NODE_ENV=production tailwindcss -i ./src/input.css -o ./assets/css/app.css --minify"
}
}
Windows 下 NODE_ENV=production 语法不兼容,改用 cross-env 或在 package.json 里写 "build:win": "set NODE_ENV=production && tailwindcss ..."。
第四步:在主题里加载产物
add_action( 'wp_enqueue_scripts', 'mimangfei_assets' );
function mimangfei_assets() {
wp_enqueue_style(
'mimangfei-app',
get_template_directory_uri() . '/assets/css/app.css',
array(),
wp_get_theme()->get( 'Version' )
);
}
版本号用主题版本或 filemtime(),避免改了样式用户端还命中旧缓存:
$css_path = get_template_directory() . '/assets/css/app.css';
$css_ver = file_exists( $css_path ) ? filemtime( $css_path ) : wp_get_theme()->get( 'Version' );
第五步:编辑器里也要有同样的样式
add_action( 'after_setup_theme', function() {
add_theme_support( 'editor-styles' );
add_editor_style( 'assets/css/editor.css' );
} );
建议单独出一个 editor.css(入口为 src/editor.css,content 里只包含 base + typography,不含 preflight 的重置部分),避免后台样式把编辑器自身的 UI 打乱:
/* src/editor.css */
@tailwind components;
@tailwind utilities;
第六步:处理动态类名
<?php
// 不推荐:类名被拼接,Tailwind 扫不到
printf( '<div class="col-span-%d">', absint( $columns ) );
// 推荐:完整的静态类名分支
$col_class = array(
2 => 'md:grid-cols-2',
3 => 'md:grid-cols-3',
4 => 'md:grid-cols-4',
);
$class = isset( $col_class[ $columns ] ) ? $col_class[ $columns ] : 'md:grid-cols-3';
printf( '<div class="grid gap-6 %s">', esc_attr( $class ) );
确实无法避免时用 safelist:
module.exports = {
content: [ './**/*.php' ],
safelist: [
'md:grid-cols-2',
'md:grid-cols-3',
'md:grid-cols-4',
{
pattern: /^(bg|text)-(brand|ink|gray)-(100|200|500|700)$/,
variants: [ 'hover', 'focus' ],
},
],
};
safelist 会显著增大产物体积,只加真正需要的那一组,不要图省事用宽泛的正则。
第七步:按需裁剪与体积检查
npm run build
ls -la assets/css/app.css
一个中型企业站的 Tailwind 产物通常在 20–60KB(gzip 后 6–12KB),若超过 150KB 说明 safelist 过宽或 content 扫描范围把 node_modules 也扫进去了。用 npx tailwindcss -i src/input.css -o /dev/null --content "./**/*.php" 配合 --minify 的产物体积对比可快速定位。
常见踩坑
- content 只写了
./**/*.html:WordPress 没有 HTML 模板,产物几乎为空,页面裸奔。 - 动态类名被 purge:本地
npm run dev时 Tailwind 的 JIT 会生成更多类,构建后消失,务必用生产产物验证一遍关键页面。 - preflight 清掉了编辑器内容样式:列表无圆点、引用无线条,用
@layer base补回或引入prose。 - 与区块核心样式打架:按钮样式被
.wp-block-button__link覆盖,需要在components层针对区块类名写规则。 - 把
node_modules与vendor扫进 content:产物体积暴涨且生成大量无用类,用.gitignore之外的显式路径列表。 - 案例:某外贸站用 Tailwind 重做主题后,产品列表的「三列/四列」切换在开发环境正常,上线后所有页面都变成单列。原因是模板里用
'grid-cols-' . $cols拼接类名,生产构建时被 purge。修复方式是把可选类名写成完整字符串的映射数组,并对历史数据里已保存的列数值做兜底映射。 - 编辑器样式用了带 preflight 的同一份产物:后台编辑器的按钮、输入框被重置,看起来像后台坏了。
- 忘记提交构建产物:Git 忽略
assets/css/app.css,部署后样式为空,应在部署脚本里执行npm run build,或明确把产物纳入版本管理。
总结
落地顺序:先定 content 扫描范围与产物入口 → 写 @layer base 补回内容区基础排版 → 编辑器单独出一份 CSS → 再处理动态类名(优先静态分支,其次 safelist)→ 最后用生产产物逐页验证。Tailwind 与 WordPress 并不冲突,冲突的是「按需生成」与「运行时拼类名」这两种思维。若你的项目是区块主题,建议把设计 token 放进 theme.json 全局样式 与 Tailwind 的 theme.extend 保持同步,避免两套颜色定义各自漂移;若是经典主题,可配合 用 Underscores 从零做主题 的骨架一起用。
