Emmet: ID and Class

Lesson 03 briefly introduced .class and #id. This lesson systematically covers these two "CSS selector-style" shorthand operators—they allow Emmet abbreviations to express both structure and attributes at once. The most critical rule: omitting a tag name in HTML context defaults to div, and Emmet will never "guess" a different element based on a class name (even if the class is btn, it won't become a <button>).

1. What You Will Learn


2. A Frontend Developer's Real Story

(1) The Pain: Tags and Attributes Always Needed Two Separate Passes

Alice was upgrading the styling of a login form. She needed to add class="btn btn-primary btn-lg" to every <button> and id="loginForm" with class="form-horizontal" to the form itself. Manually, every tag required writing the full structure:

HTML
<button class="btn btn-primary btn-lg">Login</button>
<input id="username" class="form-control" type="text" name="user">

Every tag repeated the tag name plus attributes, and the order of tags and attributes relied on memory. By her count: an 8-line login form required hand-writing 8 tags + 24 attribute names = 32 repeated text segments. Just typing the quotes and equals signs for attributes was making her fingers ache.

Even worse: she recalled a previous project where she mistakenly assumed .btn would automatically trigger a <button> tag—a misunderstanding that sent her down a troubleshooting rabbit hole. Emmet's implicit tag rule absolutely does not switch tag names based on class names.

(2) How .class and #id Shorthand Solves It

Emmet uses exactly the same syntax as CSS selectors to express attributes—.btn directly adds to the current tag's class attribute, and #loginForm adds to the id attribute. This syntax lets you write attributes at the same time you write the tag structure, without switching between "tag mode" and "attribute mode."

Alice rewrote the entire login form with a single line of Emmet:

TEXT 📖 Display only
form#loginForm.form-horizontal>(input#username.form-control[type=text name=user])+(input#pwd.form-control[type=password name=pwd])+(button.btn.btn-primary.btn-lg{Login})

#loginForm.form-horizontal sets both id and class simultaneously. .btn.btn-primary.btn-lg chains three classes with . in one go. 8 tags + 24 attributes, handled in 1 line. From 32 repeated text segments to 1 line, from 5 minutes to 12 seconds.

(3) Benefits

.class and #id are the key operators that merge "structure" and "attributes" into one in Emmet. After this lesson, Alice can write attributes for 80% of her tags simultaneously; the multi-class syntax is identical to CSS, with nearly zero learning curve; and remembering the "omit tag name → div" implicit rule (even if the class is btn, it stays a div) prevents her abbreviations from being misled by class names.


3. .class: CSS Selector-style Class Attribute

.classname in Emmet is equivalent to adding class="classname" to the current tag. When no tag is specified (Lesson 03 implicit rule), it defaults to div.

Abbreviation Expansion Notes
.box <div class="box"></div> Implicit div
p.text <p class="text"></p> Explicit p
section.hero <section class="hero"></section> Explicit section
a.link <a href="" class="link"></a> Explicit a, a auto-adds href

Tip: Multiple classes are chained with . with no spaces between class names.btn.btn-primary means two classes: btn and btn-primary. Spaces in Emmet are stop characters; they are parsed as multiple independent abbreviations.


4. #id: CSS Selector-style id Attribute

#idname in Emmet is equivalent to adding id="idname" to the current tag. Usage is completely symmetric with .class and corresponds to the CSS ID selector (#id).

Abbreviation Expansion
#main <div id="main"></div>
header#top <header id="top"></header>
form#login <form id="login"></form>
div#app <div id="app"></div>

(1) id and class Can Be Mixed

#id and .class on the same tag have no ordering requirement and can be freely combined:

Abbreviation Expansion
#main.container <div id="main" class="container"></div>
.container#main <div class="container" id="main"></div>
header#top.nav <header id="top" class="nav"></header>
nav.main.dark <nav class="main dark"></nav>

Key Point: Emmet's #id and .class come from CSS selector syntax, but the semantics are reversed—CSS uses # and . to select existing elements, while Emmet uses # and . to generate elements with those attributes. Same syntax, opposite purpose.


5. Practical Examples

The following 5 examples cover all core scenarios for .class and #id—from basic single class, to multiple classes, to attribute chains in nested structures.

▶ Example: Single Class .container (Difficulty ⭐)

The most basic class shorthand: omit the tag name and it defaults to div.

Abbreviation:

HTML
.container

Expansion:

HTML
<div class="container"></div>

Output:

TEXT 📖 Display only
Implicit div tag + single class. This shorthand is extremely common in utility-class frameworks like Bootstrap and Tailwind. .container in Bootstrap is a fixed-width container; in Tailwind it is a max-width utility. Note: when you write .container inside a ul parent, .container still implicitly becomes a div—unless your abbreviation position genuinely requires a li child of ul, .container will never auto-morph into a semantic element like li (Lesson 06 implicit rules).

▶ Example: Single ID #main (Difficulty ⭐)

The most basic id shorthand.

Abbreviation:

HTML
#main

Expansion:

HTML
<div id="main"></div>

Output:

TEXT 📖 Display only
An empty div with id="main". In HTML, ids must be globally unique, so this shorthand is often used for "page main container" or "route mount point." Note: Emmet does not validate id uniqueness—multiple #main abbreviations all expand to id="main"; whether that causes a conflict is up to you to check manually.

▶ Example: Multi-Class Chain .btn.btn-primary.btn-lg (Difficulty ⭐⭐)

Chaining multiple classes with .—note: the default is still <div>; it will not become <button> just because a class name contains "btn."

Abbreviation:

HTML
.btn.btn-primary.btn-lg

Expansion:

HTML
<div class="btn btn-primary btn-lg"></div>

Output:

TEXT 📖 Display only
This is the abbreviation beginners misunderstand most often: because the class name is `btn`, many assume Emmet will recognize `.btn` as a `<button>`. But Emmet's implicit rules do not depend on class/id values—they only depend on parent element context (see Lesson 06). When there is no parent context and no explicit tag, Emmet always defaults to `div`. To get `<button class="btn btn-primary btn-lg">`, you must explicitly write `button.btn.btn-primary.btn-lg` or `button.btn.btn-primary.btn-lg{Login}` (with text content, Lesson 09). Verify this yourself: type `.btn` in your editor and press Tab to see whether it expands to `div` or `button`.

▶ Example: id + class Combination #header.nav (Difficulty ⭐)

Mixing #id and .class—order does not affect the expansion result.

Abbreviation:

HTML
#header.nav

Expansion:

HTML
<div id="header" class="nav"></div>

Output:

TEXT 📖 Display only
Sets both id="header" and class="nav" at once. This is the classic shorthand for a page header navigation. The order of id and class can be swapped (.nav#header produces the same result), but writing id first aligns with the semantic habit of "establish unique identity, then describe type." This shorthand is important in multi-page applications—the #header id remains the same across pages, so CSS can target it uniformly.

▶ Example: Nested Attribute Chain div#main.container>section.content>p.text (Difficulty ⭐⭐)

Distributing id and class across every level of nesting—3 levels of tags, each with its own attributes.

Abbreviation:

HTML
div#main.container>section.content>p.text

Expansion:

HTML
<div id="main" class="container">
  <section class="content">
    <p class="text"></p>
  </section>
</div>

Output:

TEXT 📖 Display only
Breakdown: div carries id="main" and class="container", drills down one level to section (with class="content"), then drills down to p (with class="text"). Every level sets its own attributes via #id or .class. This is the typical Emmet usage for "component-based structure"—each level represents a semantically clear container or component. If you only want to set attributes on one level (leaving others empty), simply omit #/. from those levels—e.g., div>section>p is just 3 empty nesting levels.

6. Complete Example: Login Form + Attribute Chain

Combine .class and #id with the > and + operators from earlier lessons to build a complete login form in one line.

▶ Example: Complete Login Form (Difficulty ⭐⭐)

Abbreviation:

HTML
form#loginForm.form-horizontal>(input#user.form-control[type=text name=user placeholder="Username"])+(input#pwd.form-control[type=password name=pwd placeholder="Password"])+(button.btn.btn-primary.btn-lg{Login})

Expansion:

HTML
<form id="loginForm" class="form-horizontal">
  <input id="user" class="form-control" type="text" name="user" placeholder="Username">
  <input id="pwd" class="form-control" type="password" name="pwd" placeholder="Password">
  <button class="btn btn-primary btn-lg">Login</button>
</form>

Output:

TEXT 📖 Display only
Breakdown: form gets both id and class. Inside, + adds two input siblings, then + adds a button. Both inputs and buttons get styling classes via .class, and inputs additionally use #id (optional) and [type=name=placeholder=] (covered in the next lesson) for attributes. Note that the button level explicitly writes `<button>`, so .btn.btn-primary.btn-lg becomes a class on a button rather than an implicit div. This is the standard pattern of "using explicit tags to avoid misjudgment."

❓ FAQ

Q Does .btn.btn-primary expand to <button> or <div>? Why?
A <div>. Emmet's implicit tag rules depend only on parent element context, not on the literal value of class/id names. Unless you explicitly write button.btn.btn-primary, the default when there is no explicit tag is always div. This is the most common pitfall for beginners—many assume .btn should map to button, and then think the div expansion is a bug.
Q Do hyphens in #my-id need to be escaped?
A No. Hyphens in #my-id and .my-class are valid CSS selector characters; Emmet treats them as part of the id/class name. However, an initial hyphen (e.g., #-main) is not valid CSS syntax and Emmet may throw an error or parse unexpectedly.
Q How many times can # and . be mixed on the same tag?
A Theoretically unlimited. #a.b.c.d.e.f.g is fully valid and expands to <div id="a" class="b c d e f g"></div>. In practice, ids should be unique and limited in number, and too many classes make style management harder. Recommendation: 1 id + 2–4 classes is a common "reasonable upper limit."
Q What is the difference between #header and .header after expansion?
A .header<div class="header"></div> (class is for reusable "type"); #header<div id="header"></div> (id is for "unique identity"). At the CSS selector level: .header can match multiple elements, #header can only match one. At the HTML semantic level: class is typically used for "component type" (button, card, form), while id is typically used for "page anchor or JS hook."

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Write 5 Emmet abbreviations that expand to: (a) .alert; (b) #app; (c) .btn.btn-danger; (d) #header.nav.dark; (e) a.btn.
  2. Intermediate (Difficulty ⭐⭐): Analyze the expansion structure of header#top.navbar>nav.main>ul.nav-list>li.nav-item*3>a.nav-link (name each tag and attribute at every level), and verify that Emmet's expansion matches your analysis.
  3. Challenge (Difficulty ⭐⭐⭐): Write a complete Bootstrap-style "card grid": outer div.container, containing div.row, which contains 3 .card elements. Each card includes img.card-img-top, div.card-body, h5.card-title, p.card-text, and a.btn.btn-primary. Write the entire structure in one line of Emmet, and explain why .btn.btn-primary here automatically becomes <a> instead of an implicit div (hint: is there an explicit tag in the abbreviation?).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

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

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