Skip to content

CSS 与 SCSS

Nuxt 内置了完整的 CSS/SCSS 支持,通过 Vite 提供快速的样式开发体验。无论你用原生 CSS、预处理器还是原子化 CSS 框架,Nuxt 都能很好地支持。

样式方案怎么选?

| 方案 | 优点 | 缺点 | 适用场景 | |---|---|---|---|---| | 原生 CSS | 零配置、原生支持 | 无嵌套、无变量 | 简单项目 | | SCSS | 嵌套、变量、mixin | 需要安装 sass | 传统项目 | | CSS Modules | 命名安全、零冲突 | 语法略繁琐 | 组件化项目 | | Tailwind CSS | 快速开发、一致性好 | 类名长、需学习 | 快速原型、团队协作 | | UnoCSS | 按需生成、更灵活 | 社区较小 | 追求极致性能 |

Nuxt 4 的 CSS 默认支持

  • 原生 CSS:开箱即用
  • CSS 嵌套:Vite 已原生支持,无需 PostCSS 插件
  • CSS @import:Vite 自动处理
  • @media 嵌套:原生支持

全局样式

通过 nuxt.config.ts 引入

ts
export default defineNuxtConfig({
  css: [
    '~/app/assets/css/main.css',     // 全局基础样式
    '~/app/assets/css/variables.css', // CSS 变量
    '~/app/assets/css/reset.css',     // 样式重置
  ],
})

css 数组的顺序很重要

后面的文件会覆盖前面的同名规则。通常把变量/重置放前面,组件样式放后面。

通过 app.vue 引入

vue
<!-- app/app.vue -->
<style src="~/assets/css/main.css"></style>

nuxt.config.ts vs app.vue 引入全局样式的区别

  • nuxt.config.tscss:在所有页面和组件之前加载,始终存在
  • app.vue<style>:仅在 app.vue 渲染时加载

推荐用 nuxt.config.tscss 引入全局样式

更明确且不受组件渲染影响。

Scoped 样式

组件内使用 scoped,样式只作用于当前组件:

vue
<template>
  <div class="card">
    <h2 class="title">标题</h2>
    <p class="content">内容</p>
  </div>
</template>

<style scoped>
.card {
  background: #fff;
  border-radius: 8px;
  padding: 16px;
}

.title {
  font-size: 24px;
  color: #333;
}

.content {
  color: #666;
}
</style>

Scoped 的原理

Vue 编译器会给组件的每个 DOM 元素添加一个唯一的 data-v-xxxxxx 属性,CSS 选择器会自动加上 [data-v-xxxxxx],确保样式只作用于当前组件。

INFO

Scoped 样式的注意事项

  1. 子组件的根元素会被父组件的 scoped 样式影响(深度选择器可改变此行为)
  2. v-html 内容不受 scoped 样式影响(因为 DOM 是运行时生成的)
  3. 性能:scoped 样式比 CSS Modules 略慢(属性选择器开销)

深度选择器——影响子组件样式

vue
<style scoped>
/* 修改子组件的样式 */
:deep(.child-component-inner) {
  color: red;
}

/* 简写 ::v-deep */
::v-deep .child-component-inner {
  color: red;
}
</style>

何时用深度选择器?

当你需要在父组件中定制第三方组件或子组件的内部样式时。但过度使用说明组件的样式接口设计不够好。

插槽选择器——影响插槽内容

vue
<!-- 父组件 -->
<ChildComponent>
  <p class="slot-content">插槽内容</p>
</ChildComponent>

<!-- 子组件 -->
<style scoped>
/* 影响通过插槽传入的内容 */
:slotted(.slot-content) {
  color: blue;
}
</style>

全局选择器——在 scoped 中写全局样式

vue
<style scoped>
:global(.global-alert) {
  color: red;
}
</style>

CSS Modules

使用 CSS Modules 避免命名冲突——类名会自动哈希化:

vue
<template>
  <div :class="$style.card">
    <h2 :class="$style.title">标题</h2>
  </div>
</template>

<style module>
.card {
  background: #fff;
}
.title {
  color: #333;
}
</style>

CSS Modules vs Scoped

特性CSS ModulesScoped
命名安全✅ 哈希类名(_card_1a2b3✅ 属性选择器
JS 中访问$style.card❌ 类名是字符串
动态类名:class="[$style.card, { [$style.active]: isActive }]"✅ 更直观
可复用性✅ 导入其他模块❌ 仅限当前组件
SSR 性能✅ 更好略慢

推荐

中大型项目优先用 CSS Modules,小型项目用 Scoped 就够了。

自定义注入名称

vue
<template>
  <div :class="styles.card">内容</div>
</template>

<style module="styles">
.card { background: #fff; }
</style>

文件级 CSS Modules

文件命名为 *.module.css*.module.scss

css
/* app/assets/css/button.module.css */
.primary {
  background: #00DC82;
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
}

.secondary {
  background: #666;
  color: white;
}
vue
<template>
  <button :class="[styles.primary, { [styles.loading]: isLoading }]">
    {{ isLoading ? '加载中...' : '提交' }}
  </button>
</template>

<script setup>
import styles from '~/assets/css/button.module.css'

const isLoading = ref(false)
</script>

文件级 CSS Modules 的优势

可以在多个组件中共享同一套样式,且类名自动哈希避免冲突。

预处理器

SCSS(最常用)

安装依赖:

bash
npm install -D sass

使用:

vue
<style lang="scss" scoped>
.container {
  padding: 16px;

  .header {
    font-size: 24px;
    margin-bottom: 16px;
  }

  .body {
    padding: 16px;
  }
}
</style>

全局 SCSS 变量

在所有组件中直接使用 SCSS 变量,无需手动导入:

ts
export default defineNuxtConfig({
  css: ['~/app/assets/css/main.scss'],
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "~/app/assets/css/_variables.scss" as *;',
        },
      },
    },
  },
})
scss
// app/assets/css/_variables.scss
$primary: #00DC82;
$secondary: #666;
$font-size-base: 16px;
$border-radius: 8px;
$shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

在任何组件中直接使用变量:

vue
<style lang="scss" scoped>
.button {
  background: $primary;
  font-size: $font-size-base;
  border-radius: $border-radius;
  box-shadow: $shadow;
}
</style>

additionalData 的原理

Vite 在编译每个 SCSS 文件时,会自动在文件顶部注入 @use "~/app/assets/css/_variables.scss" as *;。这样每个组件都能直接使用变量,无需手动导入。

INFO

additionalData 中不要放实际的 CSS 规则 只放变量、mixin、function 定义。因为每个组件的样式都会注入这段代码,如果有实际 CSS 规则会导致重复

SCSS Mixin

scss
// app/assets/css/_mixins.scss
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin responsive($breakpoint) {
  @if $breakpoint == 'sm' { @media (min-width: 640px) { @content; } }
  @if $breakpoint == 'md' { @media (min-width: 768px) { @content; } }
  @if $breakpoint == 'lg' { @media (min-width: 1024px) { @content; } }
}
ts
// nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `
            @use "~/app/assets/css/_variables.scss" as *;
            @use "~/app/assets/css/_mixins.scss" as *;
          `,
        },
      },
    },
  },
})

Less

bash
npm install -D less
vue
<style lang="less" scoped>
@primary: #00DC82;

.container {
  padding: 16px;

  .header {
    font-size: 24px;
  }
}
</style>

Stylus

bash
npm install -D stylus
vue
<style lang="stylus" scoped>
$primary = #00DC82

.container
  padding 16px

  .header
    font-size 24px
</style>

PostCSS

Nuxt 内置 PostCSS,默认配置了 autoprefixer

ts
export default defineNuxtConfig({
  postcss: {
    plugins: {
      'postcss-nested': {},        // 嵌套规则
      'postcss-custom-media': {},  // 自定义媒体查询
    },
  },
})

PostCSS vs SCSS

PostCSS 通过插件实现各种功能(嵌套、变量等),是"后处理器"。SCSS 是"预处理器",有自己的语法。选择一种即可,不要混用。

Tailwind CSS

最流行的原子化 CSS 框架,Nuxt 有官方模块:

bash
npm install -D @nuxtjs/tailwindcss
ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss'],
})
vue
<template>
  <div class="bg-white rounded-lg shadow-md p-6">
    <h1 class="text-2xl font-bold text-green-500">Hello</h1>
    <p class="text-gray-600 mt-2">Welcome to Nuxt</p>
    <button class="mt-4 px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition">
      点击
    </button>
  </div>
</template>

Tailwind CSS 的优势

  • 开发速度快——不需要命名类名
  • 设计一致——统一的间距、颜色系统
  • 生产体积小——自动 Tree-shaking 未使用的样式

Nuxt + Tailwind 是目前最流行的组合

Tailwind 与 Scoped 样式配合

vue
<template>
  <div class="bg-white rounded-lg p-6">
    <h1 class="text-2xl font-bold">标题</h1>
  </div>
</template>

<!-- 对于 Tailwind 无法覆盖的样式,用 scoped 补充 -->
<style scoped>
/* 复杂的动画或特殊样式 */
.hero-gradient {
  background: linear-gradient(135deg, #00DC82 0%, #0ea5e9 100%);
}
</style>

UnoCSS

更灵活的原子化 CSS 引擎,按需生成样式:

bash
npm install -D @unocss/nuxt
ts
export default defineNuxtConfig({
  modules: ['@unocss/nuxt'],
})

UnoCSS vs Tailwind CSS

  • UnoCSS:更快的编译速度、更灵活的规则定义、更小的运行时
  • Tailwind CSS:更成熟的生态、更丰富的组件库、更好的 IDE 支持

如果你追求极致性能或需要自定义规则

选 UnoCSS;如果需要成熟的生态和社区支持,选 Tailwind。

动态类名

根据条件动态应用样式:

vue
<template>
  <!-- 对象语法 -->
  <div :class="{ 'card-active': isActive, 'card-dark': isDark }">
    内容
  </div>

  <!-- 数组语法 -->
  <div :class="['card', isActive ? 'active' : 'inactive']">
    内容
  </div>

  <!-- 数组 + 对象 -->
  <div :class="['card', { 'card-active': isActive, 'card-dark': isDark }]">
    内容
  </div>
</template>

CSS 变量

在全局样式中定义 CSS 变量,组件中直接使用:

css
/* app/assets/css/main.css */
:root {
  --color-primary: #00DC82;
  --color-secondary: #666;
  --color-bg: #ffffff;
  --color-text: #333333;
  --font-size-base: 16px;
  --border-radius: 8px;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* 暗色主题 */
[data-theme="dark"] {
  --color-bg: #1a1a1a;
  --color-text: #e0e0e0;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
vue
<style scoped>
.button {
  background: var(--color-primary);
  font-size: var(--font-size-base);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
}
</style>

CSS 变量 vs SCSS 变量

特性CSS 变量SCSS 变量
运行时✅ 浏览器运行时求值❌ 编译时固定
响应式✅ 可通过媒体查询修改❌ 编译后固定
主题切换✅ 改变 :root 变量即可❌ 需要重新编译
JavaScript 操作el.style.setProperty()❌ 不可操作
继承✅ 可继承/覆盖❌ 全局常量

推荐

需要主题切换或运行时变化的用 CSS 变量,编译时常量用 SCSS 变量。

CSS 变量 + 主题切换

ts
// app/composables/useTheme.ts
export function useTheme() {
  const theme = useCookie<'light' | 'dark'>('theme', { default: () => 'light' })

  function toggle() {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
    document.documentElement.setAttribute('data-theme', theme.value)
  }

  onMounted(() => {
    document.documentElement.setAttribute('data-theme', theme.value)
  })

  return { theme, toggle }
}

知识脉络

text
视图与布局 → 你在这里:CSS 与 SCSS

              ├─→ 下一步:资源管理

              ├─→ 相关:图片优化(图片样式处理)

              └─→ 相关:字体(@font-face 样式)

基于 Nuxt 4 官方文档整理编写