Chapter 08

样式方案:Scoped CSS 与 Tailwind

深入理解 Astro 的 Scoped CSS 机制,集成 Tailwind CSS,实现主题变量与暗色模式

Scoped CSS 深度解析

作用域实现原理

Astro 的作用域样式通过在编译时给 HTML 元素添加唯一的 data-astro-XXXXXX 属性来实现。同时,CSS 中的每个选择器都会自动加上 [data-astro-XXXXXX] 属性选择器,确保样式只影响本组件内的元素。

<!-- 源码 -->
<style>
  h2 { color: var(--accent); }
  .card { padding: 1rem; }
</style>

<h2>标题</h2>
<div class="card">内容</div>
<!-- 编译输出 -->
<style>
  h2[data-astro-abc123] { color: var(--accent); }
  .card[data-astro-abc123] { padding: 1rem; }
</style>

<h2 data-astro-abc123>标题</h2>
<div class="card" data-astro-abc123>内容</div>

全局样式

<!-- 方法1:style is:global(整个 style 块无作用域)-->
<style is:global>
  :root { --color-primary: #FF5D01; }
  body { font-family: sans-serif; }
</style>

<!-- 方法2::global() 选择器(单个规则无作用域)-->
<style>
  h2 { color: red; }  <!-- 有作用域 -->

  :global(.external) { color: blue; }  <!-- 无作用域 -->
</style>

<!-- 方法3:在 Layout 中 import 全局 CSS 文件 -->
---
// src/layouts/BaseLayout.astro
// 在布局中导入全局 CSS,所有使用该布局的页面都会应用
import '../styles/global.css';
---

<html>...</html>

集成 Tailwind CSS

# 自动安装并配置
npx astro add tailwind

# 这会自动:
# 1. 安装 tailwindcss @astrojs/tailwind
# 2. 更新 astro.config.mjs
# 3. 创建 tailwind.config.mjs
// tailwind.config.mjs
export default {
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
  theme: {
    extend: {
      colors: {
        // 扩展自定义颜色
        brand: '#FF5D01',
      },
      fontFamily: {
        mono: ['JetBrains Mono', 'monospace'],
      },
    },
  },
  // 启用暗色模式(class 策略)
  darkMode: 'class',
  plugins: [],
};

在 .astro 文件中混用 Tailwind 和 Scoped CSS

<article class="prose max-w-none">
  <!-- Tailwind 类 + Scoped CSS 可以共存 -->
  <h1 class="text-3xl font-bold mb-4">标题</h1>
  <div class="card">内容</div>
</article>

<style>
  /* Scoped CSS 处理 Tailwind 无法覆盖的复杂样式 */
  .card {
    background: linear-gradient(135deg, #FF5D01, #ff8444);
  }
</style>

暗色模式实现

---
// BaseLayout.astro
---

<html>
<head>
  <script is:inline>
    // 防止闪白:在渲染前立即设置主题(inline script 在 CSS 前执行)
    const theme = localStorage.getItem('theme') ??
      (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
    document.documentElement.classList.toggle('dark', theme === 'dark');
  </script>
</head>
<body>
  <slot />
  <script>
    document.getElementById('theme-toggle').addEventListener('click', () => {
      document.documentElement.classList.toggle('dark');
      localStorage.setItem('theme',
        document.documentElement.classList.contains('dark') ? 'dark' : 'light'
      );
    });
  </script>
</body>
</html>
CSS 变量 + Tailwind 暗色模式最佳实践

推荐将主题色定义为 CSS 变量(:root { --bg: ... } .dark { --bg: ... }),然后在 Tailwind 配置中引用这些变量(backgroundColor: { DEFAULT: 'var(--bg)' })。这样可以实现主题切换时所有颜色同步变化,而不需要在每个元素上加 dark: 前缀。

Scoped CSS 的逃逸机制

:global() 伪选择器
在作用域 style 块中,用 :global(.class) 可以使特定规则突破作用域限制,影响到组件外部的元素。适合为第三方库或 Markdown 渲染内容添加样式(这些内容不在组件内,没有 data-astro 属性)。
is:global 属性
在 <style is:global> 中的所有样式都是全局的,不加作用域。用于全局重置、主题变量定义(:root)、字体引入等。建议集中在一个专门的全局样式文件中,而非分散在各组件。
is:inline(脚本)
类似于 is:global,<script is:inline> 让脚本以原始形式内联到 HTML 中,不经过 Astro 的模块处理(不支持 import/export)。适合需要在 DOM 渲染前立即运行的代码,如主题初始化(防止闪白)。

样式隔离与组件 Slot

---
// Card.astro:Scoped CSS 不影响 slot 内容
---
<div class="card">
  <slot />  <!-- slot 内容是外部传入的,没有 data-astro-card 属性 -->
</div>

<style>
  .card { border: 1px solid #ccc; }

  /* .card 内部的 p 标签不受作用域保护:会影响 slot 内容中的 p! */
  .card p { color: red; }

  /* 解决方案:用 :global 显式声明意图 */
  .card :global(p) { color: red; }  /* 明确:影响 .card 内所有 p */
</style>

本章小结

本章核心要点