帮助选择 CSS 嵌套的语法

CSS 工作组正在继续讨论在 CSS 中定义嵌套的最佳方式。如果您是 CSS 编写者,我们希望能得到您的帮助。

嵌套是 Sass 等工具中非常受欢迎的功能。它可以节省 Web 开发人员重复编写相同选择器的时间,并能使代码更清晰、更易于理解。

未嵌套的 CSS

.class1 {
  color: green;
}
.class1 .class2 {
  border: 5px solid black;
}

Sass 中的嵌套

.class1 {
  color: green;
  .class2 {
    border: 5px solid black;
  }
}

每个人都希望 CSS 嵌套能使用 Sass 那种简单的语法。然而,由于浏览器解析引擎的工作方式,这是不可能的。如果它们看到 element:pseudo 这样的序列,它们会将整个样式规则解析为 property: value 声明。

因此,关于替代方案的漫长讨论开始了。今年夏天早些时候,CSSWG 在选项1、选项2和选项3之间进行了辩论。其中,选项3获胜。从那时起,又提出了两个选项,选项4和选项5。如果您还记得 53+个问题 中讨论的各种细节,请将所有那些旧的想法放在一边。目前,我们只在这篇文章中描述的选项3、4和5之间进行辩论,如下面的示例所示。

为了帮助我们决定选择哪个选项,我们希望您阅读这些示例并回答一个问题调查。

请查看这些示例,思考您如何编写和维护样式表,并深入思考您偏好哪种语法。然后投票选出您认为最佳的那个。

在这三个选项中,& 符号是一个标记,表示“将嵌套外部的选择器放在 [此处]”。例如……

这个……

.foo {
  & .bar {
    color: blue;
  }
}

……变成这个。

.foo .bar {
  color: blue;
}

还有这个……

.foo {
  .bar & {
    color: blue;
  }
}

……变成这个。

.bar .foo {
  color: blue;
}

明白了么?

在下面的许多示例中,& 是可选的。有些开发人员会使用它,因为它有助于使他们的代码更具可读性。其他开发人员则会选择省略它,特别是在从非嵌套上下文中复制粘贴代码时。当省略 & 时,外部选择器会作为祖先添加到开头。(当我们调查作者时,他们对可选 & 的使用意见各占 50%。)

但选项 3 *(且仅限选项 3)*的关键技巧是——如果您使用的是元素选择器(如 particlediv),则 & 是必需的。如果您使用的是任何其他选择器,如类或 ID,您可以选择省略 &。最简单的记忆方式是:选择器不能以字母开头。它必须以符号开头。CSSWG 中的一些人认为这会很容易记住和做到。其他人则想知道,鉴于 CSS 中放置在其他任何地方的样式规则都没有这样的语法限制,这是否会造成混淆,以一种难以调试的方式使开发人员陷入困境。

在比较这些选项时,请考虑您的整个团队将如何处理嵌套代码。考虑将代码从一个项目复制粘贴到另一个项目会是什么样子。哪个选项能让在嵌套上下文内外编码变得容易?哪个能最容易地阅读、编写和编辑大量的 CSS?

哪个对 CSS 的未来最有利?三十年后,当人们编写 CSS 时——当今天的习惯和期望完全被遗忘,当未来几代人从未听说过 Sass 时——哪个选项能让编写这种语言变得轻松优雅?

请务必仔细阅读*所有*示例。某个选项在一个示例中可能显得是您的最爱,但在另一个示例中却存在您不喜欢的问题。

比较选项

  • 选项 5:顶层 @nest:嵌套样式规则在专用的、独立的 at-rule 中声明,该规则只接受样式规则。声明可以使用 & { .. } 进行嵌套。
  • 选项 4:后缀块:样式规则允许在声明块后添加一个可选的第二个块,该块只包含样式规则。
  • 选项 3:非字母开头:嵌套样式规则可以直接添加到声明块中,但不能以字母开头。

示例 A:基础

未嵌套的 CSS

article {
  font-family: avenir;
}
article aside {
  font-size: 1rem;
}

选项 5

@nest article {
  & {
    font-family: avenir;
  }
  aside {
    font-size: 1rem;
  }
}

选项 4

article {
  font-family: avenir;
} {
  aside {
    font-size: 1rem;
  }
}

选项 3

article {
  font-family: avenir;
  & aside {
    font-size: 1rem;
  }
}

示例 B:带语法变体

开发人员喜欢以许多不同的方式来组织他们的 CSS——使用制表符或空格,将 { } 大括号与规则放在同一行或分开的行上,按特定顺序排列规则。编写嵌套 CSS 时也会如此。示例 B 阐述了每种选项下格式化嵌套代码的几种可能变体。这完全取决于您的个人偏好。

未嵌套的 CSS

.foo {
  color: red;
}
.foo .bar {
  color: blue;
}
.foo p {
  color: yellow;
}

选项 5

@nest .foo {
  & {
    color: red;
  }
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

或者你可以使用这种语法

@nest .foo {{
  color: red; }
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

选项 4

.foo {
  color: red;
} {
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

或者你可以这样格式化

.foo {
  color: red; } {
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

选项 3

.foo {
  color: red;
  .bar {
    color: blue;
  }
  & p {
    color: yellow;
  }
}

或者你可以使用这种语法

.foo {
  color: red;
  & .bar {
    color: blue;
  }
  & p {
    color: yellow;
  }
}

示例 C:在元素选择器内重新嵌套

对于选项 3,有一种情况仅使用 & 是不够的——当我们希望 & 引用的父选择器出现在嵌套选择器*之后*时。由于我们不能以 & 开头,我们需要使用类似 :is():where() 的方式,以便以符号而不是字母开头。

未嵌套的 CSS

a:hover {
  color: hotpink;
}
aside a:hover {
  color: red;
}

选项 5

@nest a:hover {
  & {
    color: hotpink;
  }
  aside & {
    color: red;
  }
}

选项 4

a:hover {
  color: hotpink;
} {
  aside & {
    color: red;
  }
}

选项 3

a:hover {
  color: hotpink;
  :is(aside) & {
    color: red;
  }
}

示例 D:零未嵌套声明 + 各种选择器

未嵌套的 CSS

:has(img) .product {
  margin-left: 1rem;
}
:has(img) h2 {
  font-size: 1.2rem;
}
:has(img) > h3 {
  font-size: 1rem;
}
:has(img):hover {
  box-shadow: 10px 10px;
}
a:has(img) {
  border: none;
}

选项 5

@nest :has(img) {
  .product {
    margin-left: 1rem;
  }
  h2 {
    font-size: 1.2rem;
  }
  > h3 {
    font-size: 1rem;
  }
  &:hover {
    box-shadow: 10px 10px;
  }
  a& {
    border: none;
  }
}

选项 4

:has(img) {} {
  .product {
    margin-left: 1rem;
  }
  h2 {
    font-size: 1.2rem;
  }
  > h3 {
    font-size: 1rem;
  }
  &:hover {
    box-shadow: 10px 10px;
  }
  a& {
    border: none;
  }
}

选项 3

:has(img) {
  .product {
    margin-left: 1rem;
  }
  & h2 {
    font-size: 1.2rem;
  }
  > h3 {
    font-size: 1rem;
  }
  &:hover {
    box-shadow: 10px 10px;
  }
  :is(a&) {
    border: none;
  }
}

示例 E:嵌套中的嵌套

未嵌套的 CSS

table.colortable td {
  text-align: center;
}
table.colortable td .c {
  text-transform: uppercase;
}
table.colortable td:first-child,
table.colortable td:first-child + td {
  border: 1px solid black;
}
table.colortable th {
  text-align: center;
  background: black;
  color: white;
}

选项 5

@nest table.colortable {
  @nest td {
    & {
      text-align: center;
    }
    .c {
      text-transform: uppercase;
    }
    &:first-child,
    &:first-child + td {
       border: 1px solid black;
    }
  }
  th {
    text-align: center;
    background: black;
    color: white;
  }
}

选项 4

table.colortable {} {
  td {
    text-align: center; }{
    .c {
      text-transform: uppercase;
    }
    &:first-child,
    &:first-child + td {
       border: 1px solid black;
    }
  }
  th {
    text-align: center;
    background: black;
    color: white;
  }
}

选项 3

table.colortable {
  & td {
    text-align: center;
    .c {
      text-transform: uppercase;
    }
    &:first-child,
    &:first-child + td {
       border: 1px solid black;
    }
  }
  & th {
    text-align: center;
    background: black;
    color: white;
  }
}

示例 F:与媒体查询集成

未嵌套的 CSS

ol, ul {
  padding-left: 1em;
}

@media (max-width: 30em){
  .type ul,
  .type ol {
    padding-left: 0;
  }
}

选项 5

@nest ol, ul {
  & {
    padding-left: 1em;
  }
  @media (max-width: 30em){
    .type & {
      padding-left: 0;
    }
  }
}

选项 4

ol, ul {
  padding-left: 1em;
} {
  @media (max-width: 30em){
    .type & {
      padding-left: 0;
    }
  }
}

选项 3

ol, ul {
  padding-left: 1em;
  @media (max-width: 30em){
    .type & {
      padding-left: 0;
    }
  }
}

示例 G:与层叠层集成

未嵌套的 CSS

@layer base {
  html {
    width: 100%;
  }
  @layer support {
    html body {
      min-width: 100%;
    }
  }
}

选项 5

@layer base {
  @nest html {
    & {
      width: 100%;
    }
    @layer support {
      body {
        min-width: 100%;
      }
    }
  }
}

选项 4

@layer base {
  html {
    width: 100%;
  } {
    @layer support {
      body {
        min-width: 100%;
      }
    }
  }
}

选项 3

@layer base {
  html {
    width: 100%;
    @layer support {
      & body {
        min-width: 100%;
      }
    }
  }
}

现在您已经有机会理解了这三个选项的示例,或许还自己尝试了一些,您认为未来 CSS 的编写方式应该是什么样的?

哪个选项对 CSS 的未来最有利?

  • 选项 5
    9%
     
  • 选项 4
    5%
     
  • 选项 3
    86%
     

如果您想进一步描述您的想法,请在 Twitter 上回复 @webkit,或在 Mastodon 上回复 @jensimmons@front-end.social

感谢您的反馈。设计一门编程语言需要考虑很多因素,不仅仅是像这样的调查结果。但了解 Web 开发人员在阅读示例后的想法将有助于我们正在进行的讨论。

请将这份调查转发给您认识的其他 CSS 编写者,发布到社交媒体,帮助我们传播消息。发表意见的人越多越好。

我们特别希望听到来自从事各种项目的开发人员的意见——从网站到 Web 应用,从原生应用到数字标牌,再到印刷书籍。无论是大型项目还是小型项目,庞大或微小的团队,全新的代码库还是几十年前的代码库。无论构建在何种框架、CMS、构建系统之上……动态或静态,服务器端渲染或客户端渲染……全球各地的人们使用 CSS 的方式真是*太多*了。我们需要考虑所有这些情况。