关于 modern-css
面向编写或审查样式表的开发者的 CSS 指导技能,推荐现代原生 CSS 技术,并根据浏览器支持情况和旧版需求提供建议。
主要功能
- 01以现代 CSS 替代旧式写法
- 02按浏览器支持度划分技术等级
- 03提供回退和渐进增强建议
- 04涵盖布局、选择器、颜色和排版
适用场景
- 01重构旧式 CSS 写法
- 02为新界面选择 CSS 技术
- 03采用较新 CSS 前检查浏览器支持
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ caidanw/ skills --skill modern-css安装后在 Claude Code 中输入/modern-css确认已加载。
详细介绍
现代 CSS
CSS 规范已发生巨大变化。许多旧技巧——绝对定位居中、使用 padding-top 设置宽高比、JS 滚动监听器、Sass 颜色函数——如今都有简洁的原生替代方案。此技能可确保你始终优先采用现代方法。
参考:modern-css.com——所有技术均来源于此网站。访问各个页面,查看实时演示并深入了解。
**支持数据:**浏览器支持百分比代表全球用户覆盖率,数据来自 caniuse.com,最后更新于 February 2026。随着浏览器发布更新,这些数字会不断变化——如果项目需要精确数据,请在 caniuse 上核实。
如何使用此技能
编写或审查 CSS 时:
- 始终使用 广泛可用级别的技术(支持率 90% 以上),无需询问。 如果发现旧式模式,请将其重构为现代替代方案。
- 建议使用 最新可用级别的技术(支持率 80%–90%)。说明这些技术刚刚普及,并由用户决定。适用时提供回退方案。
- 使用有限可用级别的技术(支持率低于 80%)前,先行询问。提醒用户注意浏览器支持情况,并建议采用渐进增强方案或回退方案。
- 如果用户有旧版浏览器支持要求(例如旧版 Safari、企业浏览器),使用任何技术前都要先询问——即使是广泛可用的技术,因为支持率以现代浏览器版本为前提。使用
@supports提供回退方案。
如果在现有代码中发现旧式 CSS 模式,请指出并建议现代替代方案,简要说明其优势。
广泛可用(支持率 90% 以上)——始终使用
这些技术成熟可靠,可在所有现代浏览器中使用。请放心采用。
布局
元素居中——place-items: center
/* OLD: absolute + transform hack (5 declarations, 2 selectors) */
.parent { position: relative; }
.child {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
/* MODERN: grid centering (2 declarations, 1 selector) */
.parent {
display: grid;
place-items: center;
}
元素间距——gap
/* OLD: margin hacks with :last-child override */
.grid > * { margin-right: 16px; }
.grid > *:last-child { margin-right: 0; }
/* MODERN: gap on the container */
.grid {
display: flex;
gap: 16px;
}
宽高比——aspect-ratio
/* OLD: padding-top percentage hack */
.wrapper { padding-top: 56.25%; position: relative; }
.inner { position: absolute; inset: 0; }
/* MODERN: aspect-ratio property */
.video-wrapper {
aspect-ratio: 16 / 9;
}
定位简写——inset
/* OLD: four separate properties */
.overlay { top: 0; right: 0; bottom: 0; left: 0; }
/* MODERN: inset shorthand */
.overlay {
position: absolute;
inset: 0;
}
粘性页眉——position: sticky
/* OLD: JS scroll listener + getBoundingClientRect */
// add/remove .fixed class based on scroll position
/* MODERN: sticky positioning */
.header {
position: sticky;
top: 0;
}
响应式图片——object-fit
/* OLD: background-image with background-size */
.card-image {
background-image: url(...);
background-size: cover;
background-position: center;
}
/* MODERN: object-fit on an img element */
img {
object-fit: cover;
width: 100%;
height: 200px;
}
填充可用空间——stretch
/* OLD: calc workaround */
.full { width: calc(100% - 40px); }
/* MODERN: stretch keyword */
.full { width: stretch; }
/* fills container, respects margins */
防止滚动链——overscroll-behavior
/* OLD: JS wheel event preventDefault */
// modal.addEventListener('wheel', e => e.preventDefault(), { passive: false })
/* MODERN: overscroll-behavior */
.modal-content {
overflow-y: auto;
overscroll-behavior: contain;
}
防止滚动条导致布局偏移——scrollbar-gutter
/* OLD: force scrollbar or hardcode padding */
body { overflow-y: scroll; }
/* MODERN: reserve scrollbar space */
body { scrollbar-gutter: stable; }
网格模板区域——命名区域
/* OLD: grid line numbers or floats */
.item { grid-column: 1 / 3; grid-row: 2; }
/* MODERN: named grid areas */
.layout {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
.header { grid-area: header; }
模态对话框——<dialog> 元素
/* OLD: fixed overlay + JS open/close/ESC/focus-trap */
.overlay { position: fixed; z-index: 999; }
/* MODERN: native dialog with ::backdrop */
dialog { padding: 1rem; }
dialog::backdrop { background: rgb(0 0 0 / .5); }
/* JS: dialog.showModal() / dialog.close() */
适应方向的布局——逻辑属性
/* OLD: physical properties + RTL overrides */
margin-left: 1rem;
padding-right: 1rem;
[dir="rtl"] .box { margin-right: 1rem; }
/* MODERN: logical properties */
margin-inline-start: 1rem;
padding-inline-end: 1rem;
border-block-start: 1px solid;
响应式组件——@container
/* OLD: viewport-based media queries */
@media (min-width: 768px) {
.card { grid-template-columns: auto 1fr; }
}
/* MODERN: container queries */
.wrapper { container-type: inline-size; }
@container (width > 400px) {
.card { grid-template-columns: auto 1fr; }
}
滚动吸附——scroll-snap
/* OLD: Slick/Swiper carousel or touch handlers */
// $('.carousel').slick({ ... })
/* MODERN: CSS scroll snap */
.carousel {
scroll-snap-type: x mandatory;
overflow-x: auto;
}
.carousel > * { scroll-snap-align: start; }
选择器
选择父元素——:has()
/* OLD: JavaScript el.closest('.parent').classList.add(...) */
/* MODERN: :has() parent selector */
.card:has(img) {
grid-template-rows: auto 1fr;
}
分组选择器 — :is()
/* OLD: repeat the full selector for each match */
.card h1, .card h2, .card h3, .card h4 { margin-bottom: 0.5em; }
/* MODERN: :is() grouping */
.card :is(h1, h2, h3, h4) { margin-bottom: 0.5em; }
低优先级重置 — :where()
/* OLD: class-based resets with specificity issues */
.reset ul, .reset ol { margin: 0; }
/* MODERN: :where() has zero specificity */
:where(ul, ol) {
margin: 0;
padding-inline-start: 1.5rem;
}
仅键盘焦点 — :focus-visible
/* OLD: :focus fires on mouse click too, so people remove outlines (a11y fail) */
:focus { outline: 2px solid blue; }
/* MODERN: :focus-visible only shows for keyboard navigation */
:focus-visible {
outline: 2px solid var(--focus-color);
}
颜色
设置表单控件样式 — accent-color
/* OLD: appearance: none + rebuild the entire control */
input[type="checkbox"] { appearance: none; /* 20+ lines of custom styles */ }
/* MODERN: accent-color */
input[type="checkbox"],
input[type="radio"] {
accent-color: #7c3aed;
}
感知均匀的颜色 — oklch()
/* OLD: hex/rgb with guess-and-check for each shade */
--brand: #4f46e5;
--brand-light: #818cf8;
--brand-dark: #3730a3;
/* MODERN: oklch — adjust lightness, keep perceived hue */
--brand: oklch(0.55 0.2 264);
--brand-light: oklch(0.75 0.2 264);
--brand-dark: oklch(0.35 0.2 264);
广色域颜色 — display-p3 / oklch
/* OLD: sRGB only — washed out on P3 displays */
.hero { color: rgb(200, 80, 50); }
/* MODERN: oklch or display-p3 for vivid colors */
.hero { color: oklch(0.7 0.25 29); }
/* or: color(display-p3 1 0.2 0.1) */
磨砂玻璃 — backdrop-filter
/* OLD: pseudo-element with blurred background image */
.card::before { content: ''; background-image: url(bg.jpg); filter: blur(12px); z-index: -1; }
/* MODERN: backdrop-filter */
.glass {
backdrop-filter: blur(12px);
background: rgba(255, 255, 255, .1);
}
排版
流式排版 — clamp()
/* OLD: multiple media queries for each breakpoint */
h1 { font-size: 1rem; }
@media (min-width: 600px) { h1 { font-size: 1.5rem; } }
@media (min-width: 900px) { h1 { font-size: 2rem; } }
/* MODERN: clamp for fluid scaling */
h1 { font-size: clamp(1rem, 2.5vw, 2rem); }
多行文本截断 — line-clamp
/* OLD: JS to slice text by chars/words and add "..." */
/* MODERN: line-clamp */
.card-title {
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
}
首字下沉 — initial-letter
/* OLD: float hack with manual line-height */
.drop-cap::first-letter { float: left; font-size: 3em; line-height: 1; }
/* MODERN: initial-letter */
.drop-cap::first-letter { initial-letter: 3; }
字体加载 — font-display: swap
/* OLD: invisible text until font loads (FOIT) */
@font-face { font-family: "MyFont"; src: url(...); }
/* MODERN: font-display swap shows fallback immediately */
@font-face {
font-family: "MyFont";
src: url("MyFont.woff2");
font-display: swap;
}
可变字体 — 单个文件,包含所有字重
/* OLD: separate @font-face for each weight */
@font-face { font-weight: 400; src: url("Regular.woff2"); }
@font-face { font-weight: 700; src: url("Bold.woff2"); }
/* MODERN: one variable font file */
@font-face {
font-family: "MyVar";
src: url("MyVar.woff2");
font-weight: 100 900;
}
动画
独立变换
/* OLD: rewrite entire transform shorthand to change one value */
.icon { transform: translateX(10px) rotate(45deg) scale(1.2); }
/* MODERN: individual transform properties */
.icon {
translate: 10px 0;
rotate: 45deg;
scale: 1.2;
}
/* animate any one without touching the rest */
类型化自定义属性 — @property
/* OLD: custom properties are strings, can't animate */
:root { --hue: 0; }
.wheel { transition: --hue .3s; } /* ignored */
/* MODERN: @property makes them animatable */
@property --hue {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.wheel {
background: hsl(var(--hue), 80%, 50%);
transition: --hue .3s;
}
响应式裁剪路径 — shape()
/* OLD: clip-path: path() uses fixed px coordinates */
.shape { clip-path: path('M0 200 L100 0...'); }
/* MODERN: shape() uses responsive units */
.shape { clip-path: shape(from 0% 100%, ...); }
工作流
CSS 嵌套 — 无需预处理器
/* OLD: requires Sass/Less compiler */
// .nav { & a { color: #888; } }
/* MODERN: native CSS nesting */
.nav {
& a { color: #888; }
}
/* plain .css file, no build step */
层叠层 — @layer
/* OLD: specificity wars and !important */
.page .card .title.special { color: red !important; }
/* MODERN: cascade layers control order */
@layer base, components, utilities;
@layer utilities {
.mt-4 { margin-top: 1rem; }
}
主题变量 — 自定义属性
/* OLD: Sass variables compile to static values */
$primary: #7c3aed;
.btn { background: $primary; }
/* MODERN: CSS custom properties update at runtime */
:root { --primary: #7c3aed; }
.btn { background: var(--primary); }
深色模式默认设置 — color-scheme
/* OLD: manually restyle every form control for dark mode */
@media (prefers-color-scheme: dark) { input, select, textarea { ... } }
/* MODERN: color-scheme tells the browser to handle it */
:root { color-scheme: light dark; }
延迟渲染 — content-visibility
/* OLD: IntersectionObserver to defer rendering */
// new IntersectionObserver((entries) => { /* render */ }).observe(el)
/* MODERN: content-visibility auto */
.section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
新近可用(支持率为 80–90%)— 向用户推荐
这些技术近期已在主流浏览器中推出。向用户推荐这些技术, 说明其支持程度,并在需要时提供回退方案。
布局
下拉菜单 — [popover](86%)
/* OLD: display:none + JS toggle + click-outside + ESC */
.menu { display: none; }
.menu.open { display: block; }
/* MODERN: popover attribute */
/* <button popovertarget="menu">Toggle</button> */
/* <div id="menu" popover>...</div> */
#menu[popover] { position: absolute; }
子网格 — 将嵌套网格与父级网格线对齐(88%)
/* OLD: duplicate parent track definitions in child */
.child-grid { grid-template-columns: 1fr 1fr 1fr; }
/* MODERN: subgrid inherits parent tracks */
.child-grid {
display: grid;
grid-template-columns: subgrid;
}
可自定义选择框 — appearance: base-select(86%)
/* OLD: Select2 or Choices.js replacing native select */
/* MODERN: base-select unlocks full styling */
select, select ::picker(select) {
appearance: base-select;
}
注意:支持范围正在迅速扩大 — 请在 caniuse 上确认当前状态。 参考 →
悬停提示框 — popover=hint + interestfor(86%)
<button interestfor="tip">Hover me</button>
<div id="tip" popover=hint>Tooltip content</div>
动画
入场动画 — @starting-style(85%)
/* OLD: requestAnimationFrame to add class after paint */
// requestAnimationFrame(() => el.classList.add('visible'))
/* MODERN: @starting-style defines entry state */
.card {
transition: opacity .3s, transform .3s;
@starting-style {
opacity: 0;
transform: translateY(10px);
}
}
为 display: none 添加动画 — transition-behavior: allow-discrete(85%)
/* OLD: wait for transitionend, use visibility + opacity + pointer-events */
/* MODERN: allow-discrete enables display transitions */
.panel {
transition: opacity .2s, display .2s;
transition-behavior: allow-discrete;
}
.panel.hidden { opacity: 0; display: none; }
页面过渡 — View Transitions API(89%)
/* OLD: Barba.js or React Transition Group */
/* MODERN: View Transitions API */
/* JS: document.startViewTransition(() => updateDOM()); */
.hero { view-transition-name: hero; }
/* Style with ::view-transition-old(hero) / ::view-transition-new(hero) */
颜色
颜色混合 — color-mix()(89%)
/* OLD: Sass mix($blue, $pink, 60%) */
/* MODERN: color-mix in CSS */
background: color-mix(in oklch, #3b82f6, #ec4899);
颜色变体 — 相对颜色语法(87%)
/* OLD: Sass lighten($brand, 20%) — compile-time only */
/* MODERN: relative color syntax — runtime */
.btn {
background: oklch(from var(--brand) calc(l + 0.2) c h);
}
/* change --brand and all derivatives update */
深色模式颜色 — light-dark()(83%)
/* OLD: duplicate values in prefers-color-scheme media query */
@media (prefers-color-scheme: dark) { color: #eee; }
/* MODERN: light-dark() function */
color: light-dark(#111, #eee);
color-scheme: light dark;
选择器
表单验证 — :user-invalid / :user-valid(85%)
/* OLD: JS blur listener to add .touched class */
// el.addEventListener('blur', () => el.classList.add('touched'))
/* MODERN: :user-invalid only fires after interaction */
input:user-invalid { border-color: red; }
input:user-valid { border-color: green; }
工作流
作用域样式 — @scope(84%)
/* OLD: BEM naming (.card__title) or CSS Modules */
/* MODERN: @scope limits selectors to a root */
@scope (.card) {
.title { font-size: 1.25rem; }
.body { color: #444; }
}
/* .title only matches inside .card */
范围样式查询(88%)
/* OLD: multiple style() blocks for each value */
@container style(--p: 51%) {}
@container style(--p: 52%) {}
/* MODERN: range comparisons */
@container style(--progress > 50%) {
.bar { ... }
}
排版
均衡标题 — text-wrap: balance(87%)
/* OLD: manual <br> tags or Balance-Text.js */
/* MODERN: text-wrap balance */
h1, h2 { text-wrap: balance; }
支持有限(<80% 支持)——使用前请先询问
这些是前沿 CSS 功能。使用前务必询问用户,提醒其浏览器支持情况,并建议回退方案或渐进增强策略。
布局
模态框控制 — commandfor(72%)
<button onclick="document.querySelector('#dlg').showModal()">Open</button>
<button commandfor="dlg" command="show-modal">Open</button>
<dialog id="dlg">...</dialog>
回退方案:使用小型 JS onclick 处理程序调用 dialog.showModal()。
参考 →
对话框轻量关闭 — closedby="any"(72%)
<dialog closedby="any">Click outside to close</dialog>
回退方案:在 ::backdrop 上添加点击监听器,调用 dialog.close()。
参考 →
锚点定位 — position-anchor + anchor()(77%)
/* OLD: Popper.js / Floating UI for tooltip positioning */
/* MODERN: CSS anchor positioning */
.trigger { anchor-name: --tip; }
.tooltip {
position-anchor: --tip;
top: anchor(bottom);
}
回退方案:使用 Floating UI,或配合 JS 进行绝对定位。 参考 →
轮播控件 — ::scroll-button / ::scroll-marker(72%)
/* OLD: Swiper.js or Slick carousel */
/* MODERN: native scroll markers and buttons */
.carousel::scroll-button(right) { content: "→"; }
.carousel li::scroll-marker { content: ''; }
回退方案:使用 CSS scroll-snap,并通过 JS 添加自定义按钮。 参考 →
圆角形状 — corner-shape: squircle(67%)
/* OLD: clip-path polygon with 20+ points */
/* MODERN: corner-shape property */
.card {
border-radius: 2em;
corner-shape: squircle;
}
回退方案:使用标准的 border-radius 或 SVG clip-path。
参考 →
动画
滚动驱动动画 — animation-timeline: view()(78%)
/* OLD: IntersectionObserver + GSAP or AOS.js */
/* MODERN: CSS scroll-driven animations */
@keyframes reveal {
from { opacity: 0; translate: 0 40px; }
to { opacity: 1; translate: 0 0; }
}
.reveal {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
回退方案:使用 IntersectionObserver,通过切换 .visible 类来控制。
Polyfill:scroll-timeline
参考 →
高度自动动画 — interpolate-size: allow-keywords(69%)
/* OLD: JS to measure scrollHeight, set px, then transition */
/* MODERN: interpolate-size enables height:auto transitions */
:root { interpolate-size: allow-keywords; }
.accordion { height: 0; overflow: hidden; transition: height .3s ease; }
.accordion.open { height: auto; }
回退方案:使用较大的 max-height 值,或通过 JS 测量。
参考 →
错落动画 — sibling-index()(72%)
/* OLD: nth-child custom property for each item */
li:nth-child(1) { --i: 0; }
li:nth-child(2) { --i: 1; }
/* MODERN: sibling-index() function */
li {
transition-delay: calc(0.1s * (sibling-index() - 1));
}
回退方案:使用 nth-child 和 CSS 自定义属性。 参考 →
粘性/吸附元素样式 — 滚动状态容器查询(50%)
/* OLD: JS scroll listener checking element position */
/* MODERN: @container scroll-state() */
@container scroll-state(stuck: top) {
.header { box-shadow: 0 2px 8px rgb(0 0 0 / .1); }
}
回退方案:使用 IntersectionObserver 切换类。 参考 →
排版
垂直文本居中 — text-box(79%)
/* OLD: uneven padding hacks for optical centering */
.btn { padding: 10px 20px; padding-top: 8px; /* hack */ }
/* MODERN: text-box trims leading/trailing space */
h1, button {
text-box: trim-both cap alphabetic;
}
回退方案:手动调整内边距。 参考 →
自动增高文本区域 — field-sizing: content(73%)
/* OLD: JS input listener resizing on every keystroke */
/* MODERN: field-sizing */
textarea {
field-sizing: content;
min-height: 3lh;
}
回退方案:使用 JS 自动调整大小的处理程序。 参考 →
选择器
滚动侦测 — :target-current(48%)
/* OLD: IntersectionObserver with 15+ lines of JS */
/* MODERN: :target-current pseudo-class */
nav a:target-current {
color: var(--accent);
}
后备方案:使用 IntersectionObserver 切换 .active 类。
参考 →
工作流
CSS 函数 — @function(67%)
/* OLD: Sass @function */
// @function fluid($min, $max) { @return clamp(...); }
/* MODERN: native CSS @function */
@function --fluid(--min, --max) {
@return clamp(...);
}
后备方案:使用 Sass 函数或重复使用 clamp() 表达式。
参考 →
带类型的属性值 — 带类型的 attr()(42%)
/* OLD: JS reading dataset to set styles */
// el.style.width = el.dataset.pct + '%';
/* MODERN: attr() with type() */
.bar { width: attr(data-pct type(<percentage>)); }
后备方案:使用 JS 读取 data 属性并设置内联样式。 参考 →
内联条件 — if()(35%)
/* OLD: JS classList toggle */
// el.classList.toggle('primary', isPrimary)
/* MODERN: CSS if() function */
.btn {
background: if(style(--variant: primary): blue; else: gray);
}
后备方案:使用 CSS 自定义属性,并通过切换类来控制。 参考 →
核心原则
编写任何 CSS 时,都请遵循以下指导原则:
- 优先使用原生 CSS,而非 JavaScript — 如果 CSS 能做到,就不要使用 JS。使用滚动捕捉,而非轮播库;使用弹出框,而非切换脚本;使用对话框,而非模态框库。
- 优先使用逻辑属性,而非物理属性 — 使用
inline/block代替left/right/top/bottom。这样,你的布局在任何书写方向下都能正常工作。 - 优先使用内在尺寸,而非固定断点 — 使用
clamp()、min()、max()、容器查询,以及flex/grid自动调整尺寸。让内容决定布局。 - 优先使用自定义属性,而非预处理器变量 — CSS 自定义属性会在运行时更新,可与 JS 配合使用,并支持层叠。Sass 变量会在编译后消失。
- 优先使用层叠层,而非特异性权重技巧 — 使用
@layer组织层叠。不要再用!important或深层嵌套来争夺特异性权重。 - 渐进增强 — 使用现代功能并提供后备方案。使用
@supports通过能力检测,限制 Newly Available 和 Limited 功能的使用:/* fallback for older browsers */ .card { display: flex; flex-wrap: wrap; } /* modern upgrade */ @supports (container-type: inline-size) { .wrapper { container-type: inline-size; } @container (width > 400px) { .card { flex-direction: row; } } }
快速决策指南
| 你想要…… | 使用此方案 |
|---|---|
| 居中某个元素 | display: grid; place-items: center |
| 让子元素均匀分布 | 在 flex/grid 上使用 gap |
| 保持宽高比 | aspect-ratio: 16 / 9 |
| 滚动时固定页眉 | position: sticky; top: 0 |
| 创建响应式组件 | @container 查询 |
| 创建模态框 | <dialog> 元素 |
| 创建下拉菜单 | [popover] 属性 🟡 |
| 根据子元素选择父元素 | :has() 选择器 |
| 仅设置键盘焦点样式 | :focus-visible |
| 设置流体字体大小 | clamp(min, preferred, max) |
| 截断多行文本 | line-clamp: N |
| 平衡标题换行 | text-wrap: balance 🟡 |
| 滚动时触发动画 | animation-timeline: view() ⚠️ |
| 页面过渡 | View Transitions API 🟡 |
| 设置元素进入视口时的动画 | @starting-style 🟡 |
| 根据一种颜色创建调色板 | 使用调整亮度的 oklch() |
| 混合两种颜色 | color-mix(in oklch, ...) 🟡 |
| 设置深色模式颜色 | light-dark() + color-scheme 🟡 |
| 将样式限定到某个组件 | @scope (.component) 🟡 |
| 控制层叠顺序 | @layer |
| 嵌套选择器 | 原生 CSS 嵌套 & selector |
| 为自定义属性指定类型 | @property |
| 定位工具提示 | CSS 锚点定位 ⚠️ |
无标记 = 广泛可用(90% 以上,始终使用)。 🟡 = 新近可用(80–90%,建议使用)。⚠️ = 受限(低于 80%,请先询问)。