Emmet: 自定义属性 `[attr=val]` 与编号 `$`:表格表单列表序号简写

前 7 课你已经能用 Emmet 写出任意嵌套、任意属性、任意重复的 HTML。但还有两类"工厂化"场景没解决——"10 个 input 每个 name 不同" 和 "100 行表格 序号自动递增"。这两个场景分别靠 自定义属性 [attr=val]编号 $ 解决,是 Emmet 处理"批量生成"的最后两块拼图。

1. 你将学到


2. 一个前端开发者的真实故事

(1) 痛点:表单字段多 + 表格行多,手写到手指断

Alice 接到一个登录 + 注册的合并表单任务,需要 5 个 input(用户名、邮箱、密码、确认密码、记住我)和 1 个 submit 按钮。她写了一个表单后发现,input 的 name 几乎长得一样——name="user"name="email"name="pwd" 等等,每个都要手敲。

更要命的是注册页的"用户协议"表格:100 行协议条款,每行 1 个 tr,第 1 列是序号(1-100),第 2 列是条款文字。Alice 试着手写:tr>td{1}+td{条款 1}tr>td{2}+td{条款 2}、……写到第 30 行她崩溃了。光写 30 行类似的"tr + 2 个 td"就占了她 30 分钟。

最后 Alice 试着写完所有字段后,又要在 input 上加 aria-label="Username input"data-validate="email" 这种 HTML5 属性——每个属性都得查规范、写等号、加引号。30 行 input 让她写了 90 行 HTML。

(2) [attr=val]$ 的解法

Emmet 提供两个互补机制解决 Alice 的问题:

自定义属性 [attr=val]:把任意属性写在方括号内,像 HTML 一样直接呈现——Alice 的每个 input 可以用 input[name=user type=text] 一行写完,aria 和 data-* 属性同样可以放在方括号内。

编号 $:让重复元素的 class、id、属性值自动递增。100 行协议的序号可以用 td.item$@100*100 一行生成(从 100 开始递减),条款文字用 {text} 填充(第 09 课展开)。

Alice 用一行 Emmet 重写整个表单:

TEXT 📖 仅展示
form>(input[name=user type=text placeholder="Username"])+(input[type=email name=email])+(input[type=password name=pwd])+(input[type=password name=pwd-confirm])+(label>input[type=checkbox name=remember]+{记住我})+(button[type=submit]{登录})

100 行协议表格:

TEXT 📖 仅展示
(table>(tr>td{item $}*3)*5)

Alice 验证之后惊呼:"5 个 input 字段的手写从 25 行降到 1 行,100 行协议表格从 350 行降到 1 行。Emmet 在表单和表格场景的提效是 30 倍以上。"

(3) 收益

[attr=val]$ 是 Emmet 处理"重复 + 工厂化"场景的核心。前者让你在一行简写里覆盖所有 HTML5 属性,后者让重复元素的序号自动跑。学完本课,Alice 写 5 字段表单只要 12 秒,100 行表格只要 10 秒;同时这一课也是 Phase 2 HTML 简写语法的收官——后续 9 课将进入 CSS 简写与编辑器 Actions。


3. 自定义属性 [attr=val]:方括号里的 HTML5 属性

[attr=val] 是 Emmet 给标签添加任意属性的语法。写在方括号内的内容会原样成为 HTML 属性——无论是 HTML5 标准属性(colspantypeplaceholder),HTML5 新增属性(data-*aria-*),还是自定义属性。

(1) 简写规则

简写 展开结果 说明
td[title="Hello"] <td title="Hello"></td> 单属性 + 双引号
td[colspan=3] <td colspan="3"></td> 单属性 + 无引号(值无空格时可省)
td[title colspan=3] <td title="" colspan="3"></td> 缩写形式(无值属性)
input[type=text name=user] <input type="text" name="user"> 多属性空格分隔
a[href="#" data-id=$] <a href="#" data-id="1"></a> data-* 属性 + 编号占位
💡 提示: [attr] 无值形式(如 [disabled])会生成 attr=""——值为空字符串。如果你想要 boolean 属性(如 <input disabled>),Emmet 不会自动去掉值,需要手动改。但大多数现代 HTML 标准属性都支持值形式,处理起来一致。

📌 重点: 方括号里的内容完全按 HTML5 属性语法解析——值可以用单引号、双引号、或无引号(无空格场景);多个属性用空格分隔。注意: 这里空格是合法分隔符,与 Emmet 简写本身"空格 = 停止符"不同。方括号内是"封闭"上下文。


4. 编号 $:自动递增的占位符

$ 是 Emmet 的编号占位符——在 class、id、属性值甚至文本位置写 $,配合 *N 重复,会自动被替换为当前序号(默认从 1 开始)。

(1) $ 的使用位置

位置 简写 展开(*3
class 内 li.item$*3 <li class="item1"><li class="item2"><li class="item3">
id 内 div#user$*3 <div id="user1"> ……
属性值内 a[data-id=$]*3 <a data-id="1"> ……
文本内({}) a{链接 $}*3 <a href="">链接 1</a> ……(第 09 课展开)

(2) 多位补零 $$$

多个 $ 串联表示多位补零

$ 展开形态 适用场景
item$ item1 item2 item3 列表、菜单
item$$ item01 item02 item03 小型列表
item$$$ item001 item002 item003 表格行、卡片
item$$$$ item0001 …… 罕见(千行以上)

(3) 反向 $-$-N

默认编号从 1 开始升序。用 @- 改为降序,用 @N 改为从 N 开始:

修饰符 含义 示例 (*3)
$(默认) 升序,从 1 1, 2, 3
$-@- 降序(最大值降到 1) 3, 2, 1
$@3 升序,从 3 开始 3, 4, 5
$-3@-3 降序,从 3 开始倒推 3, 2, 1
📌 重点: $@N 中 N 是起始值,不是结束值。常用于"从指定编号开始"的场景(如分页页码、产品 SKU 编号、版本号)。


5. 实战示例

下面 5 个示例覆盖 [attr]$ 的全部核心组合——从单属性到多属性,从基础编号到多位补零,从反向到表格。

▶ 示例:基础属性 td[title="Hello" colspan=3](难度⭐)

在 td 上加 title 和 colspan 两个属性。这是 HTML 表格最经典的属性组合之一。

缩写:

HTML
td[title="Hello" colspan=3]

展开:

HTML
<td title="Hello" colspan="3"></td>

输出:

TEXT 📖 仅展示
双引号包围含空格的 title("Hello"),无引号包围无空格的值(3)。Emmet 自动给所有属性加上引号,无论你简写时是否带。多个属性用空格分隔,这与 Emmet 自身的"空格=停止符"规则不同——在方括号内空格有特殊含义。colspan=3 表示单元格横向跨 3 列。

▶ 示例:input 属性 input[type=text name=user placeholder=Username](难度⭐)

一次性给 input 加 type、name、placeholder 三个属性。input 是 HTML 中最常用"属性密集"标签之一。

缩写:

HTML
input[type=text name=user placeholder=Username]

展开:

HTML
<input type="text" name="user" placeholder="Username">

输出:

TEXT 📖 仅展示
三个属性在一行简写内完成。注意:Emmet 智能默认为 input 添加 type="text"(即使你不写 type),所以这里即使简写为 input[name=user placeholder=Username],展开后仍会带 type="text"。如果想要 type=password 或 type=email,必须显式写。三属性连写是表单场景最高频的简写。

▶ 示例:单 $ 编号 ul>li.item$*5(难度⭐)

5 个 li,每个 li 的 class 自动带 1-5 编号——Emmet 官方文档的首选案例。

缩写:

HTML
ul>li.item$*5

展开:

HTML
<ul>
  <li class="item1"></li>
  <li class="item2"></li>
  <li class="item3"></li>
  <li class="item4"></li>
  <li class="item5"></li>
</ul>

输出:

TEXT 📖 仅展示
5 个 li,class 依次为 item1、item2、item3、item4、item5。这是 Emmet 唯一命名编号(item + 数字)的标准模式。如果 class 名不需要数字部分,直接写 .item(不带 $),所有 li 都共享同一个 class。

▶ 示例:多 $$$ 补零 ul>li.item$$$*5(难度⭐)

用 3 个 $ 让编号自动补零到 3 位。

缩写:

HTML
ul>li.item$$$*5

展开:

HTML
<ul>
  <li class="item001"></li>
  <li class="item002"></li>
  <li class="item003"></li>
  <li class="item004"></li>
  <li class="item005"></li>
</ul>

输出:

TEXT 📖 仅展示
补零位数 = $ 的个数。3 个 $ 对应 3 位补零(item001、item002...)。补零场景常用于:订单号(前缀补零,便于排序)、表格 ID、SKU、章节编号。如果想要 4 位补零,用 item$$$$;想要 5 位则用 item$$$$$。注意:位数比实际数字少时,会截断(如 *3 用 $$$ 会变成 item001、002、003,恰好 3 位无截断;如果是 *12 用 $$,会变成 item01、02、03、04、05、06、07、08、09、10、11、12,前两位补零后超过 2 位则用实际位数)。

▶ 示例:表格 + 编号 (table>(tr>td{item $})*3)(难度⭐⭐)

分组 + 重复 + 文本填充——3 行 ×1 列的"编号列表"用分组让 td 跟随每个 tr 重复。

缩写:

HTML
(table>(tr>td{item $})*3)

展开:

HTML
<table>
  <tr>
    <td>item 1</td>
  </tr>
  <tr>
    <td>item 2</td>
  </tr>
  <tr>
    <td>item 3</td>
  </tr>
</table>

输出:

TEXT 📖 仅展示
解析:外层括号把整个表格封装为一个独立单元;内层 (tr>td{item $}) 是"一行一个 item N"的模板,*3 让模板重复 3 次。每次重复,$ 替换为当前编号(1、2、3),最终 3 个 tr 各自包含一个 td,文案为 "item 1"、"item 2"、"item 3"。这是 Emmet 在"协议条款编号"、"待办清单序号"、"商品列表"等场景的核心模式。如果想让每一行有多个单元格,把 td{item $} 改成 td{item $}+td{...} 即可(第 09 课展开文本填充)。

6. 完整示例:表单字段集 + 多位补零 ID

[attr]$ 组合起来——一个有 5 个 input 字段的注册表单,每个 field 都有 3 位补零的 id。

▶ 示例:注册表单 + 3 位 ID(难度⭐⭐)

缩写:

HTML
form#register>(fieldset>legend{账户信息})+(input#field-001.form-control[type=text name=user])+(input#field-002.form-control[type=email name=email])+(input#field-003.form-control[type=password name=pwd])+(label>input#field-004[type=checkbox name=news]+{订阅资讯})+(button#btn-100.btn.btn-primary[type=submit]{注册})

展开:

HTML
<form id="register">
  <fieldset>
    <legend>账户信息</legend>
  </fieldset>
  <input id="field-001" class="form-control" type="text" name="user">
  <input id="field-002" class="form-control" type="email" name="email">
  <input id="field-003" class="form-control" type="password" name="pwd">
  <label>
    <input id="field-004" type="checkbox" name="news">
    订阅资讯
  </label>
  <button id="btn-100" class="btn btn-primary" type="submit">注册</button>
</form>

输出:

TEXT 📖 仅展示
解析:本例综合了 Phase 2 的全部操作符——> (嵌套)、+ (兄弟)、#id、.class、[] (属性)、{} (文本填充)。注意:每个 input 的 id 都是手写的,因为 Emmet 的 $ 只能用在 *N 重复结构内。这里 5 个 input 共用了"id 手动 + class 自动"的混合写法——id 是表单的"字段锚点"(JS 钩子),必须唯一;class 是"字段类型"(CSS 选择器),可复用。这种"手写 id + 自动 class"是表单开发的高频模式。

🌐 动手试试:Emmet 官方 Cheat Sheet 中输入 td[title="Hello world!" colspan=3] 看实时展开。


❓ 常见问题

Q [attr][attr=] 有什么区别?
A [attr] 简写等价于 [attr=""](空字符串值),展开为 <tag attr="">[attr=] 显式赋空字符串,展开为同样的 <tag attr="">。两种写法结果完全等价。在 HTML 语义上,空值属性(如 disabled="")与仅出现属性名(disabled)行为相同——但 Emmet 默认生成值形式,需要手动精简。
Q [data-id=$] 不带 * 还能用吗?
A 能,但 $ 在没有 * 重复时默认显示 1。所以 a[data-id=$] 会展开为 <a data-id="1"></a>——这通常不是你想要的。如果只是想要一个数据 ID 而不参与编号,应当用 a[data-id=user] 写实际值。
Q @-$- 哪个对?
A 在 Emmet 官方文档中,两种写法都支持:@N 表示从 N 开始,@- 表示降序。组合写法 $-3 等价于 @-3 都表示降序从 3 开始。推荐用 @ 前缀的写法($@3@3@-3),更清晰且兼容性好。

📖 小节


📝 作业

  1. 基础题(难度⭐):用一行 Emmet 简写展开为:(a) 一个 a 标签带 href="#"class="link"data-id="$"(注意 $ 不带 * 默认为 1);(b) 一个 tdtitle="Cell"colspan=2rowspan=3;(c) 一个 ul>li*5,每个 li 的 class 名为 row-$(单数字编号)。
  2. 进阶题(难度⭐⭐):写一个 5 行 4 列的"成绩表",第 1 行是表头(th),第 2-5 行是数据(td),数据 cell 的 id 用 3 位补零(cell-001cell-020)。要求完全用一行 Emmet 简写,并解释为什么 id 用 $ 实现而不能用 class。
  3. 挑战题(难度⭐⭐⭐):编写一个 100 行"用户协议表格",3 列结构:(a) 序号列用 td.item$@100*100 反向编号(从 100 降到 1);(b) 标题列用 {协议 $} 文本填充(文本从 09 课展开);(c) 内容列先留空。要求全部用一行 Emmet 简写,验证展开结果,并对比手写 100 行 vs 一行 Emmet 简写的时间差异。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏