📚 教程目录
一、Tailwind CSS 是什么?
Tailwind CSS 是一个实用优先(Utility-first)的 CSS 框架。和传统框架(如 Bootstrap)提供预定义组件(.btn、.card)不同,Tailwind 提供的是原子化的工具类:
/* 传统写法 */
.btn-primary {
background: #20a53a;
color: white;
padding: 10px 20px;
border-radius: 4px;
}
/* Tailwind 写法 - 直接在 HTML 中使用工具类组合 */
<button class="bg-green-600 text-white px-4 py-2 rounded">
按钮
</button>✨ 核心优势
| 特性 | 说明 |
|---|---|
| 开发效率 | 无需写 CSS,直接在 HTML 中组合类名 |
| 体积小 | 构建时自动删除未使用的类,最终 CSS 文件极小 |
| 无命名冲突 | 不需要担心类名重复 |
| 响应式内置 | 用 md:、lg: 前缀轻松实现响应式 |
| 可定制性强 | 通过配置文件自定义主题 |
二、环境准备与安装
2.1 两种安装方式对比
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| CDN 方式 | 快速测试、小工具 | 无需配置,直接可用 | 体积大(~3MB),无法定制 |
| 构建方式 | 正式主题、长期维护 | 按需编译,可定制,体积小 | 需要 Node.js 环境 |
推荐:正式项目使用构建方式,符合 project_rules.md 的规范要求。
2.2 构建方式安装步骤
步骤1:进入主题目录
cd usr/themes/your-theme-name步骤2:初始化 npm 项目
npm init -y步骤3:安装 Tailwind CSS 及依赖
npm install -D tailwindcss postcss autoprefixer cssnano注:postcss和autoprefixer是 Tailwind 的必备依赖
步骤4:初始化 Tailwind 配置
npx tailwindcss init -p这个命令会创建两个文件:
tailwind.config.js- Tailwind 主配置文件postcss.config.js- PostCSS 配置文件
三、基础配置
3.1 配置 tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
// 告诉 Tailwind 扫描哪些文件中的类名
content: [
'./*.php',
'./templates/**/*.php',
'./assets/js/**/*.js',
'../../plugins/**/*.php', // 如果需要扫描插件
],
theme: {
extend: {
// 扩展主题色 - 适配您的宝塔绿色调
colors: {
primary: {
DEFAULT: '#20a53a',
50: '#f0fdf4',
100: '#dcfce7',
500: '#20a53a',
600: '#18802c',
700: '#166b34',
},
},
// 扩展间距,匹配 project_rules.md 中的 mt-1,2,3
spacing: {
'18': '4.5rem',
'88': '22rem',
},
},
},
plugins: [],
// 暗黑模式支持(后面会详细讲)
darkMode: 'class',
}3.2 创建主 CSS 文件
在主题目录下创建 style.css(如果已存在则修改):
/* style.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 自定义组件层 - 复用 project_rules.md 的工具类 */
@layer components {
/* 卡片组件 */
.card {
@apply bg-white rounded-lg shadow-md p-6;
@apply dark:bg-gray-800 dark:text-white;
}
.card__header {
@apply border-b border-gray-200 pb-4 mb-4;
@apply dark:border-gray-700;
}
.card__title {
@apply text-xl font-bold text-gray-900;
@apply dark:text-gray-100;
}
/* 按钮组件 */
.btn-primary {
@apply px-4 py-2 bg-primary text-white rounded-md;
@apply hover:bg-primary-600 transition-colors;
@apply focus:outline-none focus:ring-2 focus:ring-primary-500;
}
/* 复用 project_rules.md 的工具类 */
.text-muted {
@apply text-gray-500 dark:text-gray-400;
}
.mt-1 {
@apply mt-1;
}
.mt-2 {
@apply mt-2;
}
.mt-3 {
@apply mt-3;
}
}3.3 配置 package.json 脚本
{
"scripts": {
"dev": "npx tailwindcss -i ./style.css -o ./assets/css/style.min.css --watch",
"build": "npx tailwindcss -i ./style.css -o ./assets/css/style.min.css --minify"
}
}3.4 开发流程
# 开发时:监听文件变化,实时编译
npm run dev
# 生产环境:编译并压缩
npm run build3.5 在主题中引用
在 header.php 中添加:
<!-- 引用编译后的 CSS 文件 -->
<link rel="stylesheet" href="<?php $this->options->themeUrl('assets/css/style.min.css'); ?>">四、在主题中使用
4.1 基础用法示例
文章列表卡片
<!-- index.php -->
<div class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<?php while($this->next()): ?>
<article class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow">
<!-- 文章缩略图 -->
<?php if ($this->fields->thumbnail): ?>
<img src="<?php $this->fields->thumbnail(); ?>"
class="w-full h-48 object-cover">
<?php endif; ?>
<div class="p-6">
<!-- 标题 -->
<h2 class="text-xl font-bold mb-2">
<a href="<?php $this->permalink() ?>"
class="hover:text-primary-600 transition-colors">
<?php $this->title() ?>
</a>
</h2>
<!-- 元信息 -->
<div class="flex items-center text-sm text-muted mb-4">
<span class="mr-4">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<?php $this->date('Y-m-d'); ?>
</span>
<span><?php $this->category(','); ?></span>
</div>
<!-- 摘要 -->
<p class="text-muted mb-4 line-clamp-3">
<?php $this->excerpt(100, '...'); ?>
</p>
<!-- 阅读更多 -->
<a href="<?php $this->permalink() ?>"
class="inline-flex items-center text-primary hover:text-primary-600">
阅读更多
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7" />
</svg>
</a>
</div>
</article>
<?php endwhile; ?>
</div>
</div>分页导航
<!-- 分页部分 -->
<div class="flex justify-center mt-8">
<div class="flex space-x-2">
<?php $this->pageNav('«', '»', 1, '...', [
'wrapTag' => 'div',
'wrapClass' => 'flex space-x-2',
'itemTag' => 'span',
'currentClass' => 'px-3 py-2 bg-primary text-white rounded',
'prevClass' => 'px-3 py-2 border border-gray-300 rounded hover:bg-gray-100',
'nextClass' => 'px-3 py-2 border border-gray-300 rounded hover:bg-gray-100',
]); ?>
</div>
</div>评论列表
<!-- comments.php -->
<div class="mt-8">
<h3 class="text-lg font-bold mb-4">评论</h3>
<?php $this->comments()->to($comments); ?>
<?php while($comments->next()): ?>
<div class="flex space-x-4 mb-6 p-4 bg-gray-50 rounded-lg">
<div class="flex-shrink-0">
<img src="<?php $comments->gravatar(40); ?>"
class="w-10 h-10 rounded-full">
</div>
<div class="flex-1">
<div class="flex items-center justify-between mb-2">
<span class="font-medium"><?php $comments->author(); ?></span>
<span class="text-sm text-muted"><?php $comments->date('Y-m-d H:i'); ?></span>
</div>
<div class="text-gray-700">
<?php $comments->content(); ?>
</div>
</div>
</div>
<?php endwhile; ?>
<!-- 评论表单 -->
<?php if($this->allow('comment')): ?>
<form method="post" action="<?php $this->commentUrl() ?>" class="mt-6">
<h4 class="font-medium mb-4">发表评论</h4>
<?php if(!$this->user->hasLogin()): ?>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<input type="text" name="author" placeholder="昵称 *" required
class="form-control w-full px-3 py-2 border border-gray-300 rounded-md">
<input type="email" name="mail" placeholder="邮箱 *" required
class="form-control w-full px-3 py-2 border border-gray-300 rounded-md">
<input type="url" name="url" placeholder="网站"
class="form-control w-full px-3 py-2 border border-gray-300 rounded-md">
</div>
<?php endif; ?>
<textarea name="text" rows="4" placeholder="评论内容..." required
class="form-control w-full px-3 py-2 border border-gray-300 rounded-md mb-4"></textarea>
<button type="submit" class="btn-primary">
提交评论
</button>
</form>
<?php endif; ?>
</div>4.2 常用工具类速查表
| 类别 | Tailwind 类 | 作用 |
|---|---|---|
| 布局 | container mx-auto | 居中容器 |
flex、grid | 弹性/网格布局 | |
grid-cols-3 | 3列网格 | |
gap-4 | 网格间距 | |
| 间距 | p-4 | 内边距 1rem |
px-4 | 左右内边距 | |
mt-2 | 上外边距 0.5rem | |
space-x-4 | 子元素水平间距 | |
| 排版 | text-xl | 字体大小 1.25rem |
font-bold | 粗体 | |
text-center | 居中 | |
line-clamp-3 | 最多3行后省略 | |
| 颜色 | text-primary | 主色文字 |
bg-gray-100 | 浅灰背景 | |
border-gray-300 | 灰色边框 | |
| 交互 | hover:bg-primary-600 | 悬停变深 |
transition-shadow | 阴影过渡 | |
cursor-pointer | 指针样式 |
五、响应式设计
Tailwind 采用 移动优先(Mobile First) 的响应式策略。断点前缀表示 min-width:
| 前缀 | 断点 | 说明 |
|---|---|---|
sm: | 640px | 小屏幕 |
md: | 768px | 中等屏幕 |
lg: | 1024px | 大屏幕 |
xl: | 1280px | 超大屏幕 |
2xl: | 1536px | 巨幕 |
5.1 响应式示例
<div class="
<!-- 手机:单列,小字体 -->
grid grid-cols-1 text-sm
<!-- 平板:两列,中等字体 -->
md:grid-cols-2 md:text-base
<!-- 桌面:三列,大字体 -->
lg:grid-cols-3 lg:text-lg
<!-- 间距也响应 -->
gap-4 md:gap-6 lg:gap-8
">
<!-- 内容 -->
</div>5.2 复杂响应式示例
<!-- 侧边栏布局:手机垂直,桌面水平 -->
<div class="flex flex-col md:flex-row">
<!-- 侧边栏:手机占满,桌面固定宽度 -->
<aside class="
w-full md:w-64
bg-gray-50 p-4
md:bg-transparent
">
侧边栏内容
</aside>
<!-- 主内容:自适应 -->
<main class="flex-1 p-4">
主内容
</main>
</div>六、暗黑模式
6.1 启用暗黑模式
在 tailwind.config.js 中配置:
module.exports = {
darkMode: 'class', // 或 'media'(跟随系统)
// ...
}6.2 创建暗黑模式切换脚本
在主题中创建 assets/js/theme.js:
// theme.js
(function() {
// 获取保存的主题或跟随系统
const getTheme = () => {
const saved = localStorage.getItem('theme');
if (saved) return saved;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};
// 应用主题
const applyTheme = (theme) => {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', theme);
};
// 初始化
applyTheme(getTheme());
// 切换函数
window.toggleTheme = () => {
const isDark = document.documentElement.classList.contains('dark');
applyTheme(isDark ? 'light' : 'dark');
};
})();在 footer.php 中引入:
<script src="<?php $this->options->themeUrl('assets/js/theme.js'); ?>"></script>6.3 暗黑模式切换按钮
<button onclick="toggleTheme()"
class="p-2 rounded-lg bg-gray-200 dark:bg-gray-700
text-gray-900 dark:text-gray-100
hover:bg-gray-300 dark:hover:bg-gray-600
transition-colors">
<!-- 太阳/月亮图标 -->
<svg class="w-5 h-5 dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>6.4 暗黑模式样式示例
/* 在 @layer components 中 */
@layer components {
.card {
@apply bg-white text-gray-900;
@apply dark:bg-gray-800 dark:text-gray-100;
}
.nav-link {
@apply text-gray-700 hover:text-primary;
@apply dark:text-gray-300 dark:hover:text-primary-400;
}
.border-default {
@apply border-gray-200;
@apply dark:border-gray-700;
}
}七、与 project_rules.md 结合
7.1 迁移现有工具类
将 project_rules.md 中的工具类用 Tailwind 实现:
| project_rules.md 类 | Tailwind 实现 | 说明 |
|---|---|---|
.text-muted | text-gray-500 dark:text-gray-400 | 灰色文字 |
.text-success | text-green-600 | 成功绿色 |
.text-danger | text-red-600 | 危险红色 |
.text-center | text-center | 居中 |
.mt-1 | mt-1 | 上边距 0.25rem |
.mt-2 | mt-2 | 上边距 0.5rem |
.mt-3 | mt-3 | 上边距 0.75rem |
.d-flex | flex | 弹性布局 |
.justify-between | justify-between | 两端对齐 |
.card | bg-white rounded-lg shadow p-4 | 卡片容器 |
7.2 保留兼容层
在 style.css 中添加兼容类,确保旧代码也能工作:
/* 兼容 project_rules.md 旧类名 */
@layer utilities {
.text-muted {
@apply text-gray-500 dark:text-gray-400;
}
.text-success {
@apply text-green-600;
}
.text-danger {
@apply text-red-600;
}
.btn-sm {
@apply px-3 py-1.5 text-sm;
}
.btn-lg {
@apply px-6 py-3 text-lg;
}
}7.3 资源加载顺序
根据 project_rules.md 的要求:
<!-- header.php -->
<!-- 1. 基础样式 -->
<link rel="stylesheet" href="/tools/common/css/base.css">
<!-- 2. Tailwind 生成的样式 -->
<link rel="stylesheet" href="<?php $this->options->themeUrl('assets/css/style.min.css'); ?>">
<!-- 3. 主题私有样式(如有需要) -->
<link rel="stylesheet" href="<?php $this->options->themeUrl('style.css'); ?>">八、常见问题
Q1: 为什么我添加的 Tailwind 类不起作用?
A: 检查以下几点:
tailwind.config.js中的content配置是否正确包含你的 PHP 文件- 是否运行了
npm run build或npm run dev - 是否正确引入了编译后的 CSS 文件
Q2: 如何添加自定义颜色?
A: 在 tailwind.config.js 的 theme.extend.colors 中添加:
module.exports = {
theme: {
extend: {
colors: {
brand: {
light: '#3abff8',
DEFAULT: '#0284c7',
dark: '#0b5e8c',
},
},
},
},
}使用:text-brand、bg-brand-light
Q3: 如何使用任意值(如特殊宽度)?
A: 使用方括号语法:
<div class="w-[139px] h-[77px] bg-[#165DFF]">
自定义尺寸和颜色
</div>Q4: 如何复用重复的类组合?
A: 使用 @apply 在 CSS 中提取组件:
@layer components {
.blog-card {
@apply bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow;
}
}Q5: 生产环境 CSS 文件太大怎么办?
A: Tailwind 在生产模式 (npm run build) 会自动清除未使用的类。确保 content 配置正确即可。
Q6: 可以在插件中使用 Tailwind 吗?
A: 可以。两种方式:
- 独立编译:插件自带 Tailwind 配置,独立生成 CSS
- 共用主题:在主题的
tailwind.config.js的content中添加插件路径
content: [
'./**/*.php',
'../../plugins/PluginName/**/*.php', // 扫描插件
]📝 总结
通过本教程,您已经掌握了:
- ✅ Tailwind CSS 的核心概念和优势
- ✅ 在 Typecho 主题中的完整安装配置流程
- ✅ 响应式设计和暗黑模式的实现方法
- ✅ 如何与现有的
project_rules.md规范结合
推荐的开发流程
# 1. 进入主题目录
cd usr/themes/your-theme
# 2. 安装依赖(只需一次)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# 3. 开发时监听
npm run dev
# 4. 开发完成,编译生产版本
npm run build
用了三年Typecho,一直用Bootstrap,今天试着换成Tailwind。一开始很不习惯直接在HTML里写那么多类名,但用了一天就回不去了。不用切来切去找样式文件的感觉太爽了。
The comparison table between CDN and build approaches is super helpful. I was using CDN before and wondering why my CSS was so bloated. Switched to build after reading this and cut file size by 90%.
终于有人写Typecho的Tailwind教程了!之前一直不知道怎么在主题里配置,照着这篇文章一步步做,半小时就搞定了。现在开发主题爽多了,不用再想类名想半天。