暗夜之约——明暗之间的优雅

目录

第 1 节 概述

从 2020 年起,几乎所有操作系统都内置了深色模式。用户希望网页应用能跟着系统走——白天浅色,晚上深色。

深色模式不只是在白色背景上把字变白。它需要一整套明暗调和的颜色搭配:

  • 背景用深灰而不是纯黑(纯黑太刺眼)
  • 文字用浅灰而不是纯白(降低对比度)
  • 卡片用比背景稍亮的灰色来区分层次

Tailwind 用 dark: 变体来处理这一切。

第 2 节 dark 变体

2.1 基础用法

在任意工具类前加 dark:,深色模式下生效:

images/09-dark-mode.svg

1
2
3
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <!-- 浅色:白底黑字;深色:深灰底白字 -->
</div>

2.2 两种配置方式

方式一:media 策略(默认)

自动跟随系统设置:

1
2
<!-- 什么都不用配,直接用 dark: 变体 -->
<div class="dark:bg-gray-900"></div>

等价于:@media (prefers-color-scheme: dark)

方式二:class 策略

允许用户手动切换。在 v4 中,需要在 CSS 中配置:

1
2
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

然后通过 JavaScript 在根元素上切换 dark 类:

1
2
3
<html class="dark">
  <!-- 所有 dark: 变体生效 -->
</html>

切换按钮的 JavaScript 逻辑:

1
2
3
4
// 读取系统偏好
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
// 切换 dark 类
document.documentElement.classList.toggle('dark', prefersDark.matches)
技巧

class 策略更灵活。用户选择应该被记住——切换后将偏好存到 localStorage。

第 3 节 深色模式的配色原则

3.1 背景层级

深色模式下,用不同深浅的灰色区分层次:

1
2
3
4
5
6
7
8
<!-- 页面背景 -->
<body class="bg-white dark:bg-gray-950">

<!-- 卡片背景(比页面高一层) -->
<div class="bg-gray-50 dark:bg-gray-900">

<!-- 悬浮效果(比卡片高一层) -->
<div class="hover:bg-gray-100 dark:hover:bg-gray-800">
层级浅色深色
页面背景bg-whitebg-gray-950
卡片/区块bg-gray-50bg-gray-900
悬浮态bg-gray-100bg-gray-800
导航栏/顶部bg-white/70bg-gray-950/70

3.2 文字层级

1
2
3
<p class="text-gray-900 dark:text-gray-100">主要文字</p>
<p class="text-gray-600 dark:text-gray-400">次要文字</p>
<p class="text-gray-400 dark:text-gray-500">占位文字(深色下适当变亮)</p>

3.3 边框和分隔线

1
<div class="border-gray-200 dark:border-gray-800">边框</div>

深色下的边框要比浅色更暗,避免产生"发光"的错觉。

第 4 节 图片在深色模式下的处理

有些图片在深色背景下显得刺眼,可以降低透明度或加暗:

1
2
3
4
<img class="dark:opacity-80" src="photo.jpg" />

<!-- 或者加滤镜 -->
<img class="dark:brightness-75" src="photo.jpg" />

第 5 节 实战:为产品页添加深色主题

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>星光科技 · 智能灯</title>
  <script src="https://unpkg.com/@tailwindcss/browser@4"></script>
</head>
<body class="bg-white dark:bg-gray-950 transition-colors duration-300">
  <nav class="fixed top-0 inset-x-0 bg-white/70 dark:bg-gray-950/70 backdrop-blur-md border-b border-gray-100/50 dark:border-gray-800/50 z-50">
    <div class="max-w-5xl mx-auto flex items-center justify-between px-4 sm:px-6 h-16">
      <span class="text-lg sm:text-xl font-bold text-gray-900 dark:text-white">星光科技</span>
      <div class="hidden md:flex gap-6 lg:gap-8">
        <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">产品</a>
        <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">特性</a>
        <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">用户评价</a>
        <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">联系我们</a>
      </div>
      <button class="md:hidden p-2 text-gray-600 dark:text-gray-400">
        <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
        </svg>
      </button>
    </div>
  </nav>

  <main class="max-w-5xl mx-auto px-4 sm:px-6 pt-28 sm:pt-32 pb-16">
    <!-- Hero -->
    <div class="bg-gradient-to-br from-blue-50 dark:from-blue-950 via-white dark:via-gray-950 to-purple-50 dark:to-purple-950 rounded-2xl sm:rounded-3xl p-8 sm:p-16 text-center">
      <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-gray-900 dark:text-white tracking-tight">
        智能台灯 <span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-600">Lumina</span>
      </h1>
      <p class="mt-4 sm:mt-6 text-base sm:text-lg lg:text-xl text-gray-500 dark:text-gray-400 leading-relaxed max-w-2xl mx-auto">
        光线随你而变。一盏懂你的台灯。
      </p>
      <button class="mt-6 sm:mt-8 px-6 sm:px-8 py-3 sm:py-4 bg-gradient-to-r from-blue-600 to-purple-600 text-white text-base sm:text-lg font-semibold rounded-xl shadow-lg shadow-blue-600/30
                     transform transition-all duration-200 ease-out
                     hover:-translate-y-1 hover:shadow-xl hover:shadow-blue-600/40
                     active:scale-95">
        立即购买
      </button>
    </div>

    <!-- 特性卡片 -->
    <section class="mt-20 sm:mt-32">
      <h2 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white text-center">为什么选择 Lumina</h2>
      <div class="mt-8 sm:mt-12 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
        <div class="group p-5 sm:p-6 bg-white dark:bg-gray-900 backdrop-blur-sm rounded-2xl border border-gray-100 dark:border-gray-800 shadow-md
                        transition-all duration-200 hover:shadow-xl hover:-translate-y-1">
          <div class="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-blue-100 dark:from-blue-900 to-blue-50 dark:to-blue-950 rounded-xl flex items-center justify-center
                      transition-transform duration-200 group-hover:scale-110">
            <span class="text-xl sm:text-2xl">🧠</span>
          </div>
          <h3 class="mt-3 sm:mt-4 text-lg sm:text-xl font-semibold text-gray-900 dark:text-white">AI 调光</h3>
          <p class="mt-2 text-sm sm:text-base text-gray-500 dark:text-gray-400 leading-relaxed">内置传感器实时感知环境光线,自动调整亮度和色温。</p>
        </div>

        <div class="group p-5 sm:p-6 bg-white dark:bg-gray-900 backdrop-blur-sm rounded-2xl border border-gray-100 dark:border-gray-800 shadow-md
                        transition-all duration-200 hover:shadow-xl hover:-translate-y-1">
          <div class="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-green-100 dark:from-green-900 to-green-50 dark:to-green-950 rounded-xl flex items-center justify-center
                      transition-transform duration-200 group-hover:scale-110">
            <span class="text-xl sm:text-2xl">👁️</span>
          </div>
          <h3 class="mt-3 sm:mt-4 text-lg sm:text-xl font-semibold text-gray-900 dark:text-white">护眼模式</h3>
          <p class="mt-2 text-sm sm:text-base text-gray-500 dark:text-gray-400 leading-relaxed">无频闪、低蓝光,通过德国莱茵 TÜV 护眼认证。</p>
        </div>

        <div class="group p-5 sm:p-6 bg-white dark:bg-gray-900 backdrop-blur-sm rounded-2xl border border-gray-100 dark:border-gray-800 shadow-md
                        transition-all duration-200 hover:shadow-xl hover:-translate-y-1
                        sm:col-span-2 lg:col-span-1">
          <div class="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-purple-100 dark:from-purple-900 to-purple-50 dark:to-purple-950 rounded-xl flex items-center justify-center
                      transition-transform duration-200 group-hover:scale-110">
            <span class="text-xl sm:text-2xl">🎤</span>
          </div>
          <h3 class="mt-3 sm:mt-4 text-lg sm:text-xl font-semibold text-gray-900 dark:text-white">语音控制</h3>
          <p class="mt-2 text-sm sm:text-base text-gray-500 dark:text-gray-400 leading-relaxed">支持 Siri、小爱同学、Google Assistant 语音指令。</p>
        </div>
      </div>
    </section>

    <!-- 用户评价 -->
    <section class="mt-20 sm:mt-32">
      <h2 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white text-center">用户评价</h2>
      <div class="mt-8 sm:mt-12 divide-y divide-gray-100 dark:divide-gray-800">
        <div class="py-4 sm:py-6 flex gap-3 sm:gap-4">
          <img class="w-10 h-10 sm:w-12 sm:h-12 rounded-full grayscale hover:grayscale-0 transition-all duration-300" src="https://i.pravatar.cc/48?img=1" alt="用户" />
          <div>
            <p class="font-semibold text-gray-900 dark:text-white">设计师小王</p>
            <p class="mt-1 text-sm sm:text-base text-gray-500 dark:text-gray-400">"用了两周,眼睛舒服多了。AI 调光真的很智能。"</p>
          </div>
        </div>
        <div class="py-4 sm:py-6 flex gap-3 sm:gap-4">
          <img class="w-10 h-10 sm:w-12 sm:h-12 rounded-full grayscale hover:grayscale-0 transition-all duration-300" src="https://i.pravatar.cc/48?img=2" alt="用户" />
          <div>
            <p class="font-semibold text-gray-900 dark:text-white">程序员大刘</p>
            <p class="mt-1 text-sm sm:text-base text-gray-500 dark:text-gray-400">"晚上写代码的时候,灯光会自动变暖,不刺眼。"</p>
          </div>
        </div>
        <div class="py-4 sm:py-6 flex gap-3 sm:gap-4">
          <img class="w-10 h-10 sm:w-12 sm:h-12 rounded-full grayscale hover:grayscale-0 transition-all duration-300" src="https://i.pravatar.cc/48?img=3" alt="用户" />
          <div>
            <p class="font-semibold text-gray-900 dark:text-white">学生小林</p>
            <p class="mt-1 text-sm sm:text-base text-gray-500 dark:text-gray-400">"期末复习全靠它了,灯光可以跟着时间变化。"</p>
          </div>
        </div>
      </div>
    </section>

    <footer class="mt-20 sm:mt-32 pt-8 border-t border-gray-100 dark:border-gray-800 flex flex-col sm:flex-row items-center justify-between gap-4 text-sm text-gray-400 dark:text-gray-500">
      <span>&copy; 2026 星光科技</span>
      <div class="flex gap-6">
        <a href="#" class="hover:text-gray-600 dark:hover:text-gray-300 transition-colors">隐私政策</a>
        <a href="#" class="hover:text-gray-600 dark:hover:text-gray-300 transition-colors">服务条款</a>
      </div>
    </footer>
  </main>
</body>
</html>

看看 v4 中 dark: 变体如何统一处理所有颜色:

  • bodybg-white dark:bg-gray-950 —— 页面背景从白变为深色
  • <h1>text-gray-900 dark:text-white —— 标题从深灰变为纯白
  • 卡片:bg-white dark:bg-gray-900 border-gray-100 dark:border-gray-800 —— 卡片底色和边框同步切换
  • 导航栏:bg-white/70 dark:bg-gray-950/70 border-gray-100/50 dark:border-gray-800/50 —— 毛玻璃导航栏也适配深色
  • Hero 渐变:from-blue-50 dark:from-blue-950 via-white dark:via-gray-950 to-purple-50 dark:to-purple-950 —— 渐变背景全深色化
  • 图标背景:from-blue-100 dark:from-blue-900 to-blue-50 dark:to-blue-950 —— 图标容器适配
  • 分隔线:divide-gray-100 dark:divide-gray-800 —— 用户评价的分隔线
  • 页脚边框:border-gray-100 dark:border-gray-800
  • body 上的 transition-colors duration-300 —— 切换深色/浅色时有平滑过渡
说明

用 Play CDN 预览时,深色模式默认跟随系统偏好。你可以在浏览器开发者工具中切换模拟,或者在系统设置中切换主题来查看效果。

本章小结
  • dark: 变体让深色模式集成变得简单
  • v4 默认使用 media 策略(跟随系统),通过 @custom-variant dark 切换为 class 策略
  • 深色配色原则:背景用 gray-950(非纯黑),文字用 gray-100(非纯白)
  • 搭配 transition-colors 让明暗切换更平滑

现在能浅能深,页面适配能力已经很强了。最后一篇,我们来做一个完整的实战整合——把前 9 章所有的技巧融会贯通。

目录