根本原因是css层叠规则严格执行:后加载或更高特异性的bootstrap规则会覆盖自定义样式;需确保bootstrap.min.css先引入、选择器精确匹配、特异性足够,并用devtools验证覆盖关系。

根本原因不是“Bootstrap太霸道”,而是 CSS 层叠规则在安静地执行——bootstrap.min.css 里同名选择器的规则,只要加载顺序靠后或特异性更高,就会覆盖你写的样式。问题往往出在你没意识到它已经赢了。
检查 CSS 加载顺序是否颠倒
浏览器按 <link> 标签在 HTML 中出现的顺序解析样式表,后加载的同选择器规则直接胜出,和权重无关。
- ✅ 正确顺序:
<link href="bootstrap.min.css">→<link href="style.css"> - ❌ 错误顺序:
<link href="style.css">→<link href="bootstrap.min.css">(哪怕你只改了一个color,也会被盖掉) - Webpack/Vite 项目中,检查入口 JS(如
index.js)是否先import 'bootstrap/dist/css/bootstrap.min.css',再import './style.css' -
<style></style>标签默认插入末尾,天然晚于外部 CSS —— 所以写在 HTML 里的内联样式也可能被 bootstrap 覆盖
验证选择器是否真的匹配上了
写了 .btn { color: red; } 却没生效?可能它压根没匹配到目标元素。
- 打开开发者工具 Elements 面板,选中按钮,看它的 class 列表里是不是真有
btn(不是my-btn或button) - Bootstrap 的
.carousel-item必须严格拼写,写成.c-item或.item都不会命中 - 嵌套结构不一致也会失效:比如 Bootstrap 规则写的是
.navbar-nav .nav-link,而你的 HTML 多套了一层<div>,导致路径断开 <li>React/Vue 中用了 <code>scoped或 CSS-in-JS,生成的类名带哈希(如nav-link__a1b2c),和全局.nav-link完全不匹配 -
.btn img(0,1,1) vs Bootstrap 的.btn > img(0,1,1)→ 特异性相同,后加载者胜;但若它加了!important或用了.btn.active > img(0,2,1),你就输了 - 避免用泛泛的元素选择器开头:
img { height: 100%; }很容易被.carousel-item img覆盖 - 更稳妥的方式是加自定义类:
<img class="hero-img">,再写.hero-img { object-fit: cover; } - 不要一上来就加
!important——它会掩盖真实问题,且未来想改回来时得加两个!important - 动态拼接的类名会被漏掉:
class="btn btn-${type}"中的btn-primary若未显式出现在content配置里,就会消失 - Tailwind 用户检查
tailwind.config.js的content数组是否包含所有路径,例如:['./index.html', './src/**/*.{vue,js,ts}'] - Vite 中临时关闭 PurgeCSS:设
build.cssMinify: 'light'或注释掉相关插件配置,看样式是否恢复 - Network 面板看到
style.css返回 200,不代表内容有效——点进去看响应体,搜一下你要的类名是否存在
对比特异性(Specificity)是否被碾压
即使加载顺序正确,你的选择器也可能输在“谁更具体”上。
排查构建工具是否悄悄删掉了你的类
现代打包流程(PurgeCSS、UnoCSS、Tailwind 的 content 扫描)会分析模板和代码,把“没用到的类”干掉。
真正卡住人的,往往不是“怎么写样式”,而是“为什么写了却看不见”。花十秒切到 Elements 面板的 Styles 栏,比重写三遍 CSS 都管用。尤其注意那些带删除线的规则——它们正在告诉你,谁赢了这场无声的层叠战争。











