如果你是从传统 CSS、Sass 或 Less 过来的,第一次看到 Tailwind 写出来的按钮,大概率不会立刻觉得它“优雅”。你的第一反应可能更直接:怎么把十几个 class 全塞进标签了?我们以前花了那么多时间强调结构与样式分离,现在又把样式写回 HTML,这不是绕了一圈回到内联样式吗?
这个疑问一点也不外行。相反,它刚好碰到了 Tailwind 最值得弄懂的地方。假如我们只是把 padding: 12px 改写成 py-3,把 background: blue 改写成 bg-blue-600,却没有改变组织样式的方法,那确实只是换了一套缩写。Tailwind 真正改变的是另一件事:它让我们从一套有限、统一、可组合的设计选项里搭界面,并把样式与它负责的那段结构放在一起。
这一页先不急着背工具类。我们会用同一个真实组件对比传统 CSS 与 Tailwind,看看那些 class 到底把什么复杂度搬到了眼前;然后用 Tailwind CSS v4 搭好一个能运行的项目。等环境跑起来,下一页再集中处理间距、颜色、状态变体和响应式语法。
假设产品里有一个“保存学习进度”的按钮。它需要有合适的内边距、蓝色背景、白色文字、圆角、阴影,还要在鼠标悬停、键盘聚焦和按钮禁用时给出不同反馈。
传统 CSS 往往把这些决定收进一个看起来很干净的类名:
<button class="save-button" type="button">
保存学习进度
</button>.save-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.25rem;
border: 0;
border-radius: 0.75rem;
background: #2563eb;
color: #ffffff;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
box-shadow: 0 4px 6px rgb(15 23 42 / 0.12);
cursor: pointer;
transition: background-color 150ms, box-shadow 150ms;
}
.save-button:hover {
background: #1d4ed8;
}
.save-button:focus-visible {
outline: 2px solid #60a5fa;
outline-offset: 2px;
}
.save-button:disabled {
cursor: not-allowed;
opacity: 0.5;
}这段代码没有错,而且语义类名也很好懂。不过“HTML 很干净”只说明复杂度被搬走了,不代表复杂度消失了。当你想确认按钮的圆角是多少,需要从模板跳到样式表;想把阴影调轻,又要确认 .save-button 是否被别的选择器覆盖;想知道另一个页面上的同名按钮会不会一起变化,还得搜索它的使用范围。
下面这张图把这种来回定位的成本画了出来:按钮本身很简单,真正让人分心的是结构与样式之间的往返。

右边的“就地调整”并不代表样式规则消失了,只是修改入口回到了组件旁边。带着这个区别,再看 Tailwind 版本会更容易理解。
换成 Tailwind 后,同一个按钮可以这样写:
<button
class="inline-flex items-center justify-center rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white shadow-md transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-400 disabled:cursor-not-allowed disabled:opacity-50"
type="button"
>
保存学习进度
</button>现在 class 的确很多,甚至第一眼像一碗“class 汤”。但每个 class 的职责很窄:px-5 只处理水平方向内边距,rounded-xl 只处理圆角,hover:bg-blue-700 只在悬停条件成立时改变背景色。你删掉 shadow-md,影响的就是这个元素的阴影;你把 rounded-xl 换成 rounded-full,不用担心远处某个选择器也跟着变。
初学时的不适感通常会经历下面这个转折:先看见一串标签,随后才逐渐看见它们按职责形成的秩序。

一旦能把类名分成布局、间距、外观与状态几组,长列表就不再是一团无法解释的字符,而是一张贴在元素旁边的样式清单。
所以真正的对比并不是“短 HTML 对长 HTML”,而是两种复杂度摆放方式:
两种方式都能写出好代码,也都能被写乱。Tailwind 不是用 class 数量证明自己更先进,它只是选择让局部样式显式出现。
你可以在下面的解剖台里切换传统 CSS 与 Tailwind,再逐项点亮间距、色板、圆角和交互状态。观察重点不是哪边字符更少,而是哪边能让你更快回答“这个按钮为什么长这样”。
切换完以后,再试着关闭一个视觉决定。你会发现工具类列表虽然长,每一项的作用范围却很容易单独验证。
这里的 class 多,不等于浏览器会复制十几份样式。Tailwind 在构建时识别项目里出现的工具类,并生成对应 CSS。多个元素重复使用 px-5,仍然复用同一条 CSS 规则。
把样式写在标签附近,确实让 Tailwind 与内联样式有一点相似。不过它们解决问题的能力不同。
先看真正的内联样式:
<button
style="padding: 12px 20px; border-radius: 12px; background: #2563eb; color: white;"
>
保存学习进度
</button>这里的 12px、20px、12px 和颜色值都可以随手改成任何数字。自由度很高,代价是每个人都可能做出一套新的间距。第一个页面用 12px,第二个页面觉得 13px 更顺眼,第三个页面又写成 0.8rem。单看都没大问题,项目一大,界面就会出现许多“差不多,但不完全一样”的按钮。
px-5 py-3 rounded-xl bg-blue-600 则是在一套既定刻度中做选择。工具类背后连接着统一的间距、圆角、颜色和文字层级。你仍然能定制这套系统,也能为真正的一次性需求写任意值,但日常开发先使用共同刻度。少做几次随意决定,反而更容易保持一致。
工具类还可以直接表达条件。hover: 处理悬停,focus-visible: 处理键盘焦点,disabled: 处理禁用状态,响应式前缀可以让规则只在某个屏幕范围生效。普通 style 属性不能直接写伪类和媒体查询。换句话说,Tailwind 把“样式靠近结构”的便利保留下来,同时给了我们一套可响应状态、可适配屏幕、受主题约束的语言。
当然,内联样式并没有因此被判出局。如果颜色来自数据库,元素位置来自拖拽结果,或者某个数值在运行时连续变化,内联样式与 CSS 变量通常更自然:
function ProgressBar({ percent }) {
return (
<div className="h-2 overflow-hidden rounded-full bg-slate-200">
<div
className="h-full rounded-full bg-blue-600"
style={{ width: `${percent}%` }}
/>
</div>
);
}这里 h-2、rounded-full、bg-blue-600 都来自稳定的设计选择,进度宽度则来自运行时数据。两种方法各做自己擅长的事,不需要为了“纯 Tailwind”硬把动态值塞进类名。
做界面时,真正消耗时间的往往不是写出 padding 这个属性,而是不断判断“这里到底该用 14px、15px 还是 16px”。如果整个团队每次都从零决定,代码当然很自由,结果却容易松散。
Tailwind 的默认工具类像一套共享的尺子。p-4、gap-6、text-sm、rounded-lg 不只是短写,它们让开发者在同一套刻度上交流。评审时说“这里从 gap-4 改成 gap-6”,比“把第二层容器里那两个元素的距离再加 7px”更明确。设计稿发生变化时,也更容易看出哪些地方使用了相同规则。
这套共享尺子的价值,可以用“先收束选择,再自由组合”来理解。图中左侧的随手取值看似灵活,右侧的固定刻度却更容易拼出一致的成品。

约束落在基础材料上,组件层反而不用每次重新发明颜色、间距和圆角。我们仍然可以组合出不同界面,只是每个决定都有共同语言。
这种约束带来三个很实际的结果。
第一,界面更容易保持节奏。相同层级的卡片会自然使用相近的内边距,相同类型的标题会落在有限的字号范围内。你不是不能破例,而是破例会变得显眼,需要有理由。
第二,修改范围更可预测。工具类通常只作用于当前元素的一项或一小组属性,不必沿着选择器权重追查覆盖关系。对于长期没有碰过的页面,这种局部性很省心。
第三,样式不会随着页面数量等比例增长。新页面往往复用项目中已经出现的工具类,构建结果复用同一批规则。增加一个卡片不意味着一定再增加一组 .card-title、.card-body、.card-action。
但别把“约束”误解成“永远只能用默认值”。品牌颜色、字体、阴影和间距都可以进入主题,变成团队自己的工具类。第三页会专门讲主题与配置。这里先记住判断顺序:常规需求优先使用共同刻度;反复出现的新视觉决定放进主题;真正一次性的值再考虑任意值或普通 CSS。顺序反过来,Tailwind 也会被写成一堆散乱的魔法数字。
工具类靠组合工作,列表变长是正常现象,但“正常”不等于可以随便堆。一个实用办法是让 class 大致按视觉逻辑排列:先放布局和尺寸,再放间距、外观、文字,最后放状态和响应式变体。团队不一定要采用完全相同的顺序,但至少要稳定。
<button
class="inline-flex items-center justify-center px-5 py-3 rounded-xl bg-blue-600 text-sm font-semibold text-white shadow-md transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-400"
>
保存学习进度
</button>如果格式化工具会自动整理 class,就让工具负责排序,人不要在代码评审里反复争论谁排前面。可读性的核心是能快速辨认一组类在负责什么,不是手工维持某种神秘次序。
重复也需要分情况看。相同按钮在一个组件里出现十次,通常应该抽成 SaveButton 之类的模板组件,让结构、行为和样式一起复用。只为了把一长串 class 藏起来就立刻创建 .btn-primary,未必真的减少复杂度;你可能只是重新制造了“看到类名还得跳去另一个文件”的问题。
这里有个很实用的分界线:先问“重复的是一个视觉片段,还是一个有身份的界面单元”。如果只是两处都碰巧用了 flex items-center gap-2,这点重复不值得马上抽象,因为两处布局将来可能各自变化。如果它们都是“保存按钮”,共享同样的点击行为、加载状态、禁用规则和文字结构,那就应该抽成组件。组件名称负责表达业务含义,工具类负责表达它具体长什么样,两者并不冲突。
function SaveButton({ saving = false, children }) {
return (
<button
className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
type="submit"
disabled={saving}
>
{saving ? "正在保存…" : children}
</button>
);
}使用组件以后,页面看到的是 <SaveButton>保存学习进度</SaveButton>,并没有到处复制长 class;维护者打开组件文件,又能在同一个位置看到结构、状态与视觉规则。所谓“语义”没有消失,它从一个只负责样式的 .save-button 类名,移动到了真正封装行为的 SaveButton 组件上。
另一方面,如果你维护的是服务端模板,一个很小的单标签按钮却散落在大量页面里,抽完整组件成本很高,那么写一个自定义组件类也完全合理。Tailwind 允许和普通 CSS 共存。它提供的是一种主力工作流,不是要求所有项目宣誓只写工具类。
同一个元素上不要同时保留两个互相冲突的工具类,例如 flex grid 或 bg-blue-600 bg-red-600。class 在属性中的书写先后并不能可靠地当成覆盖顺序。条件变化时,应在代码里选择一组完整、明确的类名。
在 React 或 Vue 中,还要避免拼接半截类名:
// 不要这样写:构建器看见的是零散字符串,不一定能发现最终类名
function Badge({ color }) {
return <span className={`bg-${color}-100 text-${color}-800`}>新消息</span>;
}改成把完整类名写在源码里:
const badgeStyles = {
blue: "bg-blue-100 text-blue-800",
amber: "bg-amber-100 text-amber-800",
emerald: "bg-emerald-100 text-emerald-800",
};
function Badge({ color = "blue" }) {
return <span className={badgeStyles[color]}>新消息</span>;
}这不只是为了让构建器识别。显式映射还能让不同状态使用更合适的明暗组合,并限制组件接受的视觉选项。color="随便什么" 不再悄悄制造一个不存在的类名。
Tailwind 很适合组件化应用、后台界面、产品原型和需要统一设计语言的长期项目。你经常在 React、Vue、Svelte 等组件里同时修改结构与样式,工具类能让一次修改停留在同一个文件中;团队也能借助共同的主题刻度,减少页面之间的细小偏差。
它也很适合从设计系统出发的开发。颜色、间距、圆角与字体一旦收进主题,页面不是靠每位开发者临场发挥,而是在同一套材料上组合。Tailwind 的“快”主要来自减少命名、跳转与重复决策,而不只是少敲几个字符。
下面这些情况则需要更诚实地评估:
还有一种常见误判:因为 class 看着多,就认定代码一定难维护。真正该问的是,组件边界是否清楚、重复是否被合理封装、设计值是否统一、状态是否明确。一个有二十个工具类但职责清晰的按钮,可能比一个背后叠着六层覆盖规则的 .button 更好维护;反过来也一样。
开始安装前,先确认电脑上有可用的 Node.js 与 npm。执行下面两条命令,能看到版本号就说明命令行环境已经接通:
node -v
npm -v新项目通常优先选 Vite 插件。已经有构建体系、只想单独处理一份 CSS,或者在纯 HTML 项目中学习,可以用 CLI。浏览器脚本适合几分钟的试验,不适合正式发布。
如果你还在几条路线之间犹豫,先看清它们各自从哪里出发、最后交付什么。下面的手绘路线图把选择依据压缩成了一张图。

四条路线都能让工具类产生效果,但学习试验与正式构建的要求并不一样。先按项目现状选入口,比照着某段命令盲敲更可靠。
接下来可以用交互路线图做一次选择练习:先选你的目标和现有工具链,再看系统建议的 v4 入口,同时留意哪些命令属于旧版教程。
完成选择后,把推荐路线和上表对照一下。真正需要记住的不是四套命令,而是“生产项目先接入构建链,短时试验才使用浏览器脚本”这条边界。
如果你从 v3 教程跳过来,最容易踩的坑是照抄旧命令。v4 的主 CSS 入口是:
@import "tailwindcss";它不再把下面三行当作新项目的默认入口:
/* 这是常见的 v3 写法,本课程的 v4 新项目不这样开始 */
@tailwind base;
@tailwind components;
@tailwind utilities;v4 还会自动检测常见项目文件中的完整类名,新项目通常不需要先创建 tailwind.config.js,也不需要先维护一份 content 路径数组。主题与特殊扫描范围仍然可以配置,但不是让第一个按钮显示出来的前置条件。
可以把构建过程想成一条很短的生产线:你在模板或组件中写完整工具类,Tailwind 扫描这些文本,把匹配到的规则编进 CSS,Vite 或 CLI 再把 CSS 交给浏览器。页面运行时不需要为每个按钮执行一套 Tailwind JavaScript。理解这条链以后,排错会简单很多:类名没被发现就检查扫描,规则已经生成却没效果就检查 CSS 是否载入,浏览器里样式被划掉则检查是否存在冲突。
看到教程要求先执行 npx tailwindcss init -p、再配置 content、最后写三条 @tailwind 指令时,先检查它讲的是不是 v3。旧教程并非全部失效,但安装骨架不能原样套进 v4。
下面用最轻的 Vanilla 模板演示。选择 React 或 Vue 时,Tailwind 部分仍然是安装插件、注册插件、在 CSS 中导入三步,只是组件里分别使用 className 或 class。
创建项目并安装模板依赖。终端会询问或直接创建名为 tailwind-first 的目录。
npm create vite@latest tailwind-first -- --template vanilla
cd tailwind-first
npm install安装 Tailwind 本体与 v4 的 Vite 插件。这里不必额外安装旧教程里常见的 Autoprefixer 组合。
npm install tailwindcss @tailwindcss/vite跑到这里,页面已经从朴素结构变成了一个能看、能悬停、能用键盘聚焦的组件。下面的三阶段图把这次变化拆成了结构、工具类组合和最终结果。

注意中间那一步:我们没有一次写出一个庞大的自定义样式,而是把间距、颜色、圆角和阴影这些小决定逐个装到组件上。
别急着逐个背上面所有 class。现在只做三个小改动:把 rounded-xl 改成 rounded-full,把 bg-blue-600 改成 bg-emerald-600,再把 px-5 改成 px-8。浏览器中的圆角、颜色和水平内边距会立刻变化。这个“改一处,只影响一项局部视觉”的手感,比背定义更能解释工具类为什么好用。
下面的组件调音台把这种修改变成了可操作实验。依次调整密度、强调色、圆角与容器宽度,看看预览和类名怎样同步变化。
操作时尤其留意两件事:固定刻度怎样让不同选项保持协调,以及视口变窄后组件怎样重新排布。这就是工具类、设计系统和响应式规则在同一个成品里的配合。
如果手头只有 HTML 和 CSS,不必为了 Tailwind 额外搭一个应用框架。v4 的 CLI 位于 @tailwindcss/cli 软件包中,这一点和许多 v3 命令不同。
先创建目录并安装依赖:
mkdir tailwind-cli-demo
cd tailwind-cli-demo
npm init -y
npm install tailwindcss @tailwindcss/cli
mkdir src dist在 src/input.css 中写入:
@import "tailwindcss";在项目根目录创建 index.html:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tailwind CLI 初体验</title>
<link rel="stylesheet" href="./dist/output.css" />
然后运行监听构建:
npx @tailwindcss/cli -i ./src/input.css -o ./dist/output.css --watch这个终端需要保持运行。每次你保存源码,CLI 会重新检查类名并更新 dist/output.css。打开 index.html 后,按钮应该显示为蓝底白字;修改 class 并保存,输出 CSS 也会跟着更新。
如果只是生成一次用于发布的文件,可以去掉 --watch。正式项目里通常还会把命令写进 package.json 的 scripts,避免团队成员各自记一串参数。这里先保持命令透明,知道输入文件、输出文件和监听分别在做什么。
有时你只是想验证一个类名的效果,不想创建项目。这时可以做一个单独的 index.html,直接在浏览器中试:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tailwind 浏览器试验</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</
双击打开文件,就能看到效果。它很适合课堂上试颜色、间距和状态,也适合做一次性的验证。不过浏览器需要现场处理 Tailwind,这条路线没有正式构建流程的控制与优化,因此不要把它当作生产部署方案。真实产品仍应使用 Vite、CLI、PostCSS 或对应框架的集成方式。
“CDN 能显示,Vite 项目却不显示”也不是 Tailwind 类名坏了,通常说明本地构建链路有一环没接好。下一节就沿着这条链排查。
在构建方式下,Tailwind 会扫描项目源码,把看起来像工具类的完整文本找出来,再为实际识别到的类生成 CSS。它不会运行你的 JavaScript,也不会理解模板表达式最终会拼出什么。
这就是为什么下面的写法有风险:
const color = "blue";
const buttonClass = `bg-${color}-600 hover:bg-${color}-700`;源码中没有完整出现 bg-blue-600,构建器看到的是 bg-、变量和 -600。更稳妥的方式是让每个候选类名都以完整字符串出现:
const buttonStyles = {
primary: "bg-blue-600 hover:bg-blue-700 text-white",
success: "bg-emerald-600 hover:bg-emerald-700 text-white",
};
function ActionButton({ tone = "primary", children }) {
return (
<button className={`rounded-xl px-5 py-3 font-semibold ${buttonStyles[tone]}`}>
{children}
</button>
);
v4 默认会自动寻找常见源码,不必先列出 v3 式 content 数组。它也会跳过通常不该扫描的内容,例如 node_modules、被 .gitignore 忽略的文件、图片等二进制文件、CSS 文件和常见锁文件。
下面的“安检口”会把源码当作纯文本来扫描。先让动态拼接的写法通过一次,再切换到完整字符串映射,观察两种写法为什么得到不同结果。
实验里最关键的现象是:人脑能推断出的最终字符串,扫描器未必能从源码文本中直接找到。连续变化的数值则不必硬塞进类名,可以交给 CSS 变量或行内样式。
如果类名藏在默认不会扫描的位置,例如一个被忽略的共享组件包,可以在主 CSS 中显式注册来源:
@import "tailwindcss";
@source "../node_modules/@acme/ui";路径相对于当前样式表来理解。多包仓库里如果构建命令从仓库根目录执行,也可以给导入指定扫描基准:
@import "tailwindcss" source("../src");刚开始不需要主动加这些配置。先依靠自动检测,只有确认某个目录被漏掉时再明确注册。配置越多并不代表项目越专业,能解释每一行为什么存在才重要。
这张流程图把整条链路画成了“源码—检测—生成样式”。完整写出的标签能够通过检测,拆成几段的标签则停在门外。

排查样式缺失时,可以沿图从左向右检查:源码里有没有完整类名,扫描范围是否包含文件,最后才看生成的 CSS 是否被页面加载。
第一次安装失败时,很容易盯着 class 拼写来回改。更有效的方法是从“依赖 → 插件或 CLI → CSS 入口 → 页面入口 → 源码类名”顺着检查。
先确认 tailwindcss 与 @tailwindcss/vite 已安装,再看 vite.config.js 是否真的调用了 tailwindcss()。然后检查主 CSS 是否包含 @import "tailwindcss";,入口 JavaScript 是否导入了这份 CSS。少任何一环,页面都可能只有浏览器默认样式。
检查是否写了 `bg-${color}-600` 一类动态拼接。把所有候选结果改成源码中的完整字符串映射。不要先用大量强制生成规则掩盖问题;组件可接受的视觉选项明确写出来,代码也更容易检查。
确认安装的是 @tailwindcss/cli,执行的是 npx @tailwindcss/cli。npx tailwindcss init 和 npx tailwindcss -i ... 常来自 v3 教程,不是 v4 CLI 的当前入口。
打开生成文件,确认它不是空文件;再检查 HTML 的 <link> 路径是否相对于 index.html 写对。前面的示例把 HTML 放在根目录、CSS 放在 dist/output.css,所以使用 ./dist/output.css。文件位置一变,路径也要跟着变。
Vite 方案确认 npm run dev 仍在运行;CLI 方案确认带 --watch 的终端没有退出。也可以停止进程后重新启动一次,排除监听器没有捕捉到目录变化的情况。
检查产品的浏览器支持范围。v4 面向现代浏览器,并依赖较新的 CSS 能力。如果业务必须覆盖更早版本,问题不一定能靠补一个前缀解决,需要重新评估版本选择。
最小验证很简单:先只保留 text-red-600 或 bg-blue-600 这种肉眼明显的类。如果它生效,说明安装链路大体正常,再逐步恢复组件;如果它也不生效,就继续检查 CSS 是否被构建和加载,不要在复杂组件里盲猜。
学 Tailwind 最容易走偏的方式,是先背完颜色表、间距表和所有前缀,再开始做页面。工具类数量很多,脱离真实组件背诵很快会忘。更有效的节奏是:拿一个能运行的组件,每次改一两个类,观察界面发生什么变化,再把反复出现的规律记下来。
现在回到“保存学习进度”按钮。你已经能解释它的几层信息:inline-flex items-center justify-center 决定内部布局,px-5 py-3 决定空间,rounded-xl bg-blue-600 text-white shadow-md 决定外观,带冒号的类负责特定状态。具体每一类还有哪些取值、变体怎样组合,下一页会完整展开。
这也说明 Tailwind 的学习目标不是把 class 越写越多。目标是让每个视觉决定都有清楚来源,让常用值受同一套设计系统约束,并让组件最终效果容易修改、容易复用。到了主题页,我们会把默认刻度换成项目自己的语言;到了组件页,我们会处理更复杂的布局和复用边界;最后再进入 v4 的高级能力。当前这一步只要求两件事:理解为什么工具类值得尝试,并确保你的第一条 bg-blue-600 真正在浏览器里生效。
打开项目根目录的 vite.config.js,注册 Tailwind 插件。如果你的框架模板已经有其他插件,保留它们,再把 tailwindcss() 加进 plugins 数组。
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});把 src/style.css 改成下面这一行。它会载入 Tailwind 的主题、基础样式与工具类能力。
@import "tailwindcss";确认入口脚本仍然导入这份 CSS。Vite 的 Vanilla 模板通常在 src/main.js 中已有这一行;如果你删过模板代码,就把它补回来。
import "./style.css";在 index.html 的 body 中放入我们的按钮。Vite 会从源码中识别这些完整类名,并生成实际使用到的样式。
<main class="grid min-h-screen place-items-center bg-slate-100 p-6">
<section class="w-full max-w-md rounded-2xl bg-white p-8 shadow-lg">
<p class="text-sm font-medium text-blue-600">Tailwind CSS v4</p>
<h1 class="mt-2 text-2xl font-bold text-slate-900">第一次构建已就绪</h1>
<p class="mt-3 text-sm leading-6 text-slate-600">
先让真实组件显示出来,再逐个理解它使用的工具类。
</p>
<button
class="mt-6 inline-flex w-full items-center justify-center rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white shadow-md transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-400"
type="button"
>
保存学习进度
</button>
</section>
</main>启动开发服务,打开终端提示的本地地址。看到浅灰背景中的白色卡片,并且按钮悬停时颜色变深,就说明安装、扫描和样式导入都已工作。
npm run dev