现代 CSS 特性完全指南

NOTE

本文档涵盖 Container Queries、Cascade Layers、:has() 选择器、色彩函数、CSS 作用域等现代 CSS 特性,以及浏览器兼容性策略和深度实战技巧。


CSS 的演进与当代定位

从样式表到声明式 UI 引擎

CSS(层叠样式表)自1996年诞生以来,经历了从简单的样式声明到现代复杂样式系统的漫长演进。理解现代 CSS 特性的定位,需要先回顾其发展脉络。

在 CSS 1.0 时代,我们只能定义字体、颜色、文本属性等最基础的样式。那时候的网页设计极其简陋,CSS 主要是作为一种辅助工具,用来微调 HTML 默认的渲染效果。

CSS 2.0 引入了盒模型、定位、浮动等核心概念,奠定了现代布局的基础。这个版本的 CSS 允许开发者控制元素的大小、位置和层叠关系,使得复杂的页面布局成为可能。尽管当时浏览器的实现参差不齐,但 CSS 2.0 确立了许多至今仍在使用的核心概念。

CSS 3 开始的模块化发展彻底改变了 CSS 的面貌。从 2010 年代开始,CSS 被分解为多个独立模块并行发展,引入了选择器、背景边框、文本效果、2D/3D 变换、动画、多列布局、Flexbox、Grid 等众多特性。这种模块化方法允许浏览器厂商选择性地实现不同的特性,加速了新特性的普及。

今天的现代 CSS 已经进入了声明式 UI 的新时代。Container Queries 让组件能够响应自身容器而非视口的尺寸变化。:has() 选择器让父元素能够感知子元素的状态。CSS Houdini 提供了访问浏览器渲染引擎的接口。OKLCH 色彩空间让颜色定义更加直观和一致。这些特性让 CSS 不再只是一种样式声明语言,而是具备了构建复杂用户界面的能力。

为什么现代 CSS 值得关注

在 vibecoding 时代,CSS 的角色正在发生根本性转变。传统的 CSS 开发模式需要依赖大量的 JavaScript 来实现复杂的交互效果,但现代 CSS 让这种模式发生了革命性的变化。

首先是声明式优先的理念。通过 :has() 选择器,我们可以实现过去需要事件监听器才能完成的交互逻辑。例如,当表单包含无效输入时自动显示错误状态,当列表包含特定项目时改变样式,当卡片包含徽章时自动调整布局。这些逻辑过去需要在 JavaScript 中监听 DOM 变化,现在可以直接在 CSS 中声明。

其次是组件自包含的追求。Container Queries 的出现让组件真正成为自包含的单元。在过去,一个响应式卡片组件在侧边栏中可能显示为紧凑布局,在主内容区显示为宽版布局,但这种自适应能力需要依赖外部容器传递信息。Container Queries 让组件可以直接检测自身容器的尺寸,自主决定渲染方式,而不需要任何 JavaScript 逻辑或外部 CSS 类名。

第三是性能原生支持。现代 CSS 内置了大量性能优化特性。content-visibility 可以自动跳过离屏内容的渲染,显著提升长页面的滚动性能。will-change 可以提示浏览器提前优化特定元素。CSS 动画使用 GPU 加速,确保流畅的视觉效果。

第四是设计系统集成。CSS 变量与设计 token 系统的结合让主题切换、组件变体、品牌定制等需求变得前所未有的简单。你不需要重新编译 CSS,只需要修改变量值,整个界面的主题就会自动更新。

浏览器支持现状与选型策略

截至 2026 年,现代 CSS 特性的浏览器支持已达到生产就绪阶段。对于新项目,可以直接采用所有现代 CSS 特性,因为目标用户的浏览器通常会自动更新。对于企业级项目,建议采用渐进增强策略,核心样式使用广泛支持的基础特性,高级特性使用 @supports 条件加载。对于需要兼容旧浏览器的项目,使用 PostCSS 的 autoprefixer、CSS Modules、或 CSS-in-JS 方案来处理兼容性问题。


Custom Properties 深度解析

CSS 变量的核心机制

CSS Custom Properties(CSS 变量)是现代 CSS 最基础的特性之一,它不仅是简单的变量替换,更是 CSS 架构的革命性变革。

CSS 变量与其他 CSS 值的关键区别在于:CSS 变量的值可以由 JavaScript 动态修改,而静态 CSS 值一旦编译就固定了。这种动态性为样式系统带来了无限可能。

CSS 变量的继承规则是其核心机制之一。CSS 变量遵循正常的继承规则,可以在任何选择器中定义,子元素自动继承父元素的值。这种机制允许我们为不同的上下文设置不同的变量值:

/* 全局变量 - 定义在 :root 伪类上,所有元素都可以继承 */
:root {
  --primary-color: #3b82f6;
  --spacing-unit: 8px;
  --border-radius: 4px;
  --font-family-base: 'Inter', system-ui, sans-serif;
}
 
/* 组件级变量 - 只有 .card 及其子元素可以使用 */
.card {
  --card-padding: 16px;
  --card-bg: white;
  --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
 
/* 主题变体 - 覆盖组件变量 */
.card.dark {
  --card-bg: #1e293b;
  --card-text: #f1f5f9;
  --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
 
/* 响应式变量 - 根据屏幕尺寸调整 */
@media (min-width: 768px) {
  :root {
    --spacing-unit: 10px;
    --font-size-base: 17px;
  }
}

这个继承机制的强大之处在于,我们可以为不同的 DOM 层级设置不同作用域的变量,实现真正的设计系统层级结构。设计系统的全局变量定义在根级别,组件变量定义在组件容器上,主题覆盖定义在特定类名上。

计算与组合的高级用法

CSS 变量可以在 calc() 函数中使用,实现动态计算。这种能力让 CSS 变量不仅仅是静态值的别名,而是真正的可计算表达式:

:root {
  /* 基础间距值 */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
 
  /* 组合计算 */
  --card-padding: calc(var(--spacing-md) + var(--spacing-sm));
  --card-gap: calc(var(--spacing-md) * 2);
  --modal-padding: calc(var(--spacing-lg) + var(--spacing-md));
 
  /* 复杂计算 */
  --container-width: calc(100% - var(--spacing-xl) * 2);
  --grid-columns: 12;
  --grid-gap: 24px;
  --column-width: calc(
    (var(--container-width) - (var(--grid-gap) * (var(--grid-columns) - 1))) / var(--grid-columns)
  );
 
  /* 响应式计算 */
  --fluid-font-size: clamp(1rem, 2vw, 1.5rem);
  --fluid-spacing: clamp(8px, 2vw, 24px);
}
 
/* 使用计算值 */
.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  gap: var(--grid-gap);
}
 
.card {
  padding: var(--card-padding);
  gap: var(--card-gap);
}
 
h1 {
  font-size: var(--fluid-font-size);
}

备用值机制是另一个重要特性。当变量未定义时,备用值会生效:

.element {
  /* 无备用值 - 如果变量未定义则使用默认值(通常是 initial)*/
  color: var(--theme-color);
 
  /* 带备用值 */
  color: var(--theme-color, #3b82f6);
 
  /* 备用值也可以是变量 */
  color: var(--theme-color, var(--fallback-color));
 
  /* 多个备用值 */
  color: var(--missing, red, blue);
 
  /* 复杂备用值 */
  background: var(--card-gradient, linear-gradient(to right, #667eea, #764ba2));
  font-size: var(--card-font-size, clamp(14px, 2vw, 18px));
}

JavaScript 操作 CSS 变量

CSS 变量可以通过 JavaScript 动态读取和修改,这是实现运行时主题切换的基础:

// 获取元素上设置的变量值
const element = document.querySelector('.card');
const computedStyle = getComputedStyle(element);
 
// 获取具体变量值
const primaryColor = computedStyle.getPropertyValue('--primary-color').trim();
console.log('Primary color:', primaryColor);
 
// 获取带单位的值
const spacing = computedStyle.getPropertyValue('--spacing').trim();
 
// 动态设置变量值
element.style.setProperty('--primary-color', '#10b981');
 
// 批量更新多个变量
element.style.cssText = `
  --primary-color: #10b981;
  --secondary-color: #6366f1;
  --spacing: 24px;
`;
 
// 删除变量
element.style.removeProperty('--temporary-variable');
 
// 使用 CSSUnitValue API(现代浏览器)
const value = computedStyle.getPropertyValue('--spacing');
const parsed = CSS.parseComponentValue?.(value) || value;
if (typeof parsed === 'object' && parsed.value !== undefined) {
  console.log('Value:', parsed.value, 'Unit:', parsed.unit);
}

设计 Token 系统的完整实现

CSS 变量是构建设计 Token 系统的理想选择。一个完整的设计系统应该包含多个层级的变量:

:root {
  /* ===== 色彩系统 ===== */
 
  /* 品牌色 - 使用 OKLCH 以获得更好的感知均匀性 */
  --color-brand-50: oklch(97% 0.02 250);
  --color-brand-100: oklch(94% 0.04 250);
  --color-brand-200: oklch(88% 0.08 250);
  --color-brand-300: oklch(76% 0.14 250);
  --color-brand-400: oklch(62% 0.20 250);
  --color-brand-500: oklch(52% 0.22 250);
  --color-brand-600: oklch(44% 0.20 250);
  --color-brand-700: oklch(36% 0.18 250);
  --color-brand-800: oklch(28% 0.14 250);
  --color-brand-900: oklch(22% 0.10 250);
 
  /* 语义化颜色映射 */
  --color-background: var(--color-brand-50);
  --color-foreground: var(--color-brand-900);
  --color-primary: var(--color-brand-500);
  --color-primary-hover: var(--color-brand-600);
  --color-accent: var(--color-brand-400);
 
  /* 功能色 */
  --color-success: oklch(62% 0.20 145);
  --color-warning: oklch(70% 0.18 80);
  --color-error: oklch(55% 0.22 25);
  --color-info: oklch(60% 0.20 220);
 
  /* ===== 字体系统 ===== */
  --font-family-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-family-serif: 'Georgia', 'Times New Roman', serif;
  --font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;
 
  --font-size-xs: 0.75rem;    /* 12px */
  --font-size-sm: 0.875rem;   /* 14px */
  --font-size-base: 1rem;     /* 16px */
  --font-size-lg: 1.125rem;  /* 18px */
  --font-size-xl: 1.25rem;   /* 20px */
  --font-size-2xl: 1.5rem;   /* 24px */
  --font-size-3xl: 1.875rem; /* 30px */
  --font-size-4xl: 2.25rem;  /* 36px */
  --font-size-5xl: 3rem;     /* 48px */
 
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
 
  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;
 
  /* ===== 间距系统 ===== */
  --spacing-0: 0;
  --spacing-1: 0.25rem;   /* 4px */
  --spacing-2: 0.5rem;    /* 8px */
  --spacing-3: 0.75rem;    /* 12px */
  --spacing-4: 1rem;       /* 16px */
  --spacing-5: 1.25rem;   /* 20px */
  --spacing-6: 1.5rem;    /* 24px */
  --spacing-8: 2rem;       /* 32px */
  --spacing-10: 2.5rem;    /* 40px */
  --spacing-12: 3rem;      /* 48px */
  --spacing-16: 4rem;      /* 64px */
  --spacing-20: 5rem;      /* 80px */
  --spacing-24: 6rem;     /* 96px */
 
  /* ===== 圆角系统 ===== */
  --radius-none: 0;
  --radius-sm: 0.25rem;   /* 4px */
  --radius-md: 0.5rem;    /* 8px */
  --radius-lg: 0.75rem;   /* 12px */
  --radius-xl: 1rem;      /* 16px */
  --radius-2xl: 1.5rem;  /* 24px */
  --radius-full: 9999px;
 
  /* ===== 阴影系统 ===== */
  --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
  --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
 
  /* ===== 动画系统 ===== */
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 300ms;
  --duration-slower: 500ms;
 
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
 
  /* ===== Z-Index 系统 ===== */
  --z-base: 0;
  --z-dropdown: 1000;
  --z-sticky: 1020;
  --z-fixed: 1030;
  --z-modal-backdrop: 1040;
  --z-modal: 1050;
  --z-popover: 1060;
  --z-tooltip: 1070;
  --z-toast: 1080;
}

Container Queries 容器查询

核心概念与原理

Container Queries 是 CSS 响应式设计的重大突破,它允许组件根据自身容器的尺寸来调整样式,而非仅仅依赖视口尺寸。这个特性的出现,解决了响应式设计中长期存在的一个痛点:组件无法独立于页面布局进行响应式调整。

在 Container Queries 出现之前,如果你想让一个卡片组件在侧边栏中显示紧凑布局,在主内容区显示宽版布局,你需要知道这个卡片将被放在什么容器中,然后在媒体查询中针对不同的容器尺寸编写样式。这种方式的问题是:组件与页面布局强耦合,无法复用。

Container Queries 彻底解决了这个问题。你只需要在组件的容器上定义一个查询上下文,然后组件就可以直接响应这个容器的尺寸变化,而不需要知道这个容器在页面的什么位置。

/* 定义容器 - 简写形式 */
.card-container {
  container: card / inline-size;
  /* 等价于:
     container-name: card;
     container-type: inline-size;
  */
}
 
/* 定义容器 - 详细形式 */
.card-wrapper {
  container-type: inline-size;   /* 监听内联方向尺寸(宽度)*/
  container-name: card;            /* 命名容器 */
}
 
/* 块方向容器 */
.sidebar {
  container-type: block-size;     /* 监听块方向尺寸(高度)*/
  container-name: sidebar;
}
 
/* 两个方向都监听 */
.panel {
  container-type: size;           /* 监听宽高 */
  container-name: panel;
}
 
/* 无尺寸约束的容器(仅命名空间)*/
.container {
  container-type: normal;         /* 不创建包含上下文 */
  container-name: my-container;
}

容器查询语法详解

/* 默认样式 - 无查询条件时 */
.product-card {
  display: flex;
  flex-direction: column;
}
 
/* 基础查询:容器宽度 >= 300px */
@container (min-width: 300px) {
  .product-card {
    flex-direction: row;
  }
}
 
/* 基础查询:容器宽度 >= 500px */
@container (min-width: 500px) {
  .product-card {
    gap: 32px;
    padding: 24px;
  }
 
  .product-card .title {
    font-size: 1.5rem;
  }
}
 
/* 命名容器查询 - 只响应特定容器的变化 */
@container card (min-width: 400px) {
  .product-card {
    flex-direction: row;
  }
}
 
/* 范围查询 */
@container (300px <= width <= 600px) {
  .product-card {
    /* 宽度在 300-600px 之间时触发 */
    padding: 16px;
  }
}
 
/* 组合查询 */
@container (min-width: 300px) and (max-width: 600px) {
  .product-card {
    /* 同时满足两个条件 */
    font-size: 0.875rem;
  }
}
 
/* 否定查询 */
@container not (min-width: 300px) {
  .product-card {
    /* 宽度 < 300px */
    flex-direction: column;
  }
}
 
/* 纵横比查询 */
@container (aspect-ratio > 1) {
  .product-card {
    /* 横向布局 */
    flex-direction: row;
  }
}

实战:响应式卡片组件

这是一个完整的响应式卡片组件示例,展示了如何利用 Container Queries 实现真正的组件级响应式:

/* ===== 基础容器定义 ===== */
.media-card {
  container-type: inline-size;
  container-name: media-card;
}
 
/* ===== 默认样式(最小尺寸)====== */
.card-inner {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
 
.card-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-lg);
}
 
.card-content {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
}
 
.card-title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-foreground);
}
 
.card-description {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: var(--line-height-normal);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
 
.card-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}
 
/* ===== 小容器(>= 320px)====== */
@container media-card (min-width: 320px) {
  .card-actions {
    flex-direction: row;
  }
}
 
/* ===== 中等容器(>= 400px)====== */
@container media-card (min-width: 400px) {
  .card-inner {
    flex-direction: row;
    align-items: flex-start;
  }
 
  .card-image {
    width: 45%;
    aspect-ratio: 1;
    border-radius: var(--radius-xl);
  }
 
  .card-content {
    flex: 1;
    padding: 20px;
  }
 
  .card-title {
    font-size: var(--font-size-xl);
  }
 
  .card-description {
    -webkit-line-clamp: 3;
  }
}
 
/* ===== 大容器(>= 600px)====== */
@container media-card (min-width: 600px) {
  .card-inner {
    gap: 32px;
  }
 
  .card-image {
    width: 50%;
  }
 
  .card-content {
    padding: 32px;
    justify-content: center;
  }
 
  .card-title {
    font-size: var(--font-size-2xl);
    margin-bottom: 12px;
  }
 
  .card-description {
    font-size: var(--font-size-base);
    -webkit-line-clamp: 4;
  }
 
  .card-actions {
    margin-top: 20px;
  }
}
 
/* ===== 超大容器(>= 800px)====== */
@container media-card (min-width: 800px) {
  .card-inner {
    gap: 48px;
  }
 
  .card-content {
    padding: 40px;
  }
 
  .card-title {
    font-size: var(--font-size-3xl);
  }
 
  .card-description {
    font-size: var(--font-size-lg);
    max-width: 600px;
  }
}

Container Queries 与媒体查询的对比

理解这两种查询方式的区别和使用场景非常重要:

维度媒体查询容器查询
查询基准视口(viewport)父容器(container)
适用场景页面整体布局可复用组件
组件独立性依赖页面上下文完全独立
响应式粒度页面级组件级
/* 媒体查询方式 - 组件依赖于外部页面布局 */
@media (min-width: 768px) {
  .card {
    flex-direction: row; /* 假设卡片在侧边栏时会出错 */
  }
}
 
/* 容器查询方式 - 组件自主响应 */
.card-wrapper {
  container-type: inline-size;
}
 
@container (min-width: 400px) {
  .card {
    flex-direction: row; /* 无论在哪里都正确 */
  }
}

Cascade Layers 级联层

核心概念与使用场景

Cascade Layers 解决了 CSS 中一个长期困扰开发者的问题:样式优先级冲突。当我们使用第三方库或框架时,经常会遇到难以覆盖的样式。传统的解决方案是增加选择器的特异性或使用 !important,但这会导致代码混乱和难以维护。

Cascade Layers 允许开发者显式控制不同来源样式的优先级顺序。通过将样式分配到不同的层中,后声明的层自动获得更高的优先级,无论其选择器的特异性如何。

/* 定义层顺序(从低到高)- 这个声明应该在所有 CSS 之前 */
@layer reset, base, components, patterns, utilities, overrides;
 
/* 或者先声明后定义 */
@layer utilities;
@layer components;
@layer base;
@layer reset;

实战:样式分块管理

/* 定义层顺序 */
@layer reset, base, layout, components, utilities;
 
/* reset.css - 浏览器默认样式重置 */
@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
  }
 
  * {
    margin: 0;
    padding: 0;
  }
 
  html {
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
  }
 
  body {
    min-height: 100vh;
    line-height: 1.5;
  }
 
  img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
  }
 
  input, button, textarea, select {
    font: inherit;
  }
 
  p, h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
  }
}
 
/* base.css - 基础样式 */
@layer base {
  body {
    font-family: var(--font-family-sans);
    font-size: var(--font-size-base);
    line-height: var(--line-height-normal);
    background-color: var(--color-background);
    color: var(--color-foreground);
  }
 
  h1 {
    font-size: var(--font-size-4xl);
    font-weight: var(--font-weight-bold);
    line-height: var(--line-height-tight);
  }
 
  h2 {
    font-size: var(--font-size-3xl);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
  }
 
  a {
    color: var(--color-primary);
    text-decoration: none;
    transition: color var(--duration-fast) var(--ease-out);
  }
 
  a:hover {
    color: var(--color-primary-hover);
  }
}
 
/* layout.css - 布局样式 */
@layer layout {
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--spacing-4);
  }
 
  @media (min-width: 768px) {
    .container {
      padding: 0 var(--spacing-6);
    }
  }
 
  @media (min-width: 1024px) {
    .container {
      padding: 0 var(--spacing-8);
    }
  }
 
  .grid {
    display: grid;
    gap: var(--spacing-6);
  }
 
  .flex {
    display: flex;
  }
 
  .flex-center {
    display: flex;
    align-items: center;
    justify-content: center;
  }
}
 
/* components.css - 组件样式 */
@layer components {
  .button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--spacing-2) var(--spacing-4);
    border: none;
    border-radius: var(--radius-md);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: all var(--duration-normal) var(--ease-out);
  }
 
  .button-primary {
    background-color: var(--color-primary);
    color: white;
  }
 
  .button-primary:hover {
    background-color: var(--color-primary-hover);
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
  }
 
  .card {
    background: var(--color-surface);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    transition: all var(--duration-normal) var(--ease-out);
  }
 
  .card:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
  }
}
 
/* utilities.css - 工具类 */
@layer utilities {
  .hidden {
    display: none !important;
  }
 
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
 
  .text-center {
    text-align: center;
  }
 
  .text-balance {
    text-wrap: balance;
  }
 
  .mt-4 {
    margin-top: var(--spacing-4);
  }
 
  .p-4 {
    padding: var(--spacing-4);
  }
 
  .gap-4 {
    gap: var(--spacing-4);
  }
}

覆盖第三方库样式

这是 Cascade Layers 最实用的场景之一:

/* 问题:Bootstrap 的 specificity 很高,难以覆盖 */
.bootstrap-btn {
  background: blue !important;
}
 
.my-btn {
  background: red; /* 可能无效! */
}
 
/* 解决方案:使用层 */
@layer vendors, overrides {
  @layer vendors {
    .btn {
      display: inline-block;
      padding: 8px 16px;
      border-radius: 4px;
      background: blue;
    }
  }
 
  @layer overrides {
    /* 在更高层中,无视 specificity! */
    .btn {
      background: red !important;
    }
 
    /* 即使是低 specificity 选择器也能覆盖 */
    button {
      background: green !important;
    }
  }
}

:has() 选择器

革命性的父选择器

:has() 是 CSS 选择器的一个革命性补充,它实现了父选择器和条件选择器的能力。在过去,CSS 只能从父元素选择子元素或从前面选择器选择后面的兄弟元素,但无法根据子元素的状态来选择父元素。

/* 基础语法 */
parent:has(child) { }     /* 选择包含 child 的 parent */
parent:has(> child) { }   /* 选择直接包含 child 的 parent */
parent:has(+ sibling) { } /* 选择紧邻 sibling 的 parent */
parent:has(~ sibling) { } /* 选择包含后续 sibling 的 parent */

实战场景详解

/* ===== 场景1:表单验证状态 ===== */
/* 显示验证错误的表单 */
form:has(:invalid) {
  border-color: var(--color-error);
  background-color: var(--color-error-bg);
}
 
form:has(:invalid):has(:focus) {
  border-color: var(--color-error);
  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
 
/* 成功状态的表单 */
form:has(:valid:not(:placeholder-shown)):not(:has(:invalid)) {
  border-color: var(--color-success);
  background-color: var(--color-success-bg);
}
 
/* ===== 场景2:选中状态指示 ===== */
.radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}
 
.radio-group:has(input:checked) {
  border: 2px solid var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--spacing-4);
}
 
.radio-option {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}
 
.radio-option:has(input:checked) {
  background-color: var(--color-primary-bg);
  font-weight: var(--font-weight-medium);
}
 
.radio-option:has(input:checked)::after {
  content: ' ✓';
  color: var(--color-primary);
}
 
/* ===== 场景3:空状态处理 ===== */
/* 空列表显示占位符 */
.list:empty::after {
  content: '暂无数据';
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-12);
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
}
 
/* ===== 场景4:图片处理 ===== */
/* 无图片占位 */
figure:not(:has(img)) {
  background: linear-gradient(135deg, var(--color-gray-100) 0%, var(--color-gray-200) 100%);
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}
 
figure:not(:has(img))::before {
  content: '📷';
  font-size: 48px;
  opacity: 0.3;
}
 
/* ===== 场景5:导航状态 ===== */
/* 有下拉菜单的导航项 */
.nav-item:has(.dropdown) > .nav-link::after {
  content: ' ▼';
  font-size: 10px;
  opacity: 0.6;
}
 
/* 当前激活的菜单分支 */
.nav-branch:has(.nav-item.active) > .nav-branch-header {
  font-weight: var(--font-weight-semibold);
  color: var(--color-primary);
}
 
/* ===== 场景6:卡片变体 ===== */
/* 特色卡片 */
.card:has(.featured-badge) {
  border: 2px solid var(--color-warning);
  position: relative;
}
 
.card:has(.featured-badge)::before {
  content: '特色';
  position: absolute;
  top: -10px;
  right: var(--spacing-4);
  background: var(--color-warning);
  color: white;
  padding: 2px var(--spacing-2);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
}
 
/* 有徽章的卡片 */
.card:has(.badge) {
  padding-top: var(--spacing-8);
}
 
/* ===== 场景7:复杂条件组合 ===== */
/* 同时满足多个条件 */
.article:has(h1):has(p:first-of-type):has(img) {
  grid-column: span 2;
}
 
/* 否定组合 */
.card:not(:has(.skeleton)):not(:has(.loading)) {
  animation: fadeIn var(--duration-normal) var(--ease-out);
}

色彩函数详解

color-mix() 函数

color-mix() 函数允许你在 CSS 中混合两种颜色,这是创建色阶和变体的强大工具:

:root {
  /* 基础品牌色 */
  --blue-500: #3b82f6;
 
  /* 使用 color-mix 生成色阶 */
  --blue-50: color-mix(in srgb, var(--blue-500) 10%, white);
  --blue-100: color-mix(in srgb, var(--blue-500) 20%, white);
  --blue-200: color-mix(in srgb, var(--blue-500) 40%, white);
  --blue-300: color-mix(in srgb, var(--blue-500) 60%, white);
  --blue-400: color-mix(in srgb, var(--blue-500) 80%, white);
  /* 原色 */
  --blue-500: var(--blue-500);
  --blue-600: color-mix(in srgb, var(--blue-500) 90%, black);
  --blue-700: color-mix(in srgb, var(--blue-500) 80%, black);
  --blue-800: color-mix(in srgb, var(--blue-500) 70%, black);
  --blue-900: color-mix(in srgb, var(--blue-500) 50%, black);
 
  /* 色相旋转混合 */
  --purple-mix: color-mix(in srgb, var(--blue-500) 70%, hsl(300 100% 50%));
  --green-mix: color-mix(in srgb, var(--blue-500) 70%, hsl(150 100% 50%));
  --orange-mix: color-mix(in srgb, var(--blue-500) 70%, hsl(30 100% 50%));
 
  /* 不同色彩空间 */
  --blue-srgb: color-mix(in srgb, var(--blue-500) 50%, white);
  --blue-hsl: color-mix(in hsl, var(--blue-500) 50%, white);
  --blue-lab: color-mix(in lab, var(--blue-500) 50%, white);
  --blue-oklch: color-mix(in oklch, var(--blue-500) 50%, white);
}

OKLCH 色彩空间

OKLCH 是一种感知均匀的色彩空间,特别适合设计系统。OKLCH 代表 Lightness(亮度)、Chroma(色度)、Hue(色相),这三个参数更符合人类对颜色的感知方式:

:root {
  /* OKLCH 参数解释 */
  /* L: 亮度 (0-100%) - 感知亮度,越大越亮 */
  /* C: 色度 (0-0.4) - 饱和度/鲜艳度,越大越鲜艳 */
  /* H: 色相 (0-360) - 颜色角度,0/360 是红色,120 是绿色,240 是蓝色 */
 
  /* 蓝色系列 - 相同色相,不同亮度 */
  --blue-50: oklch(97% 0.02 250);
  --blue-100: oklch(94% 0.04 250);
  --blue-200: oklch(88% 0.08 250);
  --blue-300: oklch(76% 0.14 250);
  --blue-400: oklch(62% 0.20 250);
  --blue-500: oklch(52% 0.22 250);
  --blue-600: oklch(44% 0.20 250);
  --blue-700: oklch(36% 0.18 250);
  --blue-800: oklch(28% 0.14 250);
  --blue-900: oklch(22% 0.10 250);
 
  /* 彩虹色相 - 相同亮度和色度 */
  --red: oklch(52% 0.22 20);
  --orange: oklch(62% 0.22 50);
  --yellow: oklch(82% 0.18 90);
  --green: oklch(62% 0.20 145);
  --cyan: oklch(62% 0.20 200);
  --blue: oklch(52% 0.22 250);
  --purple: oklch(52% 0.22 300);
  --pink: oklch(62% 0.22 340);
 
  /* 主题色 - 高色度值 */
  --brand-primary: oklch(55% 0.25 250);
  --brand-secondary: oklch(65% 0.18 170);
  --brand-accent: oklch(70% 0.22 30);
}
 
/* relativeColor() - 相对颜色语法 */
.button {
  --base: #3b82f6;
  background: var(--base);
}
 
.button:hover {
  /* 在原有颜色基础上增加亮度 */
  background: oklch(from var(--base) calc(l + 0.1) c h);
}
 
.button:active {
  /* 在原有颜色基础上降低亮度 */
  background: oklch(from var(--base) calc(l - 0.1) c h);
}
 
.button:disabled {
  /* 降低色度使其更灰 */
  background: oklch(from var(--base) l calc(c * 0.3) h);
  opacity: 0.6;
}

light-dark() 函数

light-dark() 函数提供了简洁的明暗主题颜色切换方式:

/* 主题配置 */
:root {
  color-scheme: light dark;
}
 
body {
  /* 自动响应系统主题 */
  background: light-dark(white, #0f172a);
  color: light-dark(#0f172a, white);
}
 
/* 组件级主题切换 */
.card {
  background: light-dark(
    hsl(0 0% 100%),      /* 浅色模式:白色 */
    hsl(220 20% 10%)     /* 深色模式:深灰蓝 */
  );
 
  color: light-dark(
    hsl(220 10% 10%),    /* 浅色模式:深灰 */
    hsl(0 0% 95%)        /* 深色模式:近白 */
  );
 
  border: 1px solid light-dark(
    hsl(220 10% 90%),    /* 浅色模式:浅灰边框 */
    hsl(220 10% 20%)     /* 深色模式:深灰边框 */
  );
}

CSS 作用域与嵌套

@scope 作用域规则

CSS 作用域允许你限制选择器的应用范围,类似于 CSS Modules 的功能但不需要构建工具:

/* 定义作用域 */
@scope (.card) {
  .title {
    font-size: 1.5rem;
    font-weight: 600;
  }
 
  .content {
    color: var(--color-text-secondary);
    line-height: 1.6;
  }
 
  .footer {
    border-top: 1px solid var(--color-border);
    padding-top: var(--spacing-4);
    margin-top: var(--spacing-4);
  }
}
 
/* 作用域限制(to 语法)*/
/* .title 只在 .card 内但不匹配 .footer 时生效 */
@scope (.card) to (.footer) {
  .title {
    font-size: 1.5rem;
    color: var(--color-foreground);
  }
 
  /* 这个不会匹配 .footer 内的元素 */
  .actions {
    display: flex;
    gap: var(--spacing-2);
  }
}

CSS 嵌套语法

现代 CSS 支持原生的嵌套语法,不再需要 SCSS 或 PostCSS 插件:

/* 现代 CSS 嵌套语法 */
.card {
  padding: var(--spacing-6);
 
  /* 直接嵌套 */
  & .card-header {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
  }
 
  /* 伪类嵌套 */
  &:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
  }
 
  /* 伪元素嵌套 */
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: var(--color-primary);
  }
 
  /* 媒体查询嵌套 */
  @media (min-width: 768px) {
    padding: var(--spacing-8);
  }
 
  /* 复杂选择器 */
  & + & {
    margin-top: var(--spacing-6);
  }
 
  &--featured {
    border: 2px solid var(--color-primary);
  }
}

Subgrid 子网格

核心概念

Subgrid 允许嵌套网格继承父级网格的轨道,实现跨组件对齐。这是 Grid 布局的终极能力:

/* 父容器定义 grid */
.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-6);
}
 
/* 子网格继承父轨道 */
.grid-item {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid; /* 继承父级行轨道 */
}
 
.grid-item .card-header {
  /* 对齐到第一行 */
}
 
.grid-item .card-body {
  /* 对齐到第二行 */
}
 
.grid-item .card-footer {
  /* 对齐到第三行 */
}

实战:产品卡片网格

/* ===== 产品卡片网格 ===== */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  grid-template-rows: auto 1fr auto;
  gap: var(--spacing-6);
}
 
/* 产品卡片使用子网格 */
.product-card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  gap: var(--spacing-4);
  padding: var(--spacing-4);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
 
.product-card__image {
  aspect-ratio: 4/3;
  border-radius: var(--radius-md);
  overflow: hidden;
}
 
.product-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
 
.product-card__title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-foreground);
}
 
.product-card__description {
  color: var(--color-text-secondary);
  line-height: var(--line-height-relaxed);
}
 
.product-card__price {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-primary);
}

性能优化与最佳实践

content-visibility 渲染优化

/* 渲染优化 - 跳过离屏内容渲染 */
.article {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* 估计高度 */
}
 
.off-screen-section {
  content-visibility: hidden; /* 完全跳过渲染 */
}
 
/* 配合 contain 使用 */
.optimized-container {
  contain: layout style paint;
  content-visibility: auto;
  contain-intrinsic-size: 1000px; /* 滚动条计算 */
}

will-change 优化

/* 合理使用 will-change */
.animated-element {
  /* 动画开始前设置 */
  will-change: transform, opacity;
}
 
/* 动画结束后移除 */
.animated-element.end-animation {
  will-change: auto;
}
 
/* 不要在大量元素上使用 */
.bad-example .item {
  will-change: transform; /* 会创建大量合成层 */
}
 
/* 正确做法 */
.good-example {
  will-change: transform;
}
 
.good-example:hover .item {
  transform: translateY(-4px);
}

常见问题与解决方案

CSS 变量不生效

原因:变量未定义、选择器优先级问题或作用域问题。

/* 确保变量在正确作用域定义 */
:root {
  --color-primary: #3b82f6; /* 全局定义 */
}
 
.card {
  --color-primary: #10b981; /* 组件覆盖 */
}
 
/* 使用 calc() 时注意单位 */
:root {
  --spacing: 16; /* 无单位 */
}
 
.card {
  padding: calc(var(--spacing) * 1px); /* 需要乘以单位 */
}

Container Queries 不生效

原因:未定义 container-type、容器有 overflow 限制或父元素高度问题。

/* 正确设置容器 */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}
 
/* 确保父元素不限制尺寸 */
.card-wrapper {
  position: relative;
  width: 100%;
  max-width: none;
}

TIP

现代 CSS 策略:建立以 Custom Properties 为基础的设计 token 系统,使用 Container Queries 实现真正的组件级响应式,通过 Cascade Layers 管理样式优先级,:has() 简化复杂选择器。这些特性组合起来,可以实现以往需要 JavaScript 才能完成的设计交互。