Emmet: Custom Attributes `[attr=val]` and Numbering `$`
After the first 7 lessons, you can already write HTML with arbitrary nesting, arbitrary attributes, and arbitrary repetition using Emmet. But two "factory-style" scenarios remain unsolved—"10 inputs each with a different name" and "100 table rows with auto-incrementing numbers." These two scenarios are solved by custom attributes
[attr=val]and numbering$respectively—the final two puzzle pieces for Emmet's batch-generation capabilities.
1. What You Will Learn
- Custom attributes
[attr=val]: add any attribute including HTML5 data-* and aria-* - Abbreviated attributes:
[attr]shorthand (no value), chaining multiple attributes - Numbering
$: insert auto-incrementing sequence numbers in class, id, and attribute values - Multi-digit zero-padding
$$$: auto-generate 001, 002, 003 - Numbering direction and starting value:
@-for reverse,@Nto start counting from N - Combined application: table + attributes + numbering + implicit tags
2. A Frontend Developer's Real Story
(1) The Pain: Too Many Form Fields + Too Many Table Rows
Alice received a task to build a combined login + registration form with 5 inputs (username, email, password, confirm password, remember me) and 1 submit button. After writing one form, she realized that all the input names looked nearly identical—name="user", name="email", name="pwd", and so on—each one had to be typed manually.
Even worse was the "Terms of Service" table on the registration page: 100 rows of terms, each row a tr, with column 1 being a sequence number (1–100) and column 2 being the term text. Alice tried hand-writing: tr>td{1}+td{Term 1}, tr>td{2}+td{Term 2}, ... by row 30 she gave up. Just writing 30 similar "tr + 2 td" lines took her 30 minutes.
Finally, after writing all the fields, Alice also needed to add HTML5 attributes like aria-label="Username input" and data-validate="email" to the inputs—each attribute required checking specs, typing equals signs, and adding quotes. 30 input lines ballooned into 90 lines of HTML.
(2) How [attr=val] and $ Solve It
Emmet provides two complementary mechanisms to solve Alice's problems:
Custom attributes [attr=val]: Write any attribute inside square brackets, presented directly like HTML—Alice can write each input as input[name=user type=text] in one line, and aria and data-* attributes can similarly go inside the brackets.
Numbering $: Makes the class, id, and attribute values of repeated elements auto-increment. The 100 terms' sequence numbers can be generated with td.item$@100*100 in one line (starting from 100, descending), and the term text can be filled with {text} (covered in Lesson 09).
Alice rewrote the entire form in one line of Emmet:
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]+{Remember me})+(button[type=submit]{Login})
And the 100-row terms table:
(table>(tr>td{item $}*3)*5)
After verifying, Alice exclaimed: "5 input fields went from 25 hand-written lines to 1 line, and the 100-row terms table went from 350 lines to 1 line. Emmet's efficiency boost in form and table scenarios is 30x or more."
(3) Benefits
[attr=val] and $ are Emmet's core operators for handling "repetition + factory-style" scenarios. The former lets you cover all HTML5 attributes in one line of abbreviation, while the latter makes sequence numbers run automatically. After this lesson, Alice can write a 5-field form in 12 seconds and a 100-row table in 10 seconds. This lesson also marks the conclusion of Phase 2—the remaining 9 lessons will cover CSS shorthand and editor Actions.
3. Custom Attributes [attr=val]: HTML5 Attributes in Square Brackets
[attr=val] is Emmet's syntax for adding arbitrary attributes to a tag. Content written inside square brackets becomes raw HTML attributes—whether standard HTML5 attributes (colspan, type, placeholder), newer HTML5 attributes (data-*, aria-*), or custom attributes.
(1) Shorthand Rules
| Abbreviation | Expansion | Notes |
|---|---|---|
td[title="Hello"] |
<td title="Hello"></td> |
Single attribute with double quotes |
td[colspan=3] |
<td colspan="3"></td> |
Single attribute without quotes (OK when value has no spaces) |
td[title colspan=3] |
<td title="" colspan="3"></td> |
Abbreviated form (valueless attributes) |
input[type=text name=user] |
<input type="text" name="user"> |
Multiple attributes separated by spaces |
a[href="#" data-id=$] |
<a href="#" data-id="1"></a> |
data-* attribute with numbering placeholder |
Tip: The
[attr]valueless form (e.g.,[disabled]) generatesattr=""—the value is an empty string. If you want a boolean attribute (e.g.,<input disabled>), Emmet won't automatically strip the value; you'll need to edit it manually. However, most modern HTML standard attributes support the value form, keeping things consistent.
Key Point: The content inside square brackets is parsed entirely according to HTML5 attribute syntax—values can use single quotes, double quotes, or no quotes (when there are no spaces); multiple attributes are separated by spaces. Note: here, spaces are valid delimiters, unlike Emmet's own "space = stop character" rule. Square brackets form a "closed" context.
4. Numbering $: The Auto-Incrementing Placeholder
$ is Emmet's numbering placeholder—write $ in a class, id, attribute value, or even text position, pair it with *N repetition, and it is automatically replaced with the current sequence number (default starting from 1).
(1) Where $ Can Be Used
| Location | Abbreviation | Expansion (*3) |
|---|---|---|
| Inside class | li.item$*3 |
<li class="item1">, <li class="item2">, <li class="item3"> |
| Inside id | div#user$*3 |
<div id="user1"> ... |
| Inside attribute value | a[data-id=$]*3 |
<a data-id="1"> ... |
| Inside text ({}) | a{Link $}*3 |
<a href="">Link 1</a> ... (covered in Lesson 09) |
(2) Multi-Digit Zero-Padding $$$
Chaining multiple $ characters produces multi-digit zero-padding:
$ Count |
Expanded Form | Use Case |
|---|---|---|
item$ |
item1 item2 item3 | Lists, menus |
item$$ |
item01 item02 item03 | Small lists |
item$$$ |
item001 item002 item003 | Table rows, cards |
item$$$$ |
item0001 ... | Rare (thousands of rows) |
(3) Reverse $- and Starting Value $@N
By default, numbering starts from 1 ascending. Use @- for descending, and @N to start from N:
| Modifier | Meaning | Example (*3) |
|---|---|---|
$ (default) |
Ascending, from 1 | 1, 2, 3 |
$- or @- |
Descending (max → 1) | 3, 2, 1 |
$@3 |
Ascending, from 3 | 3, 4, 5 |
$-3 or @-3 |
Descending, from 3 backwards | 3, 2, 1 |
Key Point: In
$@N, N is the starting value, not the ending value. This is commonly used for scenarios like "starting from a specific number" (e.g., pagination page numbers, product SKU codes, version numbers).
5. Practical Examples
The following 5 examples cover all core combinations of [attr] and $—from single attributes to multiple attributes, from basic numbering to multi-digit zero-padding, from reverse to table scenarios.
▶ Example: Basic Attributes td[title="Hello" colspan=3] (Difficulty ⭐)
Add both title and colspan attributes to a td. This is one of the most classic attribute combinations in HTML tables.
Abbreviation:
td[title="Hello" colspan=3]
Expansion:
<td title="Hello" colspan="3"></td>
Output:
TEXT 📖 Display onlyDouble quotes wrap the title containing a space ("Hello"), and no quotes wrap the spaceless value (3). Emmet automatically adds quotes to all attributes regardless of whether you included them in the abbreviation. Multiple attributes are separated by spaces, which is different from Emmet's own "space = stop character" rule—spaces have special meaning inside square brackets. colspan=3 indicates the cell spans 3 columns horizontally.
▶ Example: Input Attributes input[type=text name=user placeholder=Username] (Difficulty ⭐)
Add type, name, and placeholder attributes to an input in one go. Input is one of the most "attribute-dense" tags in HTML.
Abbreviation:
input[type=text name=user placeholder=Username]
Expansion:
<input type="text" name="user" placeholder="Username">
Output:
TEXT 📖 Display onlyThree attributes completed in one abbreviation. Note: Emmet intelligently defaults input to type="text" (even if you omit type), so writing just `input[name=user placeholder=Username]` still produces type="text". If you want type="password" or type="email", you must write it explicitly. Three chained attributes are the highest-frequency shorthand in form scenarios.
▶ Example: Single $ Numbering ul>li.item$*5 (Difficulty ⭐)
Five li elements, each with a class that auto-increments from 1 to 5—the premier example from Emmet's official documentation.
Abbreviation:
ul>li.item$*5
Expansion:
<ul>
<li class="item1"></li>
<li class="item2"></li>
<li class="item3"></li>
<li class="item4"></li>
<li class="item5"></li>
</ul>
Output:
TEXT 📖 Display onlyFive li elements with classes item1, item2, item3, item4, item5. This is Emmet's standard pattern for uniquely numbered items (item + number). If the class name doesn't need a numeric part, simply write .item (without $), and all li elements share the same class.
▶ Example: Multi-Digit $$$ Zero-Padding ul>li.item$$$*5 (Difficulty ⭐)
Use 3 $ characters to auto-zero-pad numbering to 3 digits.
Abbreviation:
ul>li.item$$$*5
Expansion:
<ul>
<li class="item001"></li>
<li class="item002"></li>
<li class="item003"></li>
<li class="item004"></li>
<li class="item005"></li>
</ul>
Output:
TEXT 📖 Display onlyZero-padding digit count = number of $ characters. 3 $ characters = 3-digit zero-padding (item001, item002...). Zero-padding scenarios include: order numbers (prefixed, easy to sort), table IDs, SKUs, and chapter numbers. For 4-digit padding, use item$$$$; for 5-digit, use item$$$$$. Note: when the actual number exceeds the padding width, it is not truncated—e.g., *12 with $$ produces item01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12 (the first 9 are zero-padded; numbers 10–12 display with their actual digits because they exceed 2 digits).
▶ Example: Table + Numbering (table>(tr>td{item $})*3) (Difficulty ⭐⭐)
Grouping + repetition + text content—a "numbered list" of 3 rows × 1 column, using grouping so that td follows each tr's repetition.
Abbreviation:
(table>(tr>td{item $})*3)
Expansion:
<table>
<tr>
<td>item 1</td>
</tr>
<tr>
<td>item 2</td>
</tr>
<tr>
<td>item 3</td>
</tr>
</table>
Output:
TEXT 📖 Display onlyBreakdown: the outer parentheses encapsulate the entire table as an independent unit; the inner (tr>td{item $}) is a template for "one row with one item N," and *3 repeats this template 3 times. Each repetition replaces $ with the current sequence number (1, 2, 3), resulting in 3 tr elements each containing one td with text "item 1," "item 2," and "item 3." This is Emmet's core pattern for scenarios like "terms numbering," "todo list numbering," and "product lists." To get multiple cells per row, change td{item $} to td{item $}+td{...} (text content covered in Lesson 09).
6. Complete Example: Form Fieldset + Multi-Digit Zero-Padded IDs
Combine [attr] and $—a registration form with 5 input fields, each field having a 3-digit zero-padded id.
▶ Example: Registration Form + 3-Digit IDs (Difficulty ⭐⭐)
Abbreviation:
form#register>(fieldset>legend{Account Info})+(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]+{Subscribe to newsletter})+(button#btn-100.btn.btn-primary[type=submit]{Register})
Expansion:
<form id="register">
<fieldset>
<legend>Account Info</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">
Subscribe to newsletter
</label>
<button id="btn-100" class="btn btn-primary" type="submit">Register</button>
</form>
Output:
TEXT 📖 Display onlyBreakdown: this example combines all Phase 2 operators—> (nesting), + (sibling), #id, .class, [] (attributes), {} (text content). Note: each input's id is hand-written because Emmet's $ can only be used inside *N repetition structures. Here, the 5 inputs use a hybrid approach—"manual id + auto class"—where id serves as the form's "field anchor" (JS hook) and must be unique, while class serves as the "field type" (CSS selector) and can be reused. This "hand-written id + auto class" pattern is a high-frequency pattern in form development.
Try It Yourself: Type
td[title="Hello world!" colspan=3]into the Emmet Official Cheat Sheet to see the real-time expansion.
❓ FAQ
[attr] and [attr=]?[attr] is shorthand equivalent to [attr=""] (empty string value), expanding to <tag attr="">; [attr=] explicitly assigns an empty string, expanding to the same <tag attr="">. Both produce identical results. In HTML semantics, a valueless attribute (e.g., disabled="") behaves the same as a bare attribute name (disabled)—but Emmet defaults to generating the value form, so you may need to trim it manually.[data-id=$] work without *?* repetition, $ defaults to 1. So a[data-id=$] expands to <a data-id="1"></a>—which is usually not what you want. If you just want a data ID without numbering, write a[data-id=user] with the actual value.@- or $-?@N means start from N, @- means descending. The combined form $-3 is equivalent to @-3—both mean descending starting from 3. Using the @ prefixed form ($@3, @3, @-3) is recommended for clarity and compatibility.📖 Summary
[attr=val]adds arbitrary attributes to a tag; multiple attributes are separated by spaces[attr]valueless form is equivalent to[attr=""]; both generate empty-value attributes- Square brackets form a "closed" context—spaces are attribute delimiters, not stop characters
$is a numbering placeholder; pair it with*Nto auto-replace with the current sequence number (default ascending from 1)- Multiple
$characters auto-zero-pad ($$$→ 3-digit zero-padding) $@Nstarts numbering from N;@-produces descending numbering$can be placed in class, id, and attribute values- All 8 Phase 2 operators are now covered:
>+^*().#[]$
📝 Exercises
- Basic (Difficulty ⭐): Write one-line Emmet abbreviations that expand to: (a) an
atag withhref="#",class="link",data-id="$"(note:$without*defaults to 1); (b) atdwithtitle="Cell",colspan=2,rowspan=3; (c) aul>li*5, each li with a class ofrow-$(single-digit numbering). - Intermediate (Difficulty ⭐⭐): Write a 5-row × 4-column "grade sheet" with the first row as a header (th) and rows 2–5 as data (td), with data cell ids using 3-digit zero-padding (
cell-001throughcell-020). Write it all in one Emmet line and explain why $ can be used for ids but class wouldn't work the same way. - Challenge (Difficulty ⭐⭐⭐): Write a 100-row "Terms of Service table" with a 3-column structure: (a) a sequence number column using
td.item$@100*100for reverse numbering (from 100 down to 1); (b) a title column using{Term $}text content (text content covered in Lesson 09); (c) a content column left empty for now. Write it all in one Emmet line, verify the expansion, and compare the time difference between hand-writing 100 rows vs. one line of Emmet.